Sample API client
A sample API client written in Python, which can be used as a starting point for using the API.
#!/usr/bin/python
"""
Sample client for the v2 API of Pentest-Tools.com.
This client starts a Web Server Scan, queries the output and writes the report in a HTML and a PDF file.?
A valid API key is necessary for this program to work.
This client contains sample requests for most API methods
API Reference: https://pentest-tools.com/docs/api/v2
Python 3.9+ is assumed here
"""
import json
import sys
import time
import traceback
import urllib
import requests
API_KEY = "xxxxxxxxxxxxxxxxxxxxxx" # <-- Place your API key here
API_URL = "https://app.pentest-tools.com/api/v2"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
class Tool:
"""Map the tool_id that the API knows to the tool name"""
SUBDOMAIN_FINDER = 20
TCP_PORT_SCANNER = 70
UDP_PORT_SCANNER = 80
URL_FUZZER = 90
FIND_VHOSTS = 160
WEBSITE_SCANNER = 170
SHARE_POINT_SCANNER = 260
WORDPRESS_SCANNER = 270
DRUPAL_SCANNER = 280
WEBSITE_RECON = 310
NETWORK_SCANNER = 350
DOMAIN_FINDER = 390
PASSWORD_AUDITOR = 400
SSL_SCANNER = 450
SNIPER = 490
CLOUD_SCANNER = 520
"""Scans
Two common ways to start a scan is by using either `target` or `target_id`.
`target` needs to be a simple URL, like "https://example.org". `target_id` is an
integer you can get from the `get_targets` method.
For both, you need:
- tool_id: ID of the tool you want to use
- tool_params: Options for the tool
- target or target_id: The target you want to scan, depending on the chosen method
"""
def start_scan(target, tool_id, tool_params, api_url=API_URL, headers=HEADERS):
"""Start a scan using the given target name"""
data = {"tool_id": tool_id, "target_name": target, "tool_params": tool_params}
return requests.post(api_url + "/scans", headers=headers, json=data)
def start_scan_by_targetid(target_id, tool_id, tool_params, api_url=API_URL, headers=HEADERS):
"""Start a scan using the given target_id"""
data = {"tool_id": tool_id, "target_id": target_id, "tool_params": tool_params}
return requests.post(api_url + "/scans", headers=headers, json=data)
"""Interacting with scans
After you started a scan, through either method, you may want to interact with it. These are the most commonly
used methods for interacting with scans after they have been started.
A scan is identified by a `scan_id`, which can be obtained from running `GET $API/scans`,
or `get_scans` from this client.
You can check the status of a scan using the `get_scan_status` function.
You can get the JSON output of a scan by calling `get_output` with a suitable `scan_id`.
The previous feature of getting output in a chosen format has moved to the `start_scan` function, through an URL callback. TODO: example
You may want to stop a running scan, which you can do with `stop_scan`.
Should you want to delete a scan entirely, tou can use the `delete_scan` function.
"""
def get_scans(workspace_id=None, target_id=None, api_url=API_URL, headers=HEADERS):
"""Get a list of scans
Specific parameters:
- workspace_id -- when set, only the scans from this workspace will be returned
(you can get a list of workspaces by using the `get_workspaces` operation)
- target_id -- when set, only the scans run on this target will be returned
(use `get_targets` for the target list)
"""
data = {}
if workspace_id is not None:
data["workspace_id"] = workspace_id
if target_id is not None:
data["target_id"] = target_id
params = "?" + urllib.parse.urlencode(data)
return requests.get(api_url + f"/scans{params}", headers=headers)
def get_scan_status(scan_id, api_url=API_URL, headers=HEADERS):
"""Get the status of a scan"""
return requests.get(api_url + f"/scans/{scan_id}", headers=headers)
def get_output(scan_id, api_url=API_URL, headers=HEADERS):
"""Get the output of a scan"""
return requests.get(api_url + f"/scans/{scan_id}/output", headers=headers)
def stop_scan(scan_id, api_url=API_URL, headers=HEADERS):
"""Stop a running scan"""
return requests.post(api_url + f"/scans/{scan_id}/stop", headers=headers)
def delete_scan(scan_id, api_url=API_URL, headers=HEADERS):
"""Delete a scan"""
return requests.delete(api_url + f"/scans/{scan_id}", headers=headers)
"""Targets
Although you can interact with targets manually, by inputting the URL everytime, Pentest-Tools offers facilities of working with targets.
The simples workflow involves three functions: Add a target with `add_target`, get all targets with `get_targets` and get a single target with
`get_target_by_id`.
Deleting and updating targets remain, for now, an operation you can only do throught the site.
"""
def add_target(name, description="", workspace_id=None, api_url=API_URL, headers=HEADERS):
"""Add a new target
Specific parameters:
- name -- the name of the target (must be a hostname, IP address or URL)
- description -- a short description of the target (optional)
- workspace_id -- the specific workspace in which to add this target (optional)
"""
data = {"name": name}
if len(description) > 0:
data["description"] = description
if workspace_id is not None:
data["workspace_id"] = workspace_id
return requests.post(api_url + "/targets", headers=headers, json=data)
def get_targets(api_url=API_URL, headers=HEADERS):
"""Get a list of targets"""
return requests.get(api_url + "/targets", headers=headers)
def get_target_by_id(target_id, api_url=API_URL, headers=HEADERS):
"""Update the description of a target
Specific parameters:
- target_id -- the ID of the updated target
"""
return requests.get(api_url + f"/targets/{target_id}", headers=headers)
if __name__ == "__main__":
# `tool_params` is specific to the tool
# Here we do a light scan with the Web Server Scanner
tool_id = Tool.WEBSITE_SCANNER
tool_params = {"scan_type": "light"}
target = "http://demo.pentest-tools.com/webapp/"
# Start the scan
res = start_scan(target, tool_id, tool_params)
try:
res_json = res.json()
except requests.exceptions.JSONDecodeError:
print(traceback.format_exc())
print(res.text)
sys.exit(1)
# Get the new `scan_id`
if "data" in res_json and "created_id" in res_json["data"]:
scan_id = res_json["data"]["created_id"]
print("Started scan %i" % scan_id)
else:
print("Scan could not start")
print(f"Status: {res_json['status']}, message: {res_json['message']}")
sys.exit(1)
# Poll periodically to check if the scan is finished
while True:
time.sleep(2)
# Get the status of our scan
status = get_scan_status(scan_id)
status_name = status.json()["data"]["status_name"]
if status_name == "finished":
print("Scan status: %s" % res_json["data"])
# Get the HTML report and write it to a file
print("Getting JSON report")
res = get_output(scan_id)
output_json = res.json()
with open("report.json", "w") as file:
json.dump(output_json, file)
print("JSON report written to file")
break