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:

BytePen Studios Patreon

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 run coverage report before submitting a PR. Ensure that all tests are passing and the test coverage reports 100%.
  • 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.
  • 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

  1. Determine software used within this target
  2. Attempt Default Credentials, if found
  3. 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:

  1. See you are on https://login.synack.com
  2. Wait 60 seconds
  3. Redirect you to https://platform.synack.com
  4. 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

VariableTypeDescription
api_tokenstrThis is the Synack Access Token used to authenticate requests
config_dirpathlib.PathThe location of the Database and Login script
debugboolUsed to show/hide debugging messages
emailstrYour email address used to log into Synack
http_proxystrA Web Proxy (Burp, etc.) to intercept requests
https_proxystrA Web Proxy (Burp, etc.) to intercept requests
loginboolUsed to enable/disable a check of the api_token upon creation of the Handler
notifications_tokenstrToken used for authentication when dealing with Synack Notifications
otp_secretstrOTP Secret held by Authy. NOT an OTP. For more information, read the Usage page
passwordstrYour Synack Password
sessionrequests.SessionTracks cookies and headers across various functions
template_dirpathlib.PathThe location of your Mission Templates
use_proxiesboolEnables/Disables Web Proxy Usage
user_idboolYour 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.

VerbDescription
addInsert/Update item(s) in the Database
buildChange data/items in some way
findQuery the Database
getRetrieve Data from the Synack API or Local Files
removeDelete item(s) from the Database
setPush 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.

ArgumentsTypeDescription
subjectstrEmail Subject
messagestrEmail 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.

ArgumentsTypeDescription
messagestrA 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.

ArgumentsTypeDescription
messagestrA 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 to api.request().

ArgumentsTypeDescription
methodstrHTTP Method (GET, POST, etc.)
pathstrThe full or partial URL to use with the Login API
**kwargskwargsPassed through to api.request(). Look there for more info

Examples

>>> 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 to api.request().

ArgumentsTypeDescription
methodstrHTTP Method (GET, POST, etc.)
pathstrThe full or partial URL to use with the Login API
**kwargskwargsPassed through to api.request(). Look there for more info

Examples

>>> 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/*.

ArgumentsTypeDescription
methodstrHTTP Method (GET, POST, etc.)
pathstrThe full or partial URL to use with the Login API
kwargs['headers']dictHeaders that should be applied to only the current request
kwargs['query']dictQuery parameters that should be added onto the URL
kwargs['data']dictData 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

ArgumentTypeDescription
csrfstrA CSRF Token used while logging in
progress_tokenstrA 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

ArgumentTypeDescription
csrfstrA 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.

PropertyRead-OnlyState OverrideDescription
api_tokenNoNoSynack Access Token used to authenticate requests
categoriesYesNoAll cached Categories
debugNoYesChanges the verbosity of some actions, such as network requests
emailNoYesThe email used to log into Synack
http_proxyNoYesThe http web proxy (Burp, etc.) to use for requests
https_proxyNoYesThe https web proxy (Burp, etc.) to use for requests
ipsYesNoAll cached IPs
notifications_tokenNoNoSynack Notifications Token used to authenticate requests
otp_secretNoYesSynack OTP Secret
passwordNoYesThe password used to log into Synack
portsYesNoAll cached Ports
proxiesYesYesA dict built from http_proxy and https_proxy
scratchspace_dirNoYesThe path to a directory where your working files (scopes, scans, etc.) are stored
slack_urlNoYesThe Slack API URL used for Notifications
smtp_email_fromNoYesEmail Source for SMTP Notifications
smtp_email_toNoYesEmail Destination for SMTP Notifications
smtp_passwordNoYesPassword to use for SMTP Server Auth
smtp_portNoYesPort of SMTP Server (Ex: 465)
smtp_serverNoYesURL of SMTP Server (Ex: smtp.gmail.com)
smtp_starttlsNoYesBoolean to determine whether TLS is used for SMTP
smtp_usernameNoYesUsername to use for SMTP Server Auth
targetsYesNoAll cached Targets
template_dirNoYesThe path to a directory where your templates are stored
use_proxiesNoYesChanges whether or not http_proxy and https_proxies are used
user_idNoNoYour 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.

ArgumentTypeDescription
categorieslistA 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

ArgumentTypeDescription
resultslist(dict)A list of dictionaries containing ip addresses and target slugs
sessionsqlalchemy.orm.sessionmaker()A database session. This function is often used with db.add_ports() and can have a session passed into it

Examples

>>> 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

ArgumentTypeDescription
targetslistA list of Target dictionaries returned from the Synack API
sessionsqlalchemy.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 session

Examples

>>> h.db.add_organizations([{...}, {...}, {...}])

db.add_ports(results)

Add port results to the database

ArgumentsTypeDescription
resultslist(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

ArgumentTypeDescription
targetslist(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

ArgumentsTypeDescription
resultslist(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

ArgumentTypeDescription
ipstrIP Address to search for
kwargskwargsAny 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

ArgumentTypeDescription
portintPort number to search for (443, 80, 25, etc.)
protocolstrProtocol to search for (tcp, udp, etc.)
sourcestrSource to search for (hydra, nmap, etc.)
ipstrIP Address to search for
kwargskwargsAny 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

ArgumentTypeDescription
kwargskwargsAny 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

ArgumentTypeDescription
urlstrUrl hosting a service on the IP
ipstrIP Address to search for
kwargskwargsAny 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.

ArgumentTypeDescription
namestrThe 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

ArgumentTypeDescription
kwargskwargsCriteria 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

ArgumentTypeDescription
namestrName 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

ArgumentsDescription
titleTitle for the debug message. Appears in the top line in capital letters
messageMessage 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

ArgumentsTypeDescription
pageintPage of the Hydra Service to start on (Default: 1)
max_pageintHighest page that should be queried (Default: 5)
update_dbboolStore 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

ArgumentsTypeDescription
missionslist(dict)A list of missions pulled from the SynackAPI
sortsrtThe 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

ArgumentsTypeDescription
missionslist(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

ArgumentsTypeDescription
statusstrStatus of missions to claim
(Default: "PUBLISHED")
max_pagesintThe maximum number of pages to grab
(Default: 1)
pageintThe page you wish to start on
(Default: 1)
per_pageintThe number of missions you wish to return per page
(Default: 20)
listing_uidsstrThe 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

ArgumentsTypeDescription
statusstrStatus of missions to claim
(Default: "PUBLISHED")
listing_uidsstrThe 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

ArgumentsTypeDescription
missiondictMission 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

ArgumentsTypeDescription
missiondictA 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

ArgumentsTypeDescription
missiondictA 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.

ArgumentsTypeDescription
missiondictA 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 and missions.set_disclaimed.

ArgumentsTypeDescription
missiondictA single mission dict returned from the SynackAPI
statusstrStatus 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.

ArgumentsTypeDescription
filenamestrDesired name of the destination file
targetdb.models.TargetA Target Database Object
codenamestrCodename 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 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 targets.get_scope() or targets.get_scope_web()

ArgumentsTypeDescription
contentstr,list(str)Either a preformatted string or (more likely) the return of targets.get_scope()
targetdb.models.TargetA Target Database Object
codenamestrCodename 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 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 targets.get_scope() or targets.get_scope_web()

ArgumentsTypeDescription
contentstr,list(str)Either a preformatted string or (more likely) the return of targets.get_scope()
targetdb.models.TargetA Target Database Object
codenamestrCodename 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 the codename folder wthin the self.db.scratchspace_dir folder.

ArgumentsTypeDescription
attachmentslist(dict)A list of attachments from h.targets.get_attachments()
targetdb.models.TargetA Target Database Object
codenamestrCodename of a Target
prompt_overwriteboolBoolean 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 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 targets.get_scope() or targets.get_scope_host()

ArgumentsTypeDescription
contentstr,list(str)Either a preformatted string or (more likely) the return of targets.get_scope_host()
targetdb.models.TargetA Target Database Object
codenamestrCodename 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.

ArgumentsTypeDescription
slugstrThe 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

ArgumentsTypeDescription
slugstrThe slug of a Target
scopelist(dict)Return of targets.get_scope_host() from Synack's API

Examples

>>> 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()

ArgumentsTypeDescription
scopelist(dict)Return of targets.get_scope_web() from Synack's API

Examples

>>> 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

ArgumentsTypeDescription
scopelist(dict)Return of targets.get_scope_web() from Synack's API

Examples

>>> 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 and out of scope URLs

ArgumentsTypeDescription
scopelist(dict)Return of targets.get_scope_web() from Synack's API

Examples

>>> 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.

ArgumentsTypeDescription
codenamestrThe 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 and listings both have a scope field, which is confusing. From the best I can tell, the one in listings is the one that matters. PLEASE double check my math before trusting it blindly and let me know if I'm wrong.

ArgumentsTypeDescription
targetdb.models.TargetA single Target returned from the database
asset_typestrType of target ('host', 'webapp', etc.)
host_typestrType of information to get back ('cidr')
activestrThis field appears to specify whether the asset is an active item in the target's scope
scopestrI'm honestly not entirely sure what this field is, but the default is ['in', 'discovered'] when made officially.
sort_dirstrSQL-type sort direction (asc, desc)
pageintThe page of assets to return
organization_uidstrslug 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.

ArgumentsTypeDescription
targetdb.models.TargetA single Target returned from the database
kwargskwargsInformation 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

ArgmentsTypeDescription
targetdb.models.TargetA single Target returned from the database
kwargskwargsInformation 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

ArgumentsTypeDescription
kwargskwargsSome 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

ArgumentsTypeDescription
statusstringThe type of targets to pull back. (Ex: registered, unregistered, upcoming, all)
query_changesdict()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.

ArgumentsTypeDescription
kwargskwargsInformation 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

ArgumentsTypeDescription
targetdb.models.TargetA single Target returned from the database
kwargskwargsInformation 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

ArgumentsTypeDescription
targetdb.models.TargetA single Target returned from the database
kwargskwargsInformation 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

ArgmentsTypeDescription
targetdb.models.TargetA single Target returned from the database
statusstrQuery either accepted, rejected or in_queue vulnerabilities
kwargskwargsInformation 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

ArgmentsTypeDescription
targetdb.models.TargetA single Target returned from the database
hours_agointThe 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 last 48 hours. Defaults to lifetime when not set.)
kwargskwargsInformation 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

ArgmentsTypeDescription
targetdb.models.TargetA single Target returned from the database
kwargskwargsInformation 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.

ArgumentsTypeDescription
targetslist(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.

ArgumentsTypeDescription
missiondictA mission dict returned from the Synack API
generic_okboolReturn 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

ArgumentsTypeDescription
textstrString to replace variables within
targetdb.models.TargetTarget to use for variables
kwargskwargsKey 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

ArgumentsTypeDescription
namestrSome 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

ArgumentsDescription
pathpathlib.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

ArgumentsTypeDescription
missiondictA 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 file

Note that if the file already exists, it will not be overwritten

ArgumentsTypeDescription
evidencesdictEvidences 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

ArgumentsTypeDescription
user_idstrThe user id to pull back data for
(Default: "me")

Examples

>>> h.user.get_profile()
{"user_id": "qwerty", ...}