Skip to content

Commit 2190291

Browse files
authored
Merge pull request #22 from bentleygd/patch/v0.1.1
Documentation updates and bug fixes.
2 parents a300683 + 9023c29 commit 2190291

File tree

5 files changed

+142
-104
lines changed

5 files changed

+142
-104
lines changed

.github/workflows/pythonapp.yml

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,33 @@
1-
name: Lint
1+
name: Lint and Test
22

33
on:
4-
push:
5-
branches: [ "master" ]
64
pull_request:
7-
branches: [ "master" ]
5+
branches:
6+
- master
87

98
jobs:
10-
lint:
9+
Lint:
1110

1211
runs-on: ubuntu-latest
1312

1413
steps:
15-
- uses: actions/checkout@v2
16-
- name: Set up Python 3.8
14+
- uses: actions/checkout@v1
15+
- name: Set up Python 3.11.10
1716
uses: actions/setup-python@v1
1817
with:
19-
python-version: 3.8
18+
python-version: 3.11.10
2019
- name: Install dependencies
2120
run: |
2221
python -m pip install --upgrade pip
23-
pip install -r requirements.txt
22+
python -m pip install -r requirements.txt
23+
# Installing testing dependencies
24+
python -m pip install flake8 bandit
2425
- name: Lint with flake8
2526
run: |
26-
pip install flake8
2727
# stop the build if there are Python syntax errors or undefined names
2828
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
29-
# exit-zero treats all errors as warnings. PEP8 max line length is 79
29+
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
3030
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=79 --statistics
31-
- name: Security lint with bandit
31+
- name: Test with bandit
3232
run: |
33-
pip install bandit
34-
bandit -ll . --recursive
33+
bandit -ll --recursive .

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Python scripts that utilize Trend Micro Cloud App Security APIs for phishing identification, mitigation and remediation. As a courtesy warning, if Trend Micro ever substantially changes their APIs this code may not work.
44

5-
[![Total alerts](https://img.shields.io/lgtm/alerts/g/bentleygd/orca.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/bentleygd/orca/alerts/) [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/bentleygd/orca.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/bentleygd/orca/context:python) [![Known Vulnerabilities](https://snyk.io/test/github/bentleygd/orca/badge.svg)](https://snyk.io/test/github/bentleygd/orca)
5+
[![Known Vulnerabilities](https://snyk.io/test/github/bentleygd/orca/badge.svg)](https://snyk.io/test/github/bentleygd/orca) ![Lint with Bandit and Flake8](https://github.com/bentleygd/ITGC/workflows/Lint/badge.svg) ![CodeQL](https://github.com/bentleygd/CSIC/workflows/CodeQL/badge.svg)
66

77
## Purpose
88
The purpose of orca is to automate finding and removing phishing emails for customers of Trend Micro Cloud App Security. The utilization of orca can significantally reduce man hours spent on containing and eradicating phishing threats. The CLI client can also be leveraged to "deputize" teams outside core security teams (such as the help desk) so that phishing threats can be addressed as soon as users report them to the help desk instead of having to wait for the extra minutes needed to notify the security team.

cli_client.py

Lines changed: 52 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
"""
2+
This module is a CLI script that calls the class/functions in orca.py and
3+
coreutils.py. This is the 'main' script that is executed by usres to find
4+
and remove phishing emails.
5+
6+
Classes:
7+
None
8+
9+
Functions:
10+
None
11+
"""
112
from argparse import ArgumentParser
213
from configparser import ConfigParser
314
from logging import basicConfig, DEBUG, getLogger
@@ -64,46 +75,36 @@
6475
# Looking for phishing emails based on supplied arguments.
6576
# Check if URL is supplied.
6677
if orca_args.url is not None:
67-
# Performing input validation.
68-
url_validate = validate.URL(orca_args.url)
69-
if url_validate is False:
70-
print('Input validation for URL failed. Exiting')
71-
exit(1)
7278
# Finding and pulling emails with indicated URL.
7379
phish_list = phish_hunt.find_phish(url=orca_args.url)
7480
print('*' * 32 + 'WARNING' + '*' * 32)
75-
print('You are going to pull email from %d mailboxes.' % len(phish_list))
81+
print('You are going to pull email from %d mailboxes.', len(phish_list))
7682
warning = str(input('Press Y/N to continue> '))
7783
if warning.lower() == 'y':
78-
log.info('Acknowledgment accepted for %d mailboxes' % len(phish_list))
84+
log.info('Acknowledgment accepted for %d mailboxes', len(phish_list))
7985
else:
8086
print('*' * 32 + 'ABORTING' + '*' * 32)
8187
exit(0)
8288
# Checking for pull or purge and taking appropriate action.
83-
log.debug('Performing URL pull for %s' % orca_args.url)
89+
log.debug('Performing URL pull for %s', orca_args.url)
8490
if orca_args.action == 'pull':
8591
phish_hunt.pull_email(phish_list)
8692
# elif orca_args.action == 'purge':
8793
# phish_hunt.purge_email(phish_list)
8894
# Check if file hash is supplied.
8995
elif orca_args.hash is not None:
90-
# Performing input validation.
91-
hash_validate = validate.SHA1(orca_args.hash)
92-
if hash_validate is False:
93-
print('SHA1 hash failed input validation. Exiting.')
94-
exit(1)
9596
# Finding and pulling emails with indicated file hash.
9697
phish_list = phish_hunt.find_phish(file_hash=orca_args.hash)
9798
print('*' * 32 + 'WARNING' + '*' * 32)
98-
print('You are going to pull email from %d mailboxes.' % len(phish_list))
99+
print('You are going to pull email from %d mailboxes.', len(phish_list))
99100
warning = str(input('Press Y/N to continue> '))
100101
if warning.lower() == 'y':
101-
log.info('Acknowledgment accepted for %d mailboxes' % len(phish_list))
102+
log.info('Acknowledgment accepted for %d mailboxes', len(phish_list))
102103
else:
103104
print('*' * 32 + 'ABORTING' + '*' * 32)
104105
exit(0)
105106
# Checking for pull or purge and taking appropriate action.
106-
log.debug('Performing SHA1 hash pull for %s' % orca_args.hash)
107+
log.debug('Performing SHA1 hash pull for %s', orca_args.hash)
107108
if orca_args.action == 'pull':
108109
phish_hunt.pull_email(phish_list)
109110
# elif orca_args.action == 'purge':
@@ -115,11 +116,11 @@
115116
orca_args.file_extension is not None
116117
):
117118
# Beginning input validation.
118-
validate_sender = validate.Email(orca_args.sender)
119+
validate_sender = validate.email(orca_args.sender)
119120
if validate_sender is False:
120121
print('Sender email address faield input validation. Exiting.')
121122
exit(1)
122-
validate_file = validate.FileExt(orca_args.file_extension)
123+
validate_file = validate.file_ext(orca_args.file_extension)
123124
if validate_file is False:
124125
print('File extension input validation failed. Exiting.')
125126
exit(1)
@@ -130,10 +131,10 @@
130131
file_ext=orca_args.file_extension
131132
)
132133
print('*' * 32 + 'WARNING' + '*' * 32)
133-
print('You are going to pull email from %d mailboxes.' % len(phish_list))
134+
print('You are going to pull email from %d mailboxes.', len(phish_list))
134135
warning = str(input('Press Y/N to continue> '))
135136
if warning.lower() == 'y':
136-
log.info('Acknowledgment accepted for %d mailboxes' % len(phish_list))
137+
log.info('Acknowledgment accepted for %d mailboxes', len(phish_list))
137138
else:
138139
print('*' * 32 + 'ABORTING' + '*' * 32)
139140
exit(0)
@@ -152,7 +153,7 @@
152153
elif (orca_args.sender is not None and
153154
orca_args.subject is not None):
154155
# Performing input validation.
155-
sender_validate = validate.Email(orca_args.sender)
156+
sender_validate = validate.email(orca_args.sender)
156157
if sender_validate is False:
157158
print('Sender email address failed validation. Exiting.')
158159
exit(1)
@@ -161,10 +162,10 @@
161162
subject=orca_args.subject
162163
)
163164
print('*' * 32 + 'WARNING' + '*' * 32)
164-
print('You are going to pull email from %d mailboxes.' % len(phish_list))
165+
print('You are going to pull email from %d mailboxes.', len(phish_list))
165166
warning = str(input('Press Y/N to continue> '))
166167
if warning.lower() == 'y':
167-
log.info('Acknowledgment accepted for %d mailboxes' % len(phish_list))
168+
log.info('Acknowledgment accepted for %d mailboxes', len(phish_list))
168169
else:
169170
print('*' * 32 + 'ABORTING' + '*' * 32)
170171
exit(0)
@@ -182,11 +183,11 @@
182183
elif (orca_args.sender is not None and
183184
orca_args.file_extension is not None):
184185
# Performing input validation.
185-
sender_validate = validate.Email(orca_args.sender)
186+
sender_validate = validate.email(orca_args.sender)
186187
if sender_validate is False:
187188
print('Sender email address failed input validation. Exiting.')
188189
exit(1)
189-
file_validate = validate.FileExt(orca_args.file_extension)
190+
file_validate = validate.file_ext(orca_args.file_extension)
190191
if file_validate is False:
191192
print('File extension input validation failed. Exiting.')
192193
exit(1)
@@ -196,10 +197,10 @@
196197
file_ext=orca_args.file_extension
197198
)
198199
print('*' * 32 + 'WARNING' + '*' * 32)
199-
print('You are going to pull email from %d mailboxes.' % len(phish_list))
200+
print('You are going to pull email from %d mailboxes.', len(phish_list))
200201
warning = str(input('Press Y/N to continue> '))
201202
if warning.lower() == 'y':
202-
log.info('Acknowledgment accepted for %d mailboxes' % len(phish_list))
203+
log.info('Acknowledgment accepted for %d mailboxes', len(phish_list))
203204
else:
204205
print('*' * 32 + 'ABORTING' + '*' * 32)
205206
exit(0)
@@ -216,22 +217,39 @@
216217
# Checking only for sender
217218
elif orca_args.sender is not None:
218219
# Performing input validation.
219-
validate_sender = validate.Email(orca_args.sender)
220-
if validate_sender is False:
221-
print('Sender email address failed input validation. Exiting.')
222-
exit(1)
220+
# validate_sender = validate.Email(orca_args.sender)
221+
# if validate_sender is False:
222+
# print('Sender email address failed input validation. Exiting.')
223+
# exit(1)
223224
# Finding and pulling emails that match the sender.
224225
phish_list = phish_hunt.find_phish(sender=orca_args.sender)
225226
print('*' * 32 + 'WARNING' + '*' * 32)
226-
print('You are going to pull email from %d mailboxes.' % len(phish_list))
227+
print('You are going to pull email from %d mailboxes.', len(phish_list))
228+
warning = str(input('Press Y/N to continue> '))
229+
if warning.lower() == 'y':
230+
log.info('Acknowledgment accepted for %d mailboxes', len(phish_list))
231+
else:
232+
print('*' * 32 + 'ABORTING' + '*' * 32)
233+
exit(0)
234+
# Checking for pull or purge and taking appropriate action.
235+
log.debug('Pulling email based on sender: %s', orca_args.sender)
236+
if orca_args.action == 'pull':
237+
phish_hunt.pull_email(phish_list)
238+
# elif orca_args.action == 'purge':
239+
# phish_hunt.purge_email(phish_list)
240+
# Chcking for subject only
241+
elif orca_args.subject is not None:
242+
phish_list = phish_hunt.find_phish(subject=orca_args.subject)
243+
print('*' * 32 + 'WARNING' + '*' * 32)
244+
print('You are going to pull email from %d mailboxes.', len(phish_list))
227245
warning = str(input('Press Y/N to continue> '))
228246
if warning.lower() == 'y':
229-
log.info('Acknowledgment accepted for %d mailboxes' % len(phish_list))
247+
log.info('Acknowledgment accepted for %d mailboxes', len(phish_list))
230248
else:
231249
print('*' * 32 + 'ABORTING' + '*' * 32)
232250
exit(0)
233251
# Checking for pull or purge and taking appropriate action.
234-
log.debug('Pulling email based on sender: %s' % orca_args.sender)
252+
log.debug('Pulling email based on sender: %s', orca_args.sender)
235253
if orca_args.action == 'pull':
236254
phish_hunt.pull_email(phish_list)
237255
# elif orca_args.action == 'purge':

libs/coreutils.py

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
#!/usr/bin/python3
1+
"""
2+
This module provides basic functions and methods meant to be used by other
3+
modules.
4+
5+
Functions:
6+
mail_send - sends email via SMTP.
7+
get_credentials - retrieves credentials from an encrypted password file.
8+
9+
Classes:
10+
ValidateInput - performs input validation.
11+
"""
212
from socket import gethostbyname, gaierror
313
from smtplib import SMTP, SMTPConnectError
414
from email.mime.text import MIMEText
@@ -34,11 +44,11 @@ def mail_send(mail_info):
3444
try:
3545
s = SMTP(gethostbyname(mail_info['server']), '25')
3646
except gaierror:
37-
print('Hostname resolution of %s failed.' % mail_info['server'])
47+
print('Hostname resolution of %s failed.', mail_info['server'])
3848
exit(1)
3949
except SMTPConnectError:
4050
print('Unable to connect to %s, the server refused the ' +
41-
'connection.' % mail_info['server'])
51+
'connection.', mail_info['server'])
4252
exit(1)
4353
# Sending the mail.
4454
s.sendmail(mail_info['sender'], mail_info['recipients'], msg.as_string())
@@ -70,7 +80,7 @@ def get_credentials(scss_dict):
7080
# Connecting to SCSS. If SSL verification fails, change verify to
7181
# false. This isn't recommended (as it defeats the purpose of
7282
# verification), but it will make the code work in an emergency.
73-
scss_response = post(url, headers=headers)
83+
scss_response = post(url, headers=headers, timeout=5)
7484
try:
7585
scss_response.raise_for_status
7686
except HTTPError:
@@ -85,35 +95,17 @@ def get_credentials(scss_dict):
8595

8696

8797
class ValidateInput:
98+
"""Performs input validation."""
8899
def __init__(self):
89100
"""Input validation class
90101
91102
Methods:
92-
URL - Input validation for a URL.
93103
SHA1 - Input validation for a SHA1 hash.
94104
Email - Input validation for a email address.
95105
FileExt - Input validation for a file extension.
96106
Subject - Input validation for email subject line."""
97107

98-
def URL(self, url):
99-
"""Input validation for a URL.
100-
101-
Input:
102-
url - str(), The supplied URL to validate.
103-
104-
Returns:
105-
Boolean - The method will return True if input validation
106-
passes or False if input validation fails."""
107-
url_pattern = (
108-
r'(http:|https:)\/\/(\w+\.\w+|\w+\.\w+\.\w+|\w+\.\w+\.\w+\.\w+)\/\S+'
109-
)
110-
url_validate = match(url_pattern, url)
111-
if url_validate:
112-
return True
113-
else:
114-
return False
115-
116-
def Email(self, email):
108+
def email(self, email):
117109
"""Input validation for an email address.
118110
119111
Input:
@@ -131,7 +123,7 @@ def Email(self, email):
131123
else:
132124
return False
133125

134-
def SHA1(self, hash):
126+
def sha1(self, _hash):
135127
"""Input validation for a SHA1 hash.
136128
137129
Input:
@@ -140,14 +132,14 @@ def SHA1(self, hash):
140132
Returns:
141133
Boolean - The method will return True if input validation
142134
passes or False if input validation fails."""
143-
hash_pattern = r'[a-z0-9]{40}'
144-
hash_validate = match(hash_pattern, hash)
135+
hash_pattern = r'[a-zA-Z0-9]{40}'
136+
hash_validate = match(hash_pattern, _hash)
145137
if hash_validate:
146138
return True
147139
else:
148140
return False
149141

150-
def FileExt(self, file_ext):
142+
def file_ext(self, file_ext):
151143
"""Input validation for a file extension.
152144
153145
Input:
@@ -163,7 +155,7 @@ def FileExt(self, file_ext):
163155
else:
164156
return False
165157

166-
def Subject(self, subject_line):
158+
def subject(self, subject_line):
167159
"""Input validation for an email subject line.
168160
Yes, it isn't a whole lot.
169161

0 commit comments

Comments
 (0)