Introduction
Welcome to the documentation for the SynackAPI Python package!
This package has been created to give you a place to start interacting with the Synack API.
Disclaimer
I am not responsible if you do something that gets you banned!
It is possible for you to abuse the Synack API. Synack will know you have done this and you will be banned or otherwise repremanded.
The current (10FEB2022) recommendation is that you should abide by the following rules:
- DO NOT poll for missions more than 1x every 30 seconds
- DO NOT generate more than 200 requests in any 5 minute period
- DO NOT be the source of egregious and/or irresponsible traffic
Support
I have spent countless hours pouring over this project in order to best help you have an easy, reliable way to interact with the Synack API.
If this has helped you, please consider helping me out in one of the following ways.
Patreon
You can find my Patreon at the following link:
Contribution
If you'd like to see extra features be added to this package, please consider contributing code.
With that said, please take the following items into consideration:
- We will be using Test Driven Development with Unit Testing in order to ensure the stability of the SynackAPI package.
- Run
coverage run -m unittest discover test
from within the primary directory, then runcoverage report
before submitting a PR. Ensure that all tests are passing and the test coverage reports 100%.
- Run
- We will be conforming to pep8, which is the Python Style Guide.
- Run
flake8 src test live-tests
from within the primary directory before submitting a PR. Ensure there are no complaints returned.
- Run
- We will be trying to break up Functions by their purpose. For example, a function related to examining a mission would go in the Mission plugin.
There is also the ./check.sh
script in the primary directory that will run everything I would like you to check before submitting a PR.
If you have any questions on how you can contribute, please reach out via the SRT Slack.
Tests
Honestly, this section is not for most people. It explains how to run the tests to ensure that the code is working or to help in writing new functionality for SynackAPI.
Unit Tests
These tests are run by me repeatedly while developing the package, and every time that I push to the repository. They are then run again every time that I tell my GitHub workflow to go release a new version. If things fail, a new version is not released.
That said, you can run them using the checks.sh
script in the main folder.
Live Tests
These tests are run manually and sparingly. They ensure that Synack has not changed any of their API endpoints in ways we are not expecting. If these tests fail, it is very possible that I will need to go in and change some functionality of the SynackAPI so they continue to work as expected.
If you run these, they SHOULD be relatively quiet, but keep in mind that they also might not. In other words, make sure you have not been heavily using the Synack API when you run these.
These tests can be run via something like the following for a single test:
coverage run -m unittest live-tests.test_missions.MissionsTestCase
You can also run the following for all live tests:
coverage run -m unittest discover live-tests
Usage
The easiest way to get started with the SynackAPI is to install it via pip
.
For example:
pip3 install --upgrade SynackAPI
After doing this, you can use one of the Examples to understand the basic usage of the package.
I am not going to provide you with a ton of awesome scripts that will leverage the SynackAPI package. That is on you.
With that in mind, I would highly recommend you become familiar with the Plugins provided and apply your own ingenuity to come up with your own scripts.
Authentication
The first time you try to do anything which requires authentication, you will be automatically prompted for your credentials.
This prompt will expect the Synack Email
and Synack Password
, which are fairly self explanitory, but it also asks for the Synack OTP Secret
.
The Synack OTP Secret
is NOT the 8 digit code you pull out of Authy.
Instead, it is a string that you must extract from Authy via a method similar to the one found here.
Use the above instructions at your own discression. I TAKE NO RESPONSIBILITY IF SOMETHING BAD HAPPENS AS A RESULT.
Once you complete these steps, your credentials are stored in a SQLiteDB at ~/.config/synack/synackapi.db
.
Examples
These subpages here will provide a couple of very simple examples of how you can use the SynackAPI. These are going to help you get started, but are definitely not intended to be the best of the best. I highly encourage you to think about ways you could use the SynackAPI to apply your own logic and make something much better for yourself.
Check Invisible Missions
This was originally part of the main package, but it's pretty noisy, so I removed it so people wouldn't accidentally run it.
Sometimes a condition will arise in which the sidebar states there are missions, but there are not. These missions are associated with a specific target. If this happens, we can use this script to easily determine which target has the mission so you can submit a ticket.
This is going to pull a list of all targets you're registered to and cache it in the Database with h.targets.get_registered_summary()
.
It then iterates through them and asks for a mission count with a HEAD request for every single target individually (h.missions.get_count()
.
If a target reports it has missions, it then tries to pull a full list of its missions with h.missions.get()
.
If there are no missions, it adds the codename to a list, which is printed out at the end.
This will make several hundred requests (1 HEAD, 1 GET for EVERY Target). DO NOT use it unless you understand the implications and are trying to track down an invisible mission so Synack can fix it.
#!/usr/bin/env python3
import synack
import time
h = synack.Handler()
h.targets.get_registered_summary()
for t in h.db.targets:
time.sleep(1)
count = h.missions.get_count("PUBLISHED", t.slug)
if count > 0:
missions = h.missions.get("PUBLISHED", 1, 1, count, t.slug)
if len(missions) == 0:
print(t.codename)
Mission Bot
This is a simple example of how you could determine if there are missions every 30 seconds, try to claim some if there are.
We start by using import synack
to bring in the SynackAPI package.
We then use h = synack.Handler()
to create the primary object we will be using; the Handler.
All plugins are a part of the handler, which allows us to use h.missions.get_count()
to make a lightweight HEAD request to determine the number of missions. If there are currently more missions than we know about from our last check, we can assume that some missions were released. We can then use h.missions.get_available()
to get a list of available missions, and request them one at a time with h.missions.set_claimed(m)
. When the number of current missions goes back down to 0, we can assume there are no missions left, and we can reset our known missions to 0
.
#!/usr/bin/env python3
import time
import synack
h = synack.Handler(login=True)
print("I just logged in and if it was the first time I logged in, I successfully filled out my credentials!")
known_missions = 0
print("Since I just started looking, I don't know about any missions!")
while True:
print("I had better sleep for a while so that I don't blow up the API, get everyone mad at me, and get myself banned!")
time.sleep(30)
curr_missions = h.missions.get_count()
print(f"There are {curr_missions} missions")
if curr_missions and curr_missions > known_missions:
print("There are new missions I didn't know about!")
known_missions = curr_missions
missions = h.missions.get_available()
print(f"I grabbed a list of {len(missions)} missions!")
for m in missions:
print("I had better sleep for a while so that I don't blow up the API, get everyone mad at me, and get myself banned!")
time.sleep(1)
outcome = h.missions.set_claimed(m)
print(f"I tried to claim a mission. You can see the outcome here: {outcome}")
elif curr_missions == 0:
print("There are currently no missions, I'd better remember that!")
known_missions = 0
Mission Templates
I am going to start off this page with a technical explanation of how you can implement templates, then explain some template basics to help you think through how you may wish to build your template library.
Usage
This section will show you how to implement a very simple bot to take in templates stored on your disk and upload them to your claimed missions.
#!/usr/bin/env python3
import synack
def replace_placeholders(template, mission):
"""Replace placeholders in a misson template
Down below, I mention how you could drop credentials
into a template on the fly. This is a super simple
example of how that sort of thing could be done.
It would replace any instance of __CODENAME__ with
the actual codename of the target
"""
for k in template.keys():
template[k] = template[k].replace('__CODENAME__',
mission['listingCodename'])
h = synack.Handler()
msns = h.missions.get_claimed()
for m in msns:
template = h.missions.get_file(m)
if template:
template = replace_placeholders(template)
h.missions.set_evidences(m, template)
File Structure
It is important to understand the file structure of your mission templates.
The default location for templates is ~/Templates
, but this could be overridden by setting template_dir
in the State.
Each mission from the Synack API has a couple of attributes I use to build the file structure.
There is the taskType
(MISSION
, SV2M
, etc), the asset
/assetTypes
('host', 'web', 'ios', 'android', etc.), and the title
(Name of the mission).
Each of these attributes are run thorough templates.build_safe_name()
, which makes everything lowercase and removes special characters to make sure there are no issuer with the name of the file.
As an example, the following mission will turn into the following filepath:
>>> mission = {
... "taskType": "MISSION",
... "assetTypes": ["web"],
... "title": "SOMe Cr@Z33 Misson Name: The Sequel"
... }
>>> targets.build_filepath(mission)
'/home/jojo/Templates/mission/web/some_cr_z33_mission_name_the_sequel.txt'
Template Basics
As someone who is part of the Synack Auto Approve group, I attribute a lot of my success with missions to my templates. Hopefully this section will help you understand some of my main considerations when doing templates/missions.
This section ended up being quite a bit longer that I originally anticipated, but hopefully you find it useful.
Spend the time to make a good template/report
When you are making a template for the first time, make it very good. The more time you put in the first time, the less time you need to put into the report each additional time while still having a very solid report.
It is not uncommon for me to spend an hour or more the first time I do a mission regardless of whether it is a $15 or a $100 mission. It seems like a lot of time, but if it is done properly, it really helps you speed through any time you are able to get that mission in the future.
Also consider that your goal is to make the report good enough that VulnOps will not reject it AND that the customer is HAPPY with the outcome. It may be tempting to think about missions as a "quick $X", this is the absolute wrong way to think about them. Instead, you should consider it as you selling the idea that the client should purchase more missions from Synack and encourage their peers to do the same. While a crappy mission may BARELY squeeze by VulnOps, if it doesn't make the client happy, this means less missions for you in the future.
With that in mind, I strongly encourage you to set the goal on making sure the client will be happy, which will result in more missions for you in the future.
Spend a lot of time on the Introduction and Tools section
I personally find the Introduction and first part of the Testing Methodology sections to be the most important sections of the entire report when doing a mission for the first time. This may sound insane because the Details of how the mission was done and Summary seem like they would be the most important, so let me explain.
The Introduction section and beginning of the Testing Methodology section can be used to CLEARLY and CONSICELY explain why you are working right this second, what your goals are, and how you are going to make a determination on whether the client asset is vulnerable or not.
This is not only for the client, but I also find it incredibly useful for myself. When knocking out a large number of missions, it is easy to forget the details of what exactly you are doing on a specific mission. Having that information readily available lets me jump into the mission much quicker than if I had to think about it.
As an example of what this may look like, consider the following mission template snippets:
Introduction
This mission requests that we determine whether or not the target accepts default credentials as defined in the OWASP WSTG.
Some applications come with default credentials configured out of the box. These default credentials can be very easy to guess if the software is well known and/or they are trivial. To make matters worse, these default credentials often provide access to highly-privileged users, such as Administrators. As such, it is highly recommended that they be changed as soon as possible to prohibit attackers from easily being able to control your application.
Testing Methodology
# Plan
- Determine software used within this target
- Attempt Default Credentials, if found
- Attempt common credentials
# Tools / Resources
- Burp Suite Pro: Network proxy that allow for the interception, modification, and automated testing of network requests and endpoints
- Common Usernames: A list of very common usernames
- Common Passwords: A list of very common passwords
## Accounts
No provided credentials were used in this mission.
# Details.
...
With this information already compiled for you, you have something that looks professional and is ready to be submitted. It also instantly reminds you that you will be using Burp to try and determine the software used so you can try specific credentials, then falling back to a pre-decided list of usernames and passwords you can throw into Burp Intruder so you don't have to spend time looking for a good list.
Provide a spot for credentials
While in this specific template, Account credentials are not provided, if this were not the case, that section would look similar to the following.
Testing Methodology
## Accounts
Username:
Bob
Password:password1234
Auth URL: [https://login.company.com](https://login.company.com)
Because this is not going to be the same between missions, the details should not be filled in.
One way to do this would be to have the structure of this section in the template so it can be very easily filled in.
If you were feeling exceptionally fancy, it also wouldn't be insane to use the targets.get_credentials()
function to pull credentials, format them, then use something like the python re
package to inject the credentials into your templates before they are uploaded.
Be clear and detailed, but generic
Any time that I get a mission I do not have a template for, I will sit down and write the best report that I possibly can, while trying to keep it relatively generic.
As an example, I will write them similar to the following:
Yes
## Phase 1
I began my research by visiting the client website seen in 1.1.png. I then installed magic as can be seen in 1.2.png. As can be seen in 1.3.png, the installation was successful and I was given access to the mainframe.
No
## Phase 1
I began my research on SLEEPYPUPPY by visiting
http://www.supersleepy.dog/secret/cat-pics
as seen in cat-pics.png. I then installed magic as can be seen in magiccat.png. As can be ssen in puppymainframe.png, the installation was successful and I was given access to the SLEEPYPUPPY mainframe.
Both of these segments would be a good start to a mission and provide the client with the information they need in a clear and concise way, but when I need to do this same mission on CATNIPPEDKITTY, the second example means that I need to change a ton of information within the report before I can submit it. Having to
Consider where you will need to deviate from generic templates
Sometimes, it isn't possible to make a report both good AND generic. When these situations arise, consider how you will handle it while fixing up your templates.
As an example, consider a situation where you are testing SQL Injection and need to explain your work in text. Making a template like this would allow you to easily identify that you need to explain your work.
#/# Phase 2
... I was able to achieve a SQL Injection as seen in 2.3.png by changing __VARIABLE__ to __SQLI__.
__EXPLAIN_MORE_ABOUT_THE_SQLI__
When reviewing your mission, these will stick out, and you can identify that you need to change them.
Register Targets
One of the main goals for this project was to be able to create super simple oneliners that I could throw into various places like my i3blocks status bar.
To show you how this can be done, let's take a look at the following example, which will register all unregistered targets for you.
If you're unfamiliar with python3 -c
, this is how you can execute a small amount of Python from your (bash, zsh, etc.) shell.
We take that to import synack
just as we would in a full script.
We then have a longer command that creates the handler, then calls the targets.set_registered()
function to get and register all unregistered targets.
python3 -c "import synack; synack.Handler().targets.set_registered()"
This could be thrown into a cronjob (crontab -e
) as seen below to register any new targets once an hour:
0 * * * * python3 -c "import synack; synack.Handler(login=True).targets.set_registered()"
Do note that the login
State variable is set to True here.
This means that every time this function is called, it will confirm you are logged in and if not, it will log you in.
This is not default behavior because it makes between 1 and 5 requests each time Handler is initiated.
That said, you likely want to make sure you are having the SynackAPI log in often on one or two scripts.
Main Components
There are a couple components that are going to be used every single time you use the SynackAPI. Information about them can be found here.
- Handler: This component contains all the plugins and tracks the State
- State: This component contains all of the emphemeral settings and information within a single Handler
- Files: There are a couple files you should be aware of
Files
synackapi.db
This is a SQLite Database which exists at ~/.config/synack/synackapi.db
and stores your permanent settings and caches some data from the Synack API.
Information such as your email, password, and OTP Secret exist here. Keep it protected!
Some information is automatically cached so that the number of redundant requests you need. For example, if you pull a lot of Target information, there is a good chance that some of the basic information (slug, codename, etc.) will not change. Once it's pulled the first time, the SQLite Database will hold some of the information so that you can do things like converting Slugs to Codenames and vice versa without sending a request to Synack's API.
Another example would be the Synack Access Token, which is what authenticates you and is used to make requests. There is no reason to generate a new Access Token every time your Handler is initiated, so when you log in, it is stored in the database. When you make a new Handler, it will try to use this token. If it's valid, it will jump into the requested function, otherwise, it will complete the login workflow, then move onto the requested function.
login.js
This is a JavaScript file which exists at ~/.config/synack/login.js and aids in keeping you logged in. It is intended to be used with the following TamperMonkey script in order to do the following:
- See you are on
https://login.synack.com
- Wait 60 seconds
- Redirect you to
https://platform.synack.com
- Inject your current Access Token into Chrome
// ==UserScript==
// @name Synack
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Go to the platform automatically
// @author You
// @match https://*.synack.com/*
// @require file:///home/<homedir>/.config/synack/login.js
// @grant none
// ==/UserScript==
alert('Change the require line to point to the right place and delete this line. Also modify tampermonkey to be able to read local files. This is probably really dumb, so I will not tell you how to do this in an effort to make certain you have thought it through.');
Note: You must change the @require line to the location of the login.js
file.
Additionally, you will want to delete the alert()
.
Handler
The Handler is responsible for, you guessed it, handling the flow of data and functions within the SynackAPI package.
Being totally honest, this isn't strictly required, but it definitely makes things easier.
It is instantiated after import synack
as seen in the following example:
#!/usr/bin/env python3
import synack
h = synack.Handler()
From there, you can use any of the Plugins as follows:
h.missions.get_count()
h.targets.set_registered()
Setting One-Off States
It's important to note that you can easily change some of the State variables by passing them into the Handler.
For example, if you want to see all requests being made in the current script, you can enable debugging as following:
h = synack.Handler(debug=True)
h.targets.do_register_all()
State
The State object tracks several variables for a single instance of the Handler.
As seen in the following example, you can create a State variable externally and control it there. In the event that you do not pass in a State, one is created automatically. You can also continue to manipulate the State in the ways shown below.
#!/usr/bin/env python3
import synack
state = synack.State()
state.debug = True
handler = synack.Handler(state)
handler.debug.log("Test", "This message WILL be seen")
handler.state.debug = False
handler.debug.log("Test", "This message will NOT be seen")
state = True
handler.debug.log("Test", "This message WILL be seen")
# ----- OR -----
handler = synack.Handler(debug=True)
handler.debug.log("Test", "This message WILL be seen")
handler.state.debug = False
handler.debug.log("Test", "This message will NOT be seen")
handler.state.debug = True
handler.debug.log("Test", "This message WILL be seen")
Out of all of the State variables, login
is the most important to understand.
If this is set to true, whenever your Handler is created, it will make sure you are logged in.
If you have a bunch of scripts based on SynackAPI, it is probably a good idea to have login
set to False, which is the default, for the majority of scripts.
Then you can have a couple scripts which are run periodically with login
set to True so it becomes responsible for making sure that everything is logged in.
As an example of a good function to set login
to True, check out the Register Targets Example.
Database vs State
The observant will notice that there is a lot of overlap between the values stored in the Database and the State. This may cause you some confusion as to where data is coming from and how it's being stored and accessed.
The Database contains persistant or shared information. For example, the api_token, which should be shared by all Handlers or cached Target information that could be quickly referenced.
The State only persists within one instance of the Handler.
In the event that one of the State variables is set and is not constantly at risk of being changed (such as the api_token), the value stored in the State will be provided instead of the Database value. This is useful when you want to override Database variables in a single Handler. For example, you may wish to enable the debug
variable for a single Handler without affecting other Handlers you may have running.
Variables
Variable | Type | Description |
---|---|---|
api_token | str | This is the Synack Access Token used to authenticate requests |
config_dir | pathlib.Path | The location of the Database and Login script |
debug | bool | Used to show/hide debugging messages |
str | Your email address used to log into Synack | |
http_proxy | str | A Web Proxy (Burp, etc.) to intercept requests |
https_proxy | str | A Web Proxy (Burp, etc.) to intercept requests |
login | bool | Used to enable/disable a check of the api_token upon creation of the Handler |
notifications_token | str | Token used for authentication when dealing with Synack Notifications |
otp_secret | str | OTP Secret held by Authy. NOT an OTP. For more information, read the Usage page |
password | str | Your Synack Password |
session | requests.Session | Tracks cookies and headers across various functions |
template_dir | pathlib.Path | The location of your Mission Templates |
use_proxies | bool | Enables/Disables Web Proxy Usage |
user_id | bool | Your Synack user id used in many requests |
Plugins
Within the SynackAPI, various "Plugins" exist to allow us to easily separate functions based on their purpose.
Function Naming Convension
Similar to PowerShell, functions within this package will begin with a verb. This is a list of approved verbs and their meaning.
Verb | Description |
---|---|
add | Insert/Update item(s) in the Database |
build | Change data/items in some way |
find | Query the Database |
get | Retrieve Data from the Synack API or Local Files |
remove | Delete item(s) from the Database |
set | Push changes to the Synack API or Local Files |
Additionally, function names should not be redundant.
In other words, consider the function name missions.get_missions()
.
missions
is specified twice, when missions.get()
is just as clear.
missions.get()
is the optimal choice.
Alerts
The Alerts Plugin is used to send alerts to various external services.
The functions within this plugin don't follow the standard naming convention.
alerts.email(subject, message)
This function attempts to use SMTP to send an email. This function expects
h.db.smtp_x
attributes to have been set.
Arguments Type Description subject
str Email Subject message
str Email Message Examples
>>> h.db.smtp_server = 'smtp.email.com' >>> h.db.smtp_port = 465 >>> h.db.smtp_starttls = True >>> h.db.smtp_username = 'me@email.com' >>> h.db.smtp_password = 'password123' >>> h.db.smtp_email_to = 'you@email.com' >>> h.db.smtp_email_from = 'me@email.com' >>> 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
>>> 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. This function expects
h.db.slack_url
to be set.
Arguments Type Description message
str A message to send to Slack Examples
>>> h.db.slack_url = 'https://hooks.slack.com/services/x/y/z' >>> h.alerts.slack('Something important happened!')
Api
The Api Plugin is used to interact DIRECTLY with the Synack API. It is the last stop before our code is sent to the Synack API.
The functions within this plugin don't follow the standard naming convention.
api.login(method, path, **kwargs)
This function is used to set up requests sent to
https://login.synack.com/api/*
.
This function takes in several arguments to build information that is sent toapi.request()
.
Arguments Type Description method
str HTTP Method (GET, POST, etc.) path
str The full or partial URL to use with the Login API **kwargs
kwargs Passed through to api.request()
. Look there for more infoExamples
>>> headers = { ... "X-CSRF-Token": "123" ... } >>> data = { ... "email": "some@guy.com", ... "password": "password1234" ... } >>> h.api.login('POST', 'authenticate', headers=headers, data=data) <class 'requests.models.Response'>
api.notifications(method, path, **kwargs)
This function is used to set up requests sent to
https://notifications.synack.com/api/v2/*
.
This function takes in several arguments to build information that is sent toapi.request()
.
Arguments Type Description method
str HTTP Method (GET, POST, etc.) path
str The full or partial URL to use with the Login API **kwargs
kwargs Passed through to api.request()
. Look there for more infoExamples
>>> h.api.notifications('GET', 'notifications?meta=1') <class 'requests.models.Response'>
api.request(method, path, **kwargs)
This function is used to set up requests sent to the primary API at
https://platform.synack.com/api/*
.
Arguments Type Description method
str HTTP Method (GET, POST, etc.) path
str The full or partial URL to use with the Login API kwargs['headers']
dict Headers that should be applied to only the current request kwargs['query']
dict Query parameters that should be added onto the URL kwargs['data']
dict Data parameters that should be used in the Body Examples
>>> query = { ... "status": "PUBLISHED", ... "viewed": "false" ... } >>> h.api.request('HEAD', 'tasks/v1/tasks', query=query) <class 'requests.models.Response'>
Auth
This plugin deals with authenticating the user to Synack.
auth.build_otp()
Use your stored otp_secret to generate a current OTP code
Examples
>>> h.auth.build_otp() '1234567'
auth.get_api_token()
Walks through the whole authentication workflow to get a new api_token
Examples
>>> h.auth.get_api_token() '489hr98hf...eh59'
auth.get_login_csrf()
Pulls a CSRF Token from the Login page
Examples
>>> h.auth.get_login_csrf() '45h998h4g5...45wh89g9wh'
auth.get_login_grant_token(csrf, progress_token)
Get a Login Grant Token by providing an OTP Code
Argument Type Description csrf
str A CSRF Token used while logging in progress_token
str A token returned after submitting a valid username and password Examples
>>> csrf = h.auth.get_login_csrf() >>> lpt = h.auth.get_login_progress_token(csrf) >>> h.auth.get_login_grant_token(csrf, lpt) '58t7i...rh87g58'
auth.get_login_progress_token(csrf)
Get the Login Progress Token by authenticating with email and password
Argument Type Description csrf
str A CSRF Token used while logging in Examples
>>> csrf = h.auth.get_login_csrf() >>> h.auth.get_login_progress_token(csrf) '239rge7...8tehtyg'
auth.get_notifications_token()
Walks through the whole process of getting a notifications token
Examples
>>> h.auth.get_notifications_token() '958htiu...h98f5ht'
auth.set_login_script()
Writes the current api_token to
~/.config/synack/login.js
JavaScript file to help with staying logged in.Examples
>>> auth.set_login_script()
Db
This plugin deals with interacting with the database.
This plugin is a little different in most in that it has a couple functions which deal with database queries, in addition to many properties. Some properties are Read-Only, while others can be Set. Additionally, some properties can be overridden by the State, which allows you to change the way one Handler instance runs without affecting others.
Property | Read-Only | State Override | Description |
---|---|---|---|
api_token | No | No | Synack Access Token used to authenticate requests |
categories | Yes | No | All cached Categories |
debug | No | Yes | Changes the verbosity of some actions, such as network requests |
No | Yes | The email used to log into Synack | |
http_proxy | No | Yes | The http web proxy (Burp, etc.) to use for requests |
https_proxy | No | Yes | The https web proxy (Burp, etc.) to use for requests |
ips | Yes | No | All cached IPs |
notifications_token | No | No | Synack Notifications Token used to authenticate requests |
otp_secret | No | Yes | Synack OTP Secret |
password | No | Yes | The password used to log into Synack |
ports | Yes | No | All cached Ports |
proxies | Yes | Yes | A dict built from http_proxy and https_proxy |
scratchspace_dir | No | Yes | The path to a directory where your working files (scopes, scans, etc.) are stored |
slack_url | No | Yes | The Slack API URL used for Notifications |
smtp_email_from | No | Yes | Email Source for SMTP Notifications |
smtp_email_to | No | Yes | Email Destination for SMTP Notifications |
smtp_password | No | Yes | Password to use for SMTP Server Auth |
smtp_port | No | Yes | Port of SMTP Server (Ex: 465) |
smtp_server | No | Yes | URL of SMTP Server (Ex: smtp.gmail.com) |
smtp_starttls | No | Yes | Boolean to determine whether TLS is used for SMTP |
smtp_username | No | Yes | Username to use for SMTP Server Auth |
targets | Yes | No | All cached Targets |
template_dir | No | Yes | The path to a directory where your templates are stored |
use_proxies | No | Yes | Changes whether or not http_proxy and https_proxies are used |
user_id | No | No | Your Synack User ID used for requests |
db.add_categories(categories)
Add Target Categories from the Synack API to the Database This is most often used with the
targets.get_assessments()
function so that you are only returned information about Categories you have access to.
Argument Type Description categories
list A list of Category dictionaries returned from the Synack API Examples
>>> h.db.add_categories([{...}, {...}, {...}])
db.add_ips(results, session=None)
Add IP Addresses to the database
Argument Type Description results
list(dict) A list of dictionaries containing ip
addresses andtarget
slugssession
sqlalchemy.orm.sessionmaker() A database session. This function is often used with db.add_ports()
and can have a session passed into itExamples
>>> h.db.add_ips([{'ip': '1.1.1.1', 'target': '230h94ei'}, ...])
db.add_organizations(targets, session)
Add Organizations from the Synack API to the Database
Argument Type Description targets
list A list of Target dictionaries returned from the Synack API session
sqlalchemy.orm.sessionmaker() A database session. This function is most often used with db.add_targets()
and I was having issues getting it to work when it would create a new sessionExamples
>>> h.db.add_organizations([{...}, {...}, {...}])
db.add_ports(results)
Add port results to the database
Arguments Type Description results
list(dict) A list of dictionaries containing results from some scan, Hydra, etc. Examples
>>> results = [ ... { ... "ip": "1.1.1.1", ... "target": "7gh33tjf72", ... "source": "nmap", ... "ports": [ ... { ... "port": "443", ... "protocol": "tcp", ... "service": "Super Apache NGINX Deluxe", ... "screenshot_url": "http://127.0.0.1/h3298h23.png", ... "url": "http://bubba.net", ... "open": True, ... "updated": 1654969137 ... ... }, ... { ... "port": "53", ... "protocol": "udp", ... "service": "DNS" ... } ... ] ... } ... ] >>> h.db.add_ports(results)
db.add_targets(targets)
Adds Target from the Synack API to the Database
Argument Type Description targets list(dict) A list of Target dictionaties returned from the Synack API Examples
>>> h.db.add_targets([{...}, {...}, {...}])
db.add_urls(results)
Add urls results to the database
Arguments Type Description results
list(dict) A list of dictionaries containing results from some scan, Hydra, etc. Examples
>>> results = [ ... { ... "ip": "1.1.1.1", ... "target": "7gh33tjf72", ... "urls": [ ... { ... "url": "https://www.google.com", ... "screenshot_url": "https://imgur.com/2uregtu", ... }, ... { ... "url": "https://www.ebay.com", ... "screenshot_url": "file:///tmp/948grt.png", ... } ... ] ... } ... ] >>> h.db.add_urls(results)
db.find_ips(ip, **kwargs)
Filters through all the ips to return ones which match a given criteria
Argument Type Description ip
str IP Address to search for kwargs
kwargs Any attribute of the Target Database Model (codename, slug, is_active, etc.) Examples
>>> h.db.find_ips(codename="SLEEPYPUPPY") [{'ip': '1.1.1.1, 'target': '12398h21'}, ... ]
db.find_ports(port, protocol, source, ip, **kwargs)
Filters through all the ports to return ones which match a given criteria
Argument Type Description port
int Port number to search for (443, 80, 25, etc.) protocol
str Protocol to search for (tcp, udp, etc.) source
str Source to search for (hydra, nmap, etc.) ip
str IP Address to search for kwargs
kwargs Any attribute of the Target Database Model (codename, slug, is_active, etc.) Examples
>>> h.db.find_ports(codename="SLEEPYPUPPY") [ { 'ip': '1.2.3.4', 'source': 'hydra', 'target': '123hg912', 'ports': [ { 'open': True, 'port': '443', 'protocol': 'tcp', 'service': 'https - Wordpress', 'updated': 1654840021 }, ... ] }, ... ]
db.find_targets(**kwargs)
Filters through all the targets to return ones which match a given criteria
Argument Type Description kwargs
kwargs Any attribute of the Target Database Model (codename, slug, is_active, etc.) Examples
>>> h.db.find_targets(codename="SLEEPYPUPPY") [<class 'synack.db.models.Target'>, ...]
db.find_urls(url=None, ip=None, **kwargs)
Filters through all the ports to return ones which match a given criteria
Argument Type Description url
str Url hosting a service on the IP ip
str IP Address to search for kwargs
kwargs Any attribute of the Target Database Model (codename, slug, is_active, etc.) Examples
>>> h.db.find_ports(codename="SLEEPYPUPPY") [ { 'ip': '1.2.3.4', 'target': '123hg912', 'ports': [ { 'url': 'https://www.google.com', 'screenshot_url': 'file:///tmp/2948geybu24.png' }, ... ] }, ... ]
db.get_config(name)
Returns a configuration from the Database.
Argument Type Description name
str The desired config to pull. If none provided, the entire config object will return. Examples
>>> h.db.get_config('api_token') 'reuif...oetuhhj' >>> g.db.get_config('user_id') 'heutih9'
db.remove_targets(**kwargs)
Remove targets from the Database based on criteria. If no criteria is provided, all entries are deleted
Argument Type Description kwargs
kwargs Criteria by which to find Targets for deletion (codename, slug, etc.) Examples
>>> h.db.remove_targets(codename='SLUGISHPARROT')
db.set_config(name, value)
Permanently sets a configuration in the Database
Argument Type Description name
str Name of the config to set value
? Value to set the config to Examples
>>> h.db.set_config('email', '1@2.com') >>> h.db.set_config('password', 'password1234')
db.set_migration()
Migrates the local database to include the newest changes. This may need to be run manually when SynackAPI is updated until I can figure out a better way to have it run automatically.
Examples
>>> h.db.set_migration()
Debug
debug.log(title, message)
Prints a debug message to the screen if the State of Database is set to True
Arguments Description title
Title for the debug message. Appears in the top line in capital letters message
Message that appears on the second line Examples
>>> h.debug('Some Title', 'Some Message') 2022-02-12 09:32:20 -- SOME TITLE Some Message
Hydra
hydra.build_db_input()
Builds a list of ports ready to be ingested by the Database from Hydra output
Examples
>>> h.hydra.build_db_input(h.hydra.get_hydra(codename='SLEEPYPUPPY', update_db=False)) [ { 'ip': '1.2.3.4', 'source': 'hydra', 'target': '123hg912', 'ports': [ { 'open': True, 'port': '443', 'protocol': 'tcp', 'service': 'https - Wordpress', 'updated': 1654840021 }, ... ] }, ... ]
hydra.get_hydra(page, max_page, update_db, **kwargs)
Returns information from Synack Hydra Service
Arguments Type Description page
int Page of the Hydra Service to start on (Default: 1) max_page
int Highest page that should be queried (Default: 5) update_db
bool Store the results in the database Examples
>>> h.hydra.get_hydra(codename='SLEEPYPUPPY') [{'host_plugins': {}, 'ip': '1.2.3.4', 'last_changed_dt': '2022-01-01T01:02:03Z', ... }, ... ] >>> h.hydra.get_hydra(codename='SLEEPYPUPPY', page=3, max_page=5, update_db=False) [{'host_plugins': {}, 'ip': '3.4.5.6', 'last_changed_dt': '2022-01-01T01:02:03Z', ... }, ... ]
Missions
missions.build_order(missions, sort)
Takes in a list of missions and returns them sorted in a particular way
Arguments Type Description missions
list(dict) A list of missions pulled from the SynackAPI sort
srt The way the missions should be sorted(Default: "payout-high")(Options: "payout-high", "payout-low", "shuffle", "reverse") Examples
>>> msns = [{"title": "Some mission",...}, {"title": "Another mission",...}] >>> h.missions.build_order(msns, "reverse") [{"title": "Another mission",...}, {"title": "Some mission",...}]
missions.build_summary(missions)
Takes a list of missions and summarizes them
Arguments Type Description missions
list(dict) A list of missions returned from the Synack API Examples
>>> h.missions.build_summary(h.missions.get_claimed()) {"count": 5, "value": 250, "time": 86158}
missions.get(status, max_pages, page, per_page, listing_uids)
Get a list of missions from the Synack API
Arguments Type Description status
str Status of missions to claim(Default: "PUBLISHED") max_pages
int The maximum number of pages to grab(Default: 1) page
int The page you wish to start on(Default: 1) per_page
int The number of missions you wish to return per page(Default: 20) listing_uids
str The slug of a specific Target to query for missions(Default: None) The default
per_page
on the Synack API is 20. I recommend leaving it there unless you also plan to try and get multiple pages of missions if there are more than 20. Don't set this number TOO high, or you may be requesting a lot of data, which may actually be detremental to the speed of your bot and the Synack API. Also consider that if there are multiple pages, you may just be better off still requesting 20 missions, but starting with the second page.In other words, when using this function, please consider what you are asking the computers to do.
Examples
>>> h.missions.get() [{"status": "PUBLISHED", "title": "Some Mission",...},...]
missions.get_approved()
Get one page (Up to 20 missions) from the missions you have previously had approved
Examples
>>> h.missions.get_approved() [{"status": "APPROVED", "title": "Some Mission",...},...]
missions.get_available()
Get one page (Up to 20 missions) from the missions currently available for claiming
Examples
>>> h.missions.get_available() [{"status": "PUBLISHED", "title": "Some Mission",...},...]
missions.get_claimed()
Get one page (Up to 20 missions) from the missions you currently have
Examples
>>> h.missions.get_claimed() [{"status": "CLAIMED", "title": "Some Mission",...},...]
missions.get_count(status, listing_uids)
Get the number of missions
Arguments Type Description status
str Status of missions to claim(Default: "PUBLISHED") listing_uids
str The slug of a specific Target to query(Default: None) Examples
>>> h.missions.get_count() 10
missions.get_evidences(mission)
Download the text part of a mission
Arguments Type Description mission
dict Mission dict pulled from the Synack API Examples
>>> msns = h.missions.get_approved() >>> h.missions.get_evidences(msns[0]) {"title": "Some Mission", "asset": "Web", "type": "MISSION", "structuredResponese": "no", "introduction": "This is the intro", "testing_methodology": "This is the testing methodology section", "conclusion": "This is the conclusion section"}
missions.get_in_review()
Get a list of missions (Up to 20) which are currently being reviewed
Examples
>>> h.missions.get_in_review() [{"status": "FOR_REVIEW", "title": "Some Mission",...},...]
missions.get_wallet_claimed()
Get the amount of missions counting against your Mission Wallet
Examples
>>> h.missions.get_wallet_claimed() 25
missions.get_wallet_limit()
Get the amount you are able to hold in your Missions Wallet
Examples
>>> h.missions.get_wallet_limit() 100
missions.set_claimed(mission)
Try and claim one mission
Arguments Type Description mission
dict A single mission dict returned from the Synack API Examples
>>> msns = h.missions.get_available() >>> h.missions.set_claimed(msns[0]) {'target': 'jwfplgu', 'title': 'Some Mission', 'payout': 50, 'status': 'CLAIMED', 'success': True}
missions.set_disclaimed(mission)
Release one mission you currently have
Arguments Type Description mission
dict A single mission dict returned from the Synack API Examples
>>> msns = h.missions.get_claimed() >>> h.missions.set_disclaimed(msns[0]) {'target': 'jwfplgu', 'title': 'Some Mission', 'payout': 50, 'status': 'DISCLAIMED', 'success': True}
missions.set_evidences(mission)
Set the evidences (text body) of a mission.
Note that there are protections in place to ensure you don't accidentally nuke a report you are writing. These protections currently include confirming that the current fields of the mission have 20 or less characters. If you have a lot of text in a mission and wish to replace it with a template, replace all of the text with a single character and try again.
Also note that the templates are pulled from your local file templates. Check out the Mission Templates page for more information.
Arguments Type Description mission
dict A single mission dict returned from the Synack API Examples
>>> msns = h.missions.get_claimed() >>> h.missions.set_evidences(msns[0]) {'evidenceId': 'uuid4...', 'title': 'Some Mission', 'codename': 'SLEEPYPUPPY'}
missions.set_status(mission, status)
Sets the status of a mission. Used in
mission.set_claimed
andmissions.set_disclaimed
.
Arguments Type Description mission
dict A single mission dict returned from the SynackAPI status
str Status to set it to (i.e., CLAIM
,DISCLAIM
)Examples
>>> msns = h.missions.get_available() >>> h.missions.set_status(msns[0], 'CLAIM') {'target': 'jwfplgu', 'title': 'Some Mission', 'payout': 50, 'status': 'CLAIMED', 'success': True} >>> h.missions.set_status(msns[0], 'DISCLAIM') {'target': 'jwfplgu', 'title': 'Some Mission', 'payout': 50, 'status': 'DISCLAIMED', 'success': True}
Notifications
notifications.get()
Return a list of notifications
Examples
>>> h.notifications.get() [{"action": "outage_starts", "subject": "SLAPHAPPYMONKEY",...}...]
notifications.get_unread_count()
Get the number of unread notifications.
Examples
>>> h.notifications.get_unread_count() 7
Scratchspace
scratchspace.build_filepath(filename, target=None, codename=None)
This function return the desired Scratchspace file name based on
db.scratchspace_dir
and a Target's Codename.
Arguments Type Description filename
str Desired name of the destination file target
db.models.Target A Target Database Object codename
str Codename of a Target Examples
>>> h.scratchspace.buildfilepath('test', codename='ADAMANTANT') '/tmp/Scratchspace/ADAMANTANT/test.txt'
scratchspace.set_assets_file(content, target=None, codename=None)
This function will save a
assets.txt
scope file within acodename
folder in within theself.db.scratchspace_dir
folder Ifself.db.use_scratchspace
isTrue
, this function is automatically run when you dotargets.get_scope()
ortargets.get_scope_web()
Arguments Type Description content
str,list(str) Either a preformatted string or (more likely) the return of targets.get_scope()
target
db.models.Target A Target Database Object codename
str Codename of a Target Examples
>>> scope = h.targets.get_scope_web(codename='ADAMANTANT') >>> h.scratchspace.set_assets_file(scope, codename='ADAMANTANT') '/tmp/Scratchspace/ADAMANTANT/assets.txt'
scratchspace.set_burp_file(content, target=None, codename=None)
This function will save a
burp.txt
scope file within acodename
folder in within theself.db.scratchspace_dir
folder Ifself.db.use_scratchspace
isTrue
, this function is automatically run when you dotargets.get_scope()
ortargets.get_scope_web()
Arguments Type Description content
str,list(str) Either a preformatted string or (more likely) the return of targets.get_scope()
target
db.models.Target A Target Database Object codename
str Codename of a Target Examples
>>> scope = h.targets.get_scope_web(codename='ADAMANTANT') >>> h.scratchspace.set_burp_file(scope, codename='ADAMANTANT') '/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 thecodename
folder wthin theself.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
>>> 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'), ...]
>>> 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 acodename
folder in within theself.db.scratchspace_dir
folder. Ifself.db.use_scratchspace
isTrue
, this function is automatically run when you dotargets.get_scope()
ortargets.get_scope_host()
Arguments Type Description content
str,list(str) Either a preformatted string or (more likely) the return of targets.get_scope_host()
target
db.models.Target A Target Database Object codename
str Codename of a Target Examples
>>> scope = h.targets.get_scope_host(codename='ADAMANTARDVARK') >>> h.scratchspace.set_hosts_file(scope, codename='ADAMANTARDVARK') '/tmp/Scratchspace/ADAMANTARDVARK/hosts.txt'
Targets
targets.build_codename_from_slug(slug)
Returns a Target's codename given its slug.
This hits the local database, then hits the Synack API if it can't find it.
Arguments Type Description slug
str The slug of a Target Examples
>>> h.targets.build_codename_from_slug('uwfpmfpgjlum') 'DAPPERDINGO'
targets.build_scope_host_db(slug, scope)
Prints a list of IPs ready to ingest into the Database
Arguments Type Description slug
str The slug of a Target scope
list(dict) Return of targets.get_scope_host()
from Synack's APIExamples
>>> scope = h.targets.get_scope(codename='JANGLYJALOPY') >>> scope_db = h.targets.build_scope_host_db(scope) >>> scope_db [ {'ip': '1.1.1.1', 'target': '2398her8h'}, ... ] >>> h.db.add_ips(scope_db)
targets.build_scope_web_burp(scope)
Prints a dictionary compatible with Burp Suite from the output of
targets.get_scope_web()
Arguments Type Description scope
list(dict) Return of targets.get_scope_web()
from Synack's APIExamples
>>> scope = h.targets.get_scope(codename='SLAPPYMONKEY') >>> h.targets.build_scope_web_burp(scope) {'target': {'scope': { 'advanced_mode': 'true', 'exclude': [{'enabled': True, 'scheme': 'https', 'host': 'bad.monkey.com', 'file': '/'}, ...] 'include': [{'enabled': True, 'scheme': 'https', 'host': 'good.monkey.com', 'file': '/'}, ...] }}}
targets.build_scope_web_db(scope)
Prints a list of URLs which can be ingested into the Database
Arguments Type Description scope
list(dict) Return of targets.get_scope_web()
from Synack's APIExamples
>>> scope = h.targets.get_scope(codename='SLAPPYMONKEY') >>> scope_db = h.targets.build_scope_web_db(scope) >>> scope_db [ { 'target': '94hw8ier', 'urls': [{'url': 'https://good.monkey.com'}] }, ... ] >>> h.db.add_urls(scope_db)
targets.build_scope_web_urls(scope)
Prints a dictionary containing lists of
in
scope andout
of scope URLs
Arguments Type Description scope
list(dict) Return of targets.get_scope_web()
from Synack's APIExamples
>>> scope = h.targets.get_scope(codename'SLAPPYMONKEY') >>> h.targets.build_scope_web_urls(scope) {'in': ['good.monkey.com'], 'out': ['bad.monkey.com']}
targets.build_slug_from_codename(codename)
Returns a Target's slug given its codename.
Arguments Type Description codename
str The codename of a Target Examples
>>> h.targets.build_slug_from_codename('DAPPERDINGO') 'uwfpmfpgjlum'
targets.get_assessments()
Pull back a list of assessments and whether you have passed them.
This function caches your status in the Database and will affect certain queries like
targets.get_unregistered()
, which will only pull back targets in categories you have passed.Examples
>>> h.targets.get_assessments() [{"id": 1, ...},...]
targets.get_assets(self, target=None, asset_type=None, host_type=None, active='true', scope=['in', 'discovered'], sort='location', sort_dir='asc', page=None, organization_uid=None, **kwargs)
Pull back a list of assets related to a target.
If no arguments are provided, whatever target you are currently connected to will be queried with the default paramters. You can use the following arguments to specify a target/organization or override default parameters.
Note that
scopeRules
andlistings
both have ascope
field, which is confusing. From the best I can tell, the one inlistings
is the one that matters. PLEASE double check my math before trusting it blindly and let me know if I'm wrong.
Arguments Type Description target
db.models.Target A single Target returned from the database asset_type
str Type of target ('host', 'webapp', etc.) host_type
str Type of information to get back ('cidr') active
str This field appears to specify whether the asset is an active item in the target's scope scope
str I'm honestly not entirely sure what this field is, but the default is ['in', 'discovered'] when made officially. sort_dir
str SQL-type sort direction ( asc
,desc
)page
int The page of assets to return organization_uid
str slug of the organization that owns the target Examples
>>> h.targets.get_assets() [{ 'listings': [{'listingUid': 'iuqwehuh4', 'scope': 'in'}, ...], 'location': 'https://www.something.com (https://www.something.com)', 'scopeRules': [{'appliesTo': 'both', 'rule': '*.www.something.com/*', 'uid': 'qiuwe'}, ...], ... }, ...]
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
>>> 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
Examples
>>> h.targets.get_connected() {"slug": "ulmpupflgm", "codename": "GOOFYGOPHER", "status": "Connected"}
targets.get_connections(target, **kwargs)
Get the connection details of a target
Argments 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
>>> h.targets.get_connections(codename='BLINKYBABOON') {"lifetime_connections":200,"current_connections":0}
targets.get_credentials(**kwargs)
Pulls back the credentials for a Target
Arguments Type Description kwargs
kwargs Some attribute that will be used to identify a Target Examples
>>> h.targets.get_credentials(slug='ljufpbgylu') [{"credentials": [{...},...],...}] >>> h.targets.get_credentials(codename='CHILLINCHILLA') [{"credentials": [{...},...],...}]
targets.get_query(status='registered', query_changes={})
Pulls back a list of targets matching the specified query
Arguments Type Description status
string The type of targets to pull back. (Ex: registered
,unregistered
,upcoming
,all
)query_changes
dict() Changes to make to the standard query. (Ex: {"sorting['field']": "dateUploaded"}
Examples
>>> h.targets.get_query(status='unregistered') [{"codename": "SLEEPYSLUG", ...}, ...]
targets.get_registered_summary()
The Registered Summary is a short list of information about every target you have registered. The endpoint used by this function is hit every time you refresh a page on the platform, so eventhough it sounds like a lot, it isn't bad.
Information from this function is cached in the Database
Examples
>>> h.targets.get_unregistered_summary() {"pflupm": {"id": "pflupm",...},...}
targets.get_scope(**kwargs)
Returns scope information for web or host targets when given target identifiers. If no kwargs are provided, the scope of your currently connected target will be retrieved.
Arguments Type Description kwargs
kwargs Information used to look up a Target in the database (ex: codename
,slug
, etc.)Examples
>>> h.targets.get_scope(codename='SILLYFILLY') ['1.1.1.1/32', '10.0.0.0/8', ...]
targets.get_scope_host(target, **kwargs)
Return CIDR IP Addresses in scope when given a Target or target identifiers
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
>>> tgt = h.db.find_targets(codename='SILLYFILLY') >>> h.targets.get_scope_host(tgt) ['1.1.1.1/32', '10.0.0.0/8', ...] >>> h.targets.get_scope_host(slug='92wg38itur') ['9,9,9,9/32', ...]
targets.get_scope_web(target, **kwargs)
Returns a ton of information about a web target's scope given a Target or target identifiers
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
>>> h.targets.get_scope_web(codename='SLAPPYFROG') [{ 'raw_url': 'https://good.frog.com', 'status': 'in', 'bandwidth': 0, 'notes': '', 'owners': [{'owner_uid': '97g8ehri', 'owner_type_id': 1, 'codename': 'slappyfrog'}, ...] }, ...]
targets.get_submissions(target, status="accepted", **kwargs)
Get the details of previously submitted vulnerabilities from the analytics of a target
Argments Type Description target
db.models.Target A single Target returned from the database status
str Query either accepted
,rejected
orin_queue
vulnerabilitieskwargs
kwargs Information used to look up a Target in the database (ex: codename
,slug
, etc.)Examples
>>> h.targets.get_submissions(codename='BLINKYBABOON') [ { "categories": ["Authorization/Permissions","SSRF"], "exploitable_locations":[ {"type":"url","value":"https://example.com/index.html","created_at":1625646235,"status":"fixed"}, ... ] }, ... ] >>> >>> h.targets.get_submissions(status="in_queue", codename='BLINKYBABOON') [ { "categories": ["Authorization/Permissions","SSRF"], "exploitable_locations":[ {"type":"url","value":"https://example.com/login.html","created_at":1625646235,"status":"pending"}, ... ] }, ... ]
targets.get_submissions_summary(target, hours_ago=None, **kwargs)
Get a summary of the submission analytics of a target
Argments Type Description target
db.models.Target A single Target returned from the database hours_ago
int The amount of hours since the current time to query the analytics for. (ex: hours_ago=48
will query how many submissions were made in the last48
hours. Defaults to lifetime when not set.)kwargs
kwargs Information used to look up a Target in the database (ex: codename
,slug
, etc.)Examples
>>> h.targets.get_submissions_summary(codename='BLINKYBABOON') 35 >>> >>> h.targets.get_submissions_summary(hours_ago=48, codename='BLINKYBABOON') 5
targets.get_unregistered()
Gets a list of unregistered Targets from the Synack API.
Only Targets in Categories you have passed will be returned.
Examples
>>> h.targets.get_unregistered() [{"slug": "lfjpgmk",...},...]
targets.get_upcoming()
Gets a list of upcoming Targets from the Synack API.
Examples
>>> h.targets.get_upcoming() [{'codename': 'SLEEPYSLUG', 'upcoming_start_date': 1668430800, ...}, ...]
targets.set_connected(target, **kwargs)
Connect to a specified target
Argments 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
h.targets.set_connected(codename='BLINKYBABOON') >>> {'slug': '12083y9', 'codename': 'BLINKYBABOON', 'status': 'Connected'} h.targets.set_connected(slug='12083y9') >>> {'slug': '12083y9', 'codename': 'BLINKYBABOON', 'status': 'Connected'}
targets.set_registered(targets)
Registers unregistered Targets.
If no
targets
are provided,targets.get_unregistered()
is used so that all unregistered targets are registered.
Arguments Type Description targets
list(dict) A list of targets returned from the Synack API Examples
>>> msns = h.targets.get_unregistered() >>> h.targets.set_unregistered([msns[0]]) [{"id": "jlgbmjpbgm",...}] >>> >>> h.targets.set_unregistered() [{"id": "pwjlgmf",...},...]
Templates
templates.build_filepath(mission, generic_ok=False)
Builds a safe filepath for the template to exist at.
Arguments Type Description mission
dict A mission dict returned from the Synack API generic_ok
bool Return the default mission template if the specified template doesn't exist (Default: False) Examples
>>> msn = { ... "taskType": "SV2M", ... "assetTypes": ["host"], ... "title": "More Realistic Name: CVE-1970-1" ... } >>> h.templates.build_filepath(msn) '/home/user/Templates/sv2m/host/more_realistic_name_cve_1970_1.txt' >>> msn = { ... "taskType": "MISSION", ... "assetTypes": ["web"], ... "title": "SoME HoRR!bl3 M!$$!0N" ... } >>> h.templates.build_filepath(msn) '/home/user/Templates/mission/web/some_horr_bl3_m_0n.txt' >>> msn["title"] = "Mission Without A Template" >>> h.templates.build_filepath(msn, generic_ok=True) '/home/user/Templates/mission/web/generic.txt' >>> h.templates.build_filepath(msn, generic_ok=False) '/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
>>> 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
Arguments Type Description name
str Some string to convert into a standardized string that if definitely safe to use as a filepath Examples
>>> h.build_safe_name('R@ND0M G@RB@G3!!!!!') 'r_nd0m_g_rb_ge_'
templates.build_sections(path)
Take the text from a local template file and prepare it to be sent to the Synack API
Arguments Description path
pathlib.PosixPath Examples
>>> h.templates.build_sections(Path('/home/user/Templates/mission/web/mission.txt')) { "introduction": "This is the intro", "testing_methodology": "This is how I tested", "conclusion": "This is the conclusion", "structuredResponse": "no" }
templates.get_file(mission)
Pulls in a local template file to upload to a given mission
Arguments Type Description mission
dict A mission dict from the Synack API Examples
>>> msns = h.missions.get_claimed() >>> h.templates.get_file(msns[0]) {"introduction": "This is the intro",...}
templates.set_file(evidences)
Writes evidences pulled from
missions.get_evidences()
to a local template fileNote that if the file already exists, it will not be overwritten
Arguments Type Description evidences
dict Evidences from missions.get_evidences()
Examples
>>> msns = h.missions.get_approved() >>> evidences = h.missions.get_evidences(msns[0]) >>> h.templates.set_file(evidences) '/home/user/Templates/mission/web/some_new_mission.txt'
Transactions
transactions.get_balance()
Returns information about your current account balance and pending payouts.
Examples
>>> h.transactions.get_balance() {"total_balance": "30.0", "pending_payout": "0.0"}
Users
users.get_profile(user_id)
DESCRIPTION
Arguments Type Description user_id
str The user id to pull back data for
(Default: "me")Examples
>>> h.user.get_profile() {"user_id": "qwerty", ...}