Skip to content

Commit

Permalink
Added functionality for attachments, mobile scope, template variable …
Browse files Browse the repository at this point in the history
…replacement, message sanitation, and changed scopes to not save to db by default
  • Loading branch information
RedByte1337 authored and bamhm182 committed Nov 22, 2022
1 parent 86b8b86 commit bee0e65
Show file tree
Hide file tree
Showing 15 changed files with 412 additions and 34 deletions.
16 changes: 8 additions & 8 deletions checks.sh
Expand Up @@ -12,19 +12,19 @@ diff_arrays() {
done
}


# Check Plugins
for plugin in ./src/synack/plugins/*.py; do
p=$(basename ${plugin})
p=${p%.*}
defs=($(awk -F'[ (]*' '/ def / {print $3}' ${plugin} | egrep -v "__init__|__init_subclass__|_fk_pragma"))
readarray -t a_defs < <(printf '%s\n' "${defs[@]}" | sort)
# Check Alphabetical
if [[ "${defs[@]}" != "${a_defs[@]}" ]]; then
echo ${plugin} is not in alphabetical order
#echo -e "\tBad: ${defs[@]}"
#echo -e "\tGood: ${a_defs[@]}"
diff_arrays defs a_defs
fi
for def in ${defs}; do
# Check Missing Documentation
for def in ${defs[@]}; do
grep "## ${p}.${def}" ./docs/src/usage/plugins/${p}.md > /dev/null 2>&1
if [[ $? != 0 ]]; then
grep "def ${def}(" ${plugin} -B1 | grep "@property" > /dev/null 2>&1
Expand All @@ -35,24 +35,24 @@ for plugin in ./src/synack/plugins/*.py; do
done
done

# Check Tests
for test in ./test/test_*.py; do
defs=($(awk -F'[ (]*' '/ def / {print $3}' ${test} | egrep -v "__init__|setUp"))
readarray -t a_defs < <(printf '%s\n' "${defs[@]}" | sort)
# Check Alphabetical
if [[ "${defs[@]}" != "${a_defs[@]}" ]]; then
echo ${test} is not in alphabetical order
#echo -e "\tBad: ${defs[@]}"
#echo -e "\tGood: ${a_defs[@]}"
diff_arrays defs a_defs
fi
done

# Check Docs
for doc in ./docs/src/usage/plugins/*.md; do
defs=($(awk -F'[ (]*' '/## / {print $2}' ${doc}))
readarray -t a_defs < <(printf '%s\n' "${defs[@]}" | sort)
# Check Alphabetical
if [[ "${defs[@]}" != "${a_defs[@]}" ]]; then
echo ${doc} is not in alphabetical order
#echo -e "\tBad: ${defs[@]}"
#echo -e "\tGood: ${a_defs[@]}"
diff_arrays defs a_defs
fi
done
Expand Down
25 changes: 25 additions & 0 deletions docs/src/usage/plugins/alerts.md
Expand Up @@ -26,6 +26,31 @@ The functions within this plugin don't follow the standard naming convention.
>> >>> h.alerts.email('Look out!', 'Some other important thing happened!')
>> ```
## alerts.sanitize(message):

> This function aims to remove URLs, IPv4, and IPv6 content from a given message.
> Sometimes Synack puts sensitive URLs and IP addresses in content like Mission Titles,
> so if you are sending these through 3rd party networks (Slack, Discord, Email, SMS, etc.),
> please make sure that you do you due dilligence to ensure you aren't sending client information.
>
> This function has been tested to ensure a wide variety of sensitive data is stripped, but it might
> not be all inclusive. If you find sensitive data that it doesn't properly sanitize, please let me
> know and we'll get it addressed.
>
> | Arguments | Type | Description
> | --- | --- | ---
> | `message` | str | A message to sanitize
>
>> Examples
>> ```python3
>> >>> h.alerts.sanitize('This is an IPv4: 1.2.3.4')
>> This is an IP: [IPv4]
>> >>> h.alerts.sanitize('This is an IP: 1234:1d8::4567:2345')
>> This is an IPv6: [IPv6]
>> >>> h.alerts.sanitize('This is a URL: https://something.com')
>> This is a URL: [URL]
>> ```
## alerts.slack(message)

> This function makes a POST request to Slack in order to post a message.
Expand Down
33 changes: 32 additions & 1 deletion docs/src/usage/plugins/scratchspace.md
Expand Up @@ -34,9 +34,39 @@
>> '/tmp/Scratchspace/ADAMANTANT/burp.txt'
>> ```
## scratchspace.set_download_attachments(attachments, target=None, codename=None, prompt_overwrite=True):

> This function will take a list of attachments from `h.targets.get_attachments()` and download them to the `codename` folder wthin the `self.db.scratchspace_dir` folder.
>
> | Arguments | Type | Description
> | --- | --- | ---
> | `attachments` | list(dict) | A list of attachments from `h.targets.get_attachments()`
> | `target` | db.models.Target | A Target Database Object
> | `codename` | str | Codename of a Target
> | `prompt_overwrite` | bool | Boolean to determine if you should be prompted before overwriting an existing file
>
>> Examples
>> ```python3
>> >>> attachments = h.targets.get_attachments()
>> >>> slug = attachments[0].get('listing_id')
>> >>> codename = h.targets.build_codename_from_slug(slug)
>> >>> h.scratchspace.set_download_attachments(attachments, codename=codename)
>> [PosixPath('/home/user/Scratchspace/SLEEPYTURTLE/file1.txt'), ...]
>> ```
>> ```python3
>> >>> h.scratchspace.set_download_attachments(attachments, codename=codename)
>> file1.txt exists. Overwrite? [y/N]: Y
>> [PosixPath('/home/user/Scratchspace/SLEEPYTURTLE/file1.txt'), ...]
>> >>> h.scratchspace.set_download_attachments(attachments, codename=codename)
>> file1.txt exists. Overwrite? [y/N]: N
>> []
>> >>> h.scratchspace.set_download_attachments(attachments, codename=codename, prompt_overwrite=False)
>> [PosixPath('/home/user/Scratchspace/SLEEPYTURTLE/file1.txt'), ...]
>> ```
## scratchspace.set_hosts_file(content, target=None, codename=None)

> This function will save a `hosts.txt` scope file within a `codename` folder in within the `self.db.scratchspace_dir` folder
> This function will save a `hosts.txt` scope file within a `codename` folder in within the `self.db.scratchspace_dir` folder.
> If `self.db.use_scratchspace` is `True`, this function is automatically run when you do `self.targets.get_scope()` or `self.targets.get_scope_host()`
>
> | Arguments | Type | Description
Expand All @@ -51,3 +81,4 @@
>> >>> h.scratchspace.set_hosts_file(scope, codename='ADAMANTARDVARK')
>> '/tmp/Scratchspace/ADAMANTARDVARK/hosts.txt'
>> ```
17 changes: 17 additions & 0 deletions docs/src/usage/plugins/targets.md
Expand Up @@ -120,6 +120,23 @@
>> [{"id": 1, ...},...]
>> ```
## targets.get_attachments(target, **kwargs)
> Gets the attachments of a specific target.
>
> | Arguments | Type | Description
> | --- | --- | ---
> | `target` | db.models.Target | A single Target returned from the database
> | `kwargs` | kwargs | Information used to look up a Target in the database (ex: `codename`, `slug`, etc.)
>
>> Examples
>> ```python3
>> >>> h.targets.get_attachments(codename='SLAPPYFROG')
>> [{
>> 'id': 1337, 'listing_id': '7sl4ppyfr0g', 'created_at': 1659461184, 'updated_at': 1659712248,
>> 'filename': 'FrogApp.apk', 'url': 'https://storage.googleapis.com/...'
>> }, ...]
## targets.get_connected()
> Return minimal information about your currently connected Target
Expand Down
17 changes: 17 additions & 0 deletions docs/src/usage/plugins/templates.md
Expand Up @@ -32,6 +32,23 @@
>> '/home/user/Templates/mission/web/mission_without_a_template.txt'
>> ```
## templates.build_replace_variables(text, target=None, **kwargs)

> Replaces some variables within a given piece of text based on the target provided
>
> | Arguments | Type | Description
> | --- | --- | ---
> | `text` | str | String to replace variables within
> | `target` | db.models.Target | Target to use for variables
> | `kwargs` | kwargs | Key word arguments to use for finding a target (codename, slug, etc.)
>
>> Examples
>> ```python3
>> >>> target = h.db.find_targets(slug='2oh3ui')[0]
>> >>> h.templates.build_replace_variables("This mission is for {{ TARGET_CODENAME }}", target=target)
>> This mission is for TRANSFORMERTURKEY
>> ```
## templates.build_safe_name(name)
> Takes a name and converts it into something that is definitely safe for a filepath
Expand Down
22 changes: 22 additions & 0 deletions src/synack/plugins/alerts.py
Expand Up @@ -6,6 +6,7 @@
import email
import datetime
import json
import re
import requests
import smtplib

Expand Down Expand Up @@ -36,6 +37,27 @@ def email(self, subject='Test Alert', message='This is a test'):
server.login(self.db.smtp_username, self.db.smtp_password)
server.send_message(msg)

def sanitize(self, message):
message = re.sub(r'[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}.[0-9]{1,3}', '[IPv4]', message)
message = re.sub(r'(?:https?:\/\/)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}' +
r'\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\\/=]*)', '[URL]', message)
message = re.sub(r'(?:https?:\/\/)?(?:www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}' +
r'\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&\\/=]*)', '[URL]', message)
message = re.sub(r'[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b' +
r'(?:[-a-zA-Z0-9()@:%_\+.~#?&\\/=]*)', '[URL]', message)
message = re.sub(r'(?:^|(?<=\s))(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|' +
r'([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|' +
r'([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}' +
r'(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|' +
r'([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:' +
r'((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:' +
r'(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}' +
r'((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}' +
r'(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:' +
r'((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}' +
r'(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(?=\s|$)', '[IPv6]', message)
return message

def slack(self, message='This is a test'):
requests.post(self.db.slack_url,
data=json.dumps({'text': message}),
Expand Down
9 changes: 8 additions & 1 deletion src/synack/plugins/missions.py
Expand Up @@ -62,7 +62,14 @@ def build_summary(self, missions):
except ValueError:
claimed_on = datetime.strptime(m['claimedOn'],
"%Y-%m-%dT%H:%M:%SZ")
elapsed = int((utc - claimed_on).total_seconds())
try:
modified_on = datetime.strptime(m['modifiedOn'],
"%Y-%m-%dT%H:%M:%S.%fZ")
except ValueError:
modified_on = datetime.strptime(m['modifiedOn'],
"%Y-%m-%dT%H:%M:%SZ")
report_time = claimed_on if claimed_on > modified_on else modified_on
elapsed = int((utc - report_time).total_seconds())
time = m['maxCompletionTimeInSecs'] - elapsed
if time < ret['time'] or ret['time'] == 0:
ret['time'] = time
Expand Down
25 changes: 21 additions & 4 deletions src/synack/plugins/scratchspace.py
Expand Up @@ -11,7 +11,7 @@
class Scratchspace(Plugin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for plugin in ['Db']:
for plugin in ['Api', 'Db']:
setattr(self,
plugin.lower(),
self.registry.get(plugin)(self.state))
Expand All @@ -25,22 +25,39 @@ def build_filepath(self, filename, target=None, codename=None):
f = f / codename
f.mkdir(parents=True, exist_ok=True)
f = f / filename
return f'{f}.txt'
return f

def set_burp_file(self, content, target=None, codename=None):
if target or codename:
if type(content) == dict:
content = json.dumps(content)
dest_file = self.build_filepath('hosts', target=target, codename=codename)
dest_file = self.build_filepath('burp.txt', target=target, codename=codename)
with open(dest_file, 'w') as fp:
fp.write(content)
return dest_file

def set_download_attachments(self, attachments, target=None, codename=None, prompt_overwrite=True, overwrite=True):
downloads = list()
for attachment in attachments:
overwrite_current = overwrite
if target or codename:
dest_file = self.build_filepath(attachment.get('filename'), target=target, codename=codename)
if prompt_overwrite and dest_file.exists():
ans = input(f'{attachment.get("filename")} exists. Overwrite? [y/N]: ')
overwrite_current = ans.lower().startswith('y')
if overwrite_current or not dest_file.exists():
res = self.api.request('GET', attachment.get('url'))
if res.status_code == 200:
with open(dest_file, 'wb') as fp:
fp.write(res.content)
downloads.append(dest_file)
return downloads

def set_hosts_file(self, content, target=None, codename=None):
if target or codename:
if type(content) == list:
content = '\n'.join(content)
dest_file = self.build_filepath('hosts', target=target, codename=codename)
dest_file = self.build_filepath('hosts.txt', target=target, codename=codename)
with open(dest_file, 'w') as fp:
fp.write(content)
return dest_file
28 changes: 21 additions & 7 deletions src/synack/plugins/targets.py
Expand Up @@ -104,6 +104,18 @@ def get_assessments(self):
self.db.add_categories(res.json())
return self.db.categories

def get_attachments(self, target=None, **kwargs):
"""Get the attachments of a target."""
if target is None:
if len(kwargs) == 0:
kwargs = {'codename': self.get_connected().get('codename')}
target = self.db.find_targets(**kwargs)
if target:
target = target[0]
res = self.api.request('GET', f'targets/{target.slug}/resources')
if res.status_code == 200:
return res.json()

def get_connected(self):
"""Return information about the currenly selected target"""
res = self.api.request('GET', 'launchpoint')
Expand Down Expand Up @@ -166,7 +178,7 @@ def get_registered_summary(self):
ret[t['id']] = t
return ret

def get_scope(self, **kwargs):
def get_scope(self, add_to_db=False, **kwargs):
"""Get the scope of a target"""
if len(kwargs) > 0:
target = self.db.find_targets(**kwargs)
Expand All @@ -180,11 +192,11 @@ def get_scope(self, **kwargs):
for category in self.db.categories:
categories[category.id] = category.name
if categories[target.category].lower() == 'host':
return self.get_scope_host(target)
return self.get_scope_host(target, add_to_db=add_to_db)
elif categories[target.category].lower() in ['web application', 'mobile']:
return self.get_scope_web(target)
return self.get_scope_web(target, add_to_db=add_to_db)

def get_scope_host(self, target=None, **kwargs):
def get_scope_host(self, target=None, add_to_db=False, **kwargs):
"""Get the scope of a Host target"""
if target is None:
target = self.db.find_targets(**kwargs)
Expand All @@ -194,12 +206,13 @@ def get_scope_host(self, target=None, **kwargs):
res = self.api.request('GET', f'targets/{target.slug}/cidrs?page=all')
if res.status_code == 200:
scope = res.json()['cidrs']
self.db.add_ips(self.build_scope_host_db(target.slug, scope))
if add_to_db:
self.db.add_ips(self.build_scope_host_db(target.slug, scope))
if self.db.use_scratchspace:
self.scratchspace.set_hosts_file(scope, target=target)
return scope

def get_scope_web(self, target=None, **kwargs):
def get_scope_web(self, target=None, add_to_db=False, **kwargs):
"""Get the web scpope of a Web or Mobile target"""
if target is None:
target = self.db.find_targets(**kwargs)
Expand All @@ -212,7 +225,8 @@ def get_scope_web(self, target=None, **kwargs):
for asset in res.json():
if target.slug in [o['owner_uid'] for o in asset['owners']]:
scope.append(asset)
self.db.add_urls(self.build_scope_web_db(scope))
if add_to_db:
self.db.add_urls(self.build_scope_web_db(scope))
if self.db.use_scratchspace:
self.scratchspace.set_burp_file(self.build_scope_web_burp(scope), target=target)
return scope
Expand Down
17 changes: 14 additions & 3 deletions src/synack/plugins/templates.py
Expand Up @@ -12,7 +12,7 @@
class Templates(Plugin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for plugin in ['Db']:
for plugin in ['Alerts', 'Db', 'Targets']:
setattr(self,
plugin.lower(),
self.registry.get(plugin)(self.state))
Expand All @@ -31,9 +31,20 @@ def build_filepath(self, mission, generic_ok=False):
return str(generic)
return str(specific)

@staticmethod
def build_safe_name(name):
def build_replace_variables(self, text, target=None, **kwargs):
"""Replaces known variables within text"""
if target is None:
target = self.db.find_targets(**kwargs)
if target:
target = target[0]

if target:
text = text.replace("{{ TARGET_CODENAME }}", str(target.codename))
return text

def build_safe_name(self, name):
"""Simplify a name to use for a file path"""
name = self.alerts.sanitize(name)
name = name.lower()
name = re.sub('[^a-z0-9]', '_', name)
return re.sub('_+', '_', name)
Expand Down

0 comments on commit bee0e65

Please sign in to comment.