This extension adds a new tab Turbo Miner to Burp Suite's UI as well as a new entry Turbo Data Miner to Burp Suite's Extensions context menu. In the new tab, you are able to write new or select existing Python scripts that are executed on each request/response item currently stored in the Proxy History, Side Map, or on each request/response item that is sent or received by Burp Suite.
2 |
3 |
The objective of these Python scripts is the flexible and dynamic extraction, correlation, and structured presentation of information from the Burp Suite state as well as the flexible and dynamic on-the-fly modification of outgoing or incoming HTTP requests. Thus, Turbo Data Miner shall aid in gaining a better and faster understanding of the data collected and processed by Burp Suite.
4 |
5 |
Please refer to the GitHub repo for usage instructions
6 |
--------------------------------------------------------------------------------
/turbodataminer/scripts/89f968ba-6eb0-404a-be2c-aa2aa745b6ef.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 3
5 | ],
6 | "uuid": "89f968ba-6eb0-404a-be2c-aa2aa745b6ef",
7 | "version": "v1.0",
8 | "script": "\"\"\"\nThis script performs on-the-fly modifications on outgoing in-scope HTTP requests.\nUse this template script to perform modifications on HTTP request bodies or headers\nby modifying the content of variables headers or body_content.\n\"\"\"\nif in_scope and is_request:\n\trequest = message_info.getRequest()\n\trequest_info = helpers.analyzeRequest(request)\n\theaders = request_info.getHeaders()\n\tbody_offset = request_info.getBodyOffset()\n\tbody_bytes = request[body_offset:]\n\tbody_content = helpers.bytesToString(body_bytes)\n\t\n\t# insert code here\n\t\n\trequest = helpers.buildHttpMessage(headers, body_content)\n\tmessage_info.setRequest(request)",
9 | "name": "Template to Perform On-The-Fly Modifications On In-Scope Request Bodies/Headers"
10 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/e0f0e59d-1cd7-46c2-a314-62ee6237a5b6.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 3
5 | ],
6 | "uuid": "e0f0e59d-1cd7-46c2-a314-62ee6237a5b6",
7 | "version": "v1.0",
8 | "script": "\"\"\"\nThis script performs on-the-fly modifications on incoming in-scope HTTP responses.\nUse this template script to perform modifications on HTTP response bodies or headers\nby modifying the content of variables headers or body_content.\n\"\"\"\nif in_scope and not is_request:\n\tresponse = message_info.getResponse()\n\tresponse_info = helpers.analyzeResponse(response)\n\theaders = response_info.getHeaders()\n\tbody_offset = response_info.getBodyOffset()\n\tbody_bytes = response[body_offset:]\n\tbody_content = helpers.bytesToString(body_bytes)\n\t\n\t# insert code here\n\t\n\tresponse = helpers.buildHttpMessage(headers, body_content)\n\tmessage_info.setResponse(response)",
9 | "name": "Template to Perform On-The-Fly Modifications On In-Scope Response Bodies/Headers"
10 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/f0cfa6f0-70d7-4d82-91c2-24bbf7efd61d.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "uuid": "f0cfa6f0-70d7-4d82-91c2-24bbf7efd61d",
9 | "version": "v1.0",
10 | "script": "\"\"\"\nThis script extracts all cookie information from in-scope HTTP requests and adds\r\nthe information to the table above.\n\"\"\"\r\n\n# Do the initial setup\nif ref == 1:\n\theader = [\"Ref.\", \"Host\", \"URL\", \"Cookie Name\", \"Cookie Value\", \"JavaScript Set Cookie\"]\n\n# Process only in-scope HTTP requests\nif in_scope:\n\t_, cookies = get_header(request_info.getHeaders(), \"Cookie\")\n\tif cookies:\n\t\tfor cookie in cookies.split(\";\"):\n\t\t\tcookie = cookie.strip()\n\t\t\ttmp = cookie.split(\"=\")\n\t\t\tcookie_name = tmp[0]\n\t\t\tcookie_value = \"=\".join(tmp[1:])\n\t\t\trows.append([ref, get_hostname(url), url.getPath(), cookie_name, cookie_value, 'document.cookie=\"{}\"'.format(cookie)])",
11 | "name": "Cookie - Template Script to Extract Cookie Information From HTTP Requests"
12 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/e0c874c5-9736-4213-b8dc-8bb21f1e0d48.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "uuid": "e0c874c5-9736-4213-b8dc-8bb21f1e0d48",
9 | "version": "v1.1",
10 | "script": "\"\"\"\nThis script extracts all cookie information from in-scope HTTP responses and adds\r\nthe information to the table above.\n\"\"\"\r\n\n# Do the initial setup\nif ref == 1 or \"attributes\" not in session:\n\tsession[\"attributes\"] = get_cookie_attributes()\n\theader = [\"Ref.\", \"Host\", \"URL\"]\n\theader.extend(session[\"attributes\"])\n\n# Process only in-scope HTTP responses\nresponse = message_info.getResponse()\nif in_scope and response:\n\tresponse_info = helpers.analyzeResponse(response)\n\tcookies = get_cookies(response_info)\n\tfor cookie in cookies:\n\t\trow = [ref, get_hostname(url), url.getPath()]\n\t\tfor key in session[\"attributes\"]:\n\t\t\tvalue = cookie[key] if cookie[key] is not None else \"\"\n\t\t\trow.append(value)\n\t\trows.append(row)",
11 | "name": "Cookie - Template Script to Extract Cookie Information From HTTP Responses"
12 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/1a2e4592-73d4-4c7e-81fd-23b379a54570.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "burp_professional_only": false,
9 | "uuid": "1a2e4592-73d4-4c7e-81fd-23b379a54570",
10 | "version": "v1.0",
11 | "script": "\"\"\"\r\nThis script adds all IHttpRequestResponse items that are not in scope to the above table.\r\nThereby, the rows of the table are deduplicated.\r\n\r\nYou can use this script to perform a review on the current scope configuration. Furthermore,\r\nyou can use the above table's context menu, entry \"Add Selected Host(s) To Scope\" to\r\ninclude certain IHttpRequestResponse items to Burp Suite's scope. \r\n\"\"\"\r\n\r\n# Do the initial setup\r\nif ref == 1 or \"dedup\" not in session:\n\theader = [\"Ref.\", \"Host\"]\n\tsession[\"dedup\"] = {}\n\r\n# Process only out-of-scope HTTP requests\nif not in_scope:\n\thost = get_hostname(url)\n\tif host not in session[\"dedup\"]:\n\t\trows = [[ref, host]]\n\t\tsession[\"dedup\"][host] = None",
12 | "name": "Scope - Template Script To Obtain List of All Out-Of-Scope Hostnames"
13 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/21c8632e-2213-4450-8198-9f3bd657771f.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "burp_professional_only": false,
9 | "uuid": "21c8632e-2213-4450-8198-9f3bd657771f",
10 | "version": "v1.1",
11 | "script": "\"\"\"\nThis script scans all in-scope HTTP responses for known software versions and adds\nidentified software information to the table above.\n\nNote that the extraction is based on the following regular expressions:\nhttps://vulners.com/api/v3/burp/rules\n\"\"\"\n\n# Do the initial setup\nif ref == 1 or \"attributes\" not in session:\n\tsession[\"attributes\"] = [\"software\", \"version\", \"type\", \"alias\", \"cpe\"]\n\theader = [\"Ref.\", \"Host\", \"Path\"]\n\theader.extend(session[\"attributes\"])\n\n# Process only in-scope HTTP responses\nresponse = message_info.getResponse()\nif in_scope and response:\n\tresponse_string = helpers.bytesToString(response).encode(\"utf-8\")\n\tresults = find_versions(response_string)\n\tfor item in results:\n\t\trow=[]\r\n\t\tif has_stopped():\r\n\t\t\tbreak\n\t\tfor attribute in session[\"attributes\"]:\n\t\t\trow.append(item[attribute] if item[attribute] else \"\")\n\t\ttmp = [ref, get_hostname(url), url.getPath()]\n\t\ttmp.extend(row)\n\t\trows.append(tmp)",
12 | "name": "Misc - Template Script to Scan Project for Known Software Versions"
13 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/1d52128b-9770-4179-a3f1-6af16c032d3e.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "burp_professional_only": false,
9 | "uuid": "1d52128b-9770-4179-a3f1-6af16c032d3e",
10 | "version": "v1.1",
11 | "script": "\"\"\"\nThis script identifies all out-of-scope HTTP requests that contain in-scope URLs \nin their Referer header and adds them to the table above.\n\"\"\"\nimport re\nfrom java.net import URL\n\n# Do the initial setup\nif ref == 1 or \"dedup\" not in session:\n\theader = [\"Ref.\", \"HTTPS\", \"URL\", \"Referer Header\"]\n\tsession[\"dedup\"] = {}\n\n# Process only out-of-scope HTTP requests and responses\nif not in_scope:\n\trequest_info = analyze_request(message_info)\n\tresult = get_header(request_info.getHeaders(), \"Referer\")\n\treferer = result[1]\n\tif referer:\n\t\treferer = URL(referer)\n\t\thost_name = get_hostname(url)\n\t\tdedup = unicode(host_name) + unicode(referer)\n\t\t# If referer header host is in scope, then add it to the table above\n\t\tif referer.getHost() != url.getHost() and callbacks.isInScope(referer) and dedup not in session[\"dedup\"]:\n\t\t\trows = [[ref, url.getProtocol().lower()==\"https\", host_name, referer]]\n\t\t\tsession[\"dedup\"][dedup] = True",
12 | "name": "Referer - Template Script to Determine Whether Information is Disclosed via HTTP Referer Header To Out-Of-Scope Page"
13 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/26d5dd1a-401e-4e37-a6c1-ae4fde640c3a.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "burp_professional_only": false,
9 | "uuid": "26d5dd1a-401e-4e37-a6c1-ae4fde640c3a",
10 | "version": "v1.1",
11 | "script": "\"\"\"\nThis script shall aid in the fast identification of parameters that are potentially prone to \nobject ID enumeration vulnerabilities by identifying those parameters that just transport \ninteger values and adding them to the table above.\n\"\"\"\nfrom burp import IParameter\n\n# Do the initial setup\nif ref == 1 or \"types\" not in session:\n\tsession[\"types\"] = [IParameter.PARAM_URL,\n\t\t\t\t\t\t\t\t\t\t\tIParameter.PARAM_BODY, \n\t\t\t\t\t\t\t\t\t\t\tIParameter.PARAM_COOKIE, \n\t\t\t\t\t\t\t\t\t\t\tIParameter.PARAM_XML, \n\t\t\t\t\t\t\t\t\t\t\tIParameter.PARAM_XML_ATTR, \n\t\t\t\t\t\t\t\t\t\t\tIParameter.PARAM_MULTIPART_ATTR, \n\t\t\t\t\t\t\t\t\t\t\tIParameter.PARAM_JSON]\n\theader = [\"Ref.\", \"Host\", \"URL\", \"Type\", \"Parameter\", \"Value\"]\n\n# Process only in-scope HTTP requests\nif in_scope:\n\trequest = message_info.getRequest()\n\trequest_info = helpers.analyzeRequest(request)\n\tfor parameter in request_info.getParameters():\n\t\tvalue = parameter.getValue()\n\t\ttype = parameter.getType()\r\n\t\tif has_stopped():\r\n\t\t\tbreak\n\t\telif value.isnumeric() and type in session[\"types\"]:\n\t\t\trows.append([ref, get_hostname(url), url.getPath(), get_parameter_name(type), parameter.getName(), value])",
12 | "name": "Parameter - Template Script to Identify Parameters With Potential Object ID Enumeration"
13 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/24768fe1-0bb5-4f99-ac79-f11b54dff146.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "burp_professional_only": false,
9 | "uuid": "24768fe1-0bb5-4f99-ac79-f11b54dff146",
10 | "version": "v1.1",
11 | "script": "\"\"\"\nThis script adds all issues of in-scope HTTP requests and responses to the table above.\n\"\"\"\n\n# Do the initial setup\nif ref == 1 or \"ScanIssues\" not in session:\n\theader = [\"Ref.\", \"Host\", \"URL\", \"Type\", \"Issue Name\", \"Severity\", \"Confidence\"]\n\tsession[\"ScanIssues\"] = {}\n\tsession[\"Severities\"] = {\"Information\": \"1 - Information\", \"Low\": \"2 - Low\", \"Medium\": \"3 - Medium\", \"High\": \"4 - High\"}\n\tfor issue in callbacks.getScanIssues(None):\n\t\turl_str = unicode(issue.getUrl())\n\t\tif callbacks.isInScope(issue.getUrl()):\n\t\t\tif url_str not in session[\"ScanIssues\"]:\n\t\t\t\tsession[\"ScanIssues\"][url_str] = [issue]\n\t\t\telse:\n\t\t\t\tsession[\"ScanIssues\"][url_str].append(issue)\n\n# Process only in-scope HTTP responses\nif in_scope:\n\turl_str = unicode(url)\n\tif url_str in session[\"ScanIssues\"]:\n\t\t\tissues = session[\"ScanIssues\"][url_str]\n\t\t\thost = get_hostname(url)\n\t\t\tfor issue in issues:\n\t\t\t\tseverity = session[\"Severities\"][issue.getSeverity()] if issue.getSeverity() in session[\"Severities\"] else \"\"\"Update session[\"Severities\"]\"\"\"\n\t\t\t\trows.append([ref, host, url_str, issue.getIssueType(), issue.getIssueName(), severity, issue.getConfidence()])",
12 | "name": "Issues - Template Script to Obtain Scan Issues For All In-Scope HTTP Requests And Responses"
13 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/b61fc205-cf03-48d7-820a-75f2cbf12530.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "burp_professional_only": false,
9 | "uuid": "b61fc205-cf03-48d7-820a-75f2cbf12530",
10 | "version": "v1.1",
11 | "script": "\"\"\"\r\nThis script displays information about redirects in the table above.\r\n\r\nUse this script to identify potential redirects that for example leak information.\r\n\"\"\"\r\nfrom burp import IParameter\r\n\r\n# Do the initial setup\r\nif ref ==1 or \"exclude_parameters\" not in session:\r\n\theader = [\"Ref.\", \"Host\", \"URL\", \"Params\", \"Location Params\", \"Status Code\", \"Content Length\", \"Response Size\", \"Location\"]\r\n\tsession[\"exclude_parameters\"] = [IParameter.PARAM_COOKIE]\r\n\r\n# Process only in-scope HTTP responses\r\nresponse = message_info.getResponse()\r\nif in_scope and response:\r\n\tresponse_info = helpers.analyzeResponse(response)\r\n\tstatus_code = response_info.getStatusCode()\r\n\tparameters = [item for item in request_info.getParameters() if item.getType() not in session[\"exclude_parameters\"]]\r\n\tcontent_length = get_content_length(response_info.getHeaders())\r\n\t_, location = get_header(response_info.getHeaders(), \"Location\")\r\n\tif location:\r\n\t\tlocation_params = []\r\n\t\tfor parameter in parameters:\r\n\t\t\traw_paramter = helpers.urlDecode(parameter.getValue())\r\n\t\t\traw_location = helpers.urlDecode(location)\r\n\t\t\tif has_stopped():\r\n\t\t\t\tbreak\r\n\t\t\telif raw_paramter in raw_location:\r\n\t\t\t\tlocation_params.append(parameter.getName())\r\n\t\trows.append([ref, get_hostname(url), url.getPath(), len(parameters) > 0, \", \".join(location_params), status_code, content_length if content_length else -1, len(response), location])",
12 | "name": "Redirect - Template Script to Analyze Redirects"
13 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/aa0b7080-f27c-4ed4-a6a2-7802c6656a64.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "burp_professional_only": false,
9 | "uuid": "aa0b7080-f27c-4ed4-a6a2-7802c6656a64",
10 | "version": "v1.0",
11 | "script": "\"\"\"\nThis script tries to convert HTTP response bodies into JSON objects and afterwards tries to extract the \nvalues of the JSON attributes that are specified in variable session[\"attributes\"] (see Line 16). Note\r\nthat the specified JSON attributes have to exist within the same hierarchy level in order to be added\r\nto the table above.\n\nIn addition, variable session[\"matches\"] (see Line 17) specifies how many of the specified attributes\r\nmust exist within the same hierarchy level in order to be added to the table. This mechanism functions\r\nas a customizable threshold to minimize false positives.\n\"\"\"\nimport traceback\n\n# Do the initial setup\n# Update the contents of session[\"attributes\"] and session[\"matches\"] accordingly\nif ref == 1 or \"attributes\" not in session or \"matches\" not in session:\n\tsession[\"attributes\"] = [\"id\", \"username\"] # TODO: Update accordingly\n\tsession[\"matches\"] = 2 #len(session[\"attributes\"]) # TODO: Update accordingly\n\theader = [\"Ref.\", \"Host\", \"URL\"]\n\theader.extend(session[\"attributes\"])\n\n# Process only in-scope HTTP responses\nresponse = message_info.getResponse()\nif in_scope and response:\n\tresponse_info = helpers.analyzeResponse(response)\n\tbody_offset = response_info.getBodyOffset()\n\tbody_bytes = response[body_offset:]\n\tbody_string = helpers.bytesToString(body_bytes)\n\ttry:\n\t\trvalues = get_json_attributes(body_string, session[\"attributes\"], session[\"matches\"])\n\t\tfor item in rvalues:\r\n\t\t\tvalues = [ref, get_hostname(url), url.getPath()]\r\n\t\t\tif has_stopped():\r\n\t\t\t\tbreak\n\t\t\tfor key in session[\"attributes\"]:\n\t\t\t\tvalues.append(item[key])\n\t\t\trows.append(values)\n\texcept:\n\t\ttraceback.print_exc(file=callbacks.getStderr())",
12 | "name": "JSON - Template Script to Extract JSON Attribute Values From Responses"
13 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/cd9f9437-da1d-41b9-9660-4e3575268f62.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 4
5 | ],
6 | "uuid": "cd9f9437-da1d-41b9-9660-4e3575268f62",
7 | "version": "v1.1",
8 | "script": "\"\"\"\nThis script\na) silently drops HTTP requests to URLs that are specified in list session[\"filter\"] (see Lines 13+) or\nb) silently forwards (without proxy interception) HTTP requests to hosts that are specified in list\r\nsession[\"ignore\"] (see Lines 23+) and which were issued by Burp Suite Proxy tool\r\n(IBurpExtenderCallbacks.TOOL_PROXY).\n\"\"\"\nimport re\nfrom burp import IInterceptedProxyMessage\nfrom burp import IBurpExtenderCallbacks\r\n\n# Do the initial setup\nif ref == 1 or \"filter\" not in session or \"ignore\" not in session:\n\tsession[\"filter\"] = [re.compile(\"^https?://(.+\\.)?mozilla\\.((com)|(org)|(net))(:\\d+)?.*$\", re.IGNORECASE),\n\t\t\t\t\t\t\t\t\t\t\tre.compile(\"^https?://(.+\\.)?firefox\\.((com)|(org)|(net))(:\\d+)?.*$\", re.IGNORECASE),\n\t\t\t\t\t\t\t\t\t\t\tre.compile(\"^https?://(.+\\.)?google\\.((com)|(org)|(net))(:\\d+)?.*$\", re.IGNORECASE),\n\t\t\t\t\t\t\t\t\t\t\tre.compile(\"^https?://(.+\\.)?google-analytics\\.((com)|(org)|(net))(:\\d+)?.*$\", re.IGNORECASE),\n\t\t\t\t\t\t\t\t\t\t\tre.compile(\"^https?://(.+\\.)?yahoo\\.((com)|(org)|(net))(:\\d+)?.*$\", re.IGNORECASE),\n\t\t\t\t\t\t\t\t\t\t\tre.compile(\"^https?://(.+\\.)?goo\\.gl(:\\d+)?.*$\", re.IGNORECASE),\n\t\t\t\t\t\t\t\t\t\t\tre.compile(\"^https?://(.+\\.)?googleapis\\.((com)|(org)|(net))(:\\d+)?.*$\", re.IGNORECASE),\n\t\t\t\t\t\t\t\t\t\t\tre.compile(\"^https?://(.+\\.)?gstatic\\.((com)|(org)|(net))(:\\d+)?.*$\", re.IGNORECASE),\n\t\t\t\t\t\t\t\t\t\t\tre.compile(\"^https?://ciscobinary\\.openh264\\.org(:\\d+)?.*$\", re.IGNORECASE),\n\t\t\t\t\t\t\t\t\t\t\tre.compile(\"^https?://ocsp\\.apple\\.com(:\\d+)?.*$\", re.IGNORECASE)]\n\tsession[\"ignore\"] = []\r\n\n# Process each HTTP request issued by the Burp Suite Proxy tool\nif is_request and plugin_id in [IBurpExtenderCallbacks.TOOL_PROXY]:\n\turl_str = unicode(url)\n\tfor filter in session[\"filter\"]:\n\t\tif filter.match(url_str):\n\t\t\tprint(\"Dropping: {}\".format(url_str))\n\t\t\tproxy_message_info.setInterceptAction(IInterceptedProxyMessage.ACTION_DROP)\n\tfor filter in session[\"ignore\"]:\n\t\tif filter.match(url_str):\n\t\t\tprint(\"Ignoring url: {}\". format(url_str))\n\t\t\tproxy_message_info.setInterceptAction(IInterceptedProxyMessage.ACTION_DONT_INTERCEPT)",
9 | "name": "Template Script to Silently Drop Unwanted Communication"
10 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | .idea
29 | MANIFEST
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .nox/
45 | .coverage
46 | .coverage.*
47 | .cache
48 | nosetests.xml
49 | coverage.xml
50 | *.cover
51 | *.py,cover
52 | .hypothesis/
53 | .pytest_cache/
54 |
55 | # Translations
56 | *.mo
57 | *.pot
58 |
59 | # Django stuff:
60 | *.log
61 | local_settings.py
62 | db.sqlite3
63 | db.sqlite3-journal
64 |
65 | # Flask stuff:
66 | instance/
67 | .webassets-cache
68 |
69 | # Scrapy stuff:
70 | .scrapy
71 |
72 | # Sphinx documentation
73 | docs/_build/
74 |
75 | # PyBuilder
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | .python-version
87 |
88 | # pipenv
89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
92 | # install all needed dependencies.
93 | #Pipfile.lock
94 |
95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
96 | __pypackages__/
97 |
98 | # Celery stuff
99 | celerybeat-schedule
100 | celerybeat.pid
101 |
102 | # SageMath parsed files
103 | *.sage.py
104 |
105 | # Environments
106 | .env
107 | .venv
108 | env/
109 | venv/
110 | ENV/
111 | env.bak/
112 | venv.bak/
113 |
114 | # Spyder project settings
115 | .spyderproject
116 | .spyproject
117 |
118 | # Rope project settings
119 | .ropeproject
120 |
121 | # mkdocs documentation
122 | /site
123 |
124 | # mypy
125 | .mypy_cache/
126 | .dmypy.json
127 | dmypy.json
128 |
129 | # Pyre type checker
130 | .pyre/
131 |
132 | .DS_Store
133 |
134 | BappModules/
135 |
--------------------------------------------------------------------------------
/turbodataminer/scripts/3fe1e364-1c65-48e9-8696-cc89e2d09d56.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 5
5 | ],
6 | "burp_professional_only": false,
7 | "uuid": "3fe1e364-1c65-48e9-8696-cc89e2d09d56",
8 | "version": "v1.0",
9 | "script": "def is_enabled(content, is_request, session):\r\n\t\"\"\"\r\n\tThis method is invoked before an HTTP message is displayed in an custom editor tab, so that this custom \r\n\ttab can indicate whether it should be enabled for that message.\r\n\r\n\tFor more information, refer to the Burp Suite API, IMessageEditorTab interface, method isEnabled.\r\n\t:param content (List[bytes]): The message that is about to be displayed by this custom editor tab, or a \r\n\tzero-length array if the existing message is to be cleared.\r\n\t:param is_request (bool): Indicates whether the message is a request or a response.\r\n\t:param session (dict): The dictionary allows storing information accross method calls.\r\n\t:return (bool) If the custom tab is able to handle the specified message, and so will be displayed within the \r\n\teditor. Otherwise, the tab will be hidden while this message is displayed.\r\n\t\"\"\"\r\n\tresult = True\r\n\t# todo: implement code\r\n\treturn result\r\n\r\ndef set_message(content, is_request, session):\r\n\t\"\"\"\r\n\tThis method compiles the message to be displayed in this custom editor tab.\r\n\r\n\tFor more information, refer to the Burp Suite API, IMessageEditorTab interface, method set_message.\r\n\t:param content (List[bytes]): The original message based on which the new message, which is going to be \r\n\tdisplayed by this custom editor tab, is created.\r\n\t:param is_request (bool): Indicates whether the message is a request or a response.\r\n\t:param session (dict): The dictionary allows storing information accross method calls.\r\n\t:return (List[bytes]) Returns the modified content of variable content.\r\n\t\"\"\"\r\n\tresult = content\r\n\t# todo: decode content\r\n\treturn result\r\n\r\ndef get_message(content, session):\r\n\t\"\"\"\r\n\tThis method converts back the currently displayed message.\r\n\r\n\tFor more information, refer to the Burp Suite API, IMessageEditorTab interface, method set_message.\r\n\t:param content (List[bytes]): The original message based on which the new message, which is going to be \r\n\tdisplayed by this custom editor tab, is created.\r\n\t:param session (dict): The dictionary allows storing information accross method calls.\r\n\t:return (List[bytes]) Returns the modified content of variable content.\r\n\t\"\"\"\r\n\tresult = None\r\n\t# todo: encode contents\r\n\treturn result",
10 | "name": "Template Script to Implement a Custom Message Editor"
11 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/d8dbb2e9-1765-4319-baef-81ba36d3654b.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "burp_professional_only": false,
9 | "uuid": "d8dbb2e9-1765-4319-baef-81ba36d3654b",
10 | "version": "v1.2",
11 | "script": "\"\"\"\nThis script parses the HTTP response body for JSON objects and displays each leaf attribute together with its\nvalues in the table above. Thereby, the rows of the table are deduplicated.\n\nUse this script to identify the location of a specific value within the JSON object or to reduce the complexity of\nthe JSON object during a review.\n\"\"\"\nimport os\nimport json\nimport traceback\n\n# Do the initial setup\nif ref == 1 or \"dedup\" not in session:\n\theader = [\"Ref.\", \"Host\", \"URL\", \"Full Path\", \"Name\", \"Value\", \"Depth\"]\n\t# If you want to disable deduplication, remove the following line and press button \"Clear Session\" to \n\t# reset the content of the session variable\n\tsession[\"dedup\"] = {}\n\ndef get_items(content, url, ref, path=\"/\", depth=0):\n\t\"\"\"\n\tThis method recursively parses the given JSON object tag and returns the results in a two-dimensional list.\n\t\"\"\"\n\tresult = []\n\tif isinstance(content, dict):\n\t\tfor key, value in content.items():\r\n\t\t\tif has_stopped():\r\n\t\t\t\tbreak\n\t\t\tresult += get_items(value, url, ref, os.path.join(path, unicode(key)))\n\telif isinstance(content, list):\n\t\tfor item in content:\r\n\t\t\tif has_stopped():\r\n\t\t\t\tbreak\n\t\t\tresult += get_items(item, url, ref, path)\n\telse:\n\t\tunicode_path = unicode(path)\n\t\tpath_items = unicode_path.split(\"/\")\n\t\tresult = [[ref, get_hostname(url), url.getPath(), unicode_path, path_items[-1], unicode(content), len(path_items) - 1]]\n\treturn result\n\n# Process only in-scope HTTP responses\nresponse = message_info.getResponse()\nif in_scope and response:\n\tresponse_info = helpers.analyzeResponse(response)\n\tbody_offset = response_info.getBodyOffset()\n\tbody_bytes = response[body_offset:]\n\tbody_content = helpers.bytesToString(body_bytes)\n\t\n\ttry:\n\t\tjson_object = json.JSONDecoder().decode(body_content)\n\t\tresults = get_items(json_object, url, ref)\n # perform deduplication\n\t\tfor row in results:\n\t\t\tkey = \":\".join([unicode(item) for item in row[1:]])\r\n\t\t\tif has_stopped():\r\n\t\t\t\tbreak\n\t\t\telif key not in session[\"dedup\"]:\n\t\t\t\trows.append(row)\n\t\t\t\tsession[\"dedup\"][key] = None\n\t\telse:\n\t\t\trows = results\n\texcept:\n\t\ttraceback.print_exc(file=callbacks.getStderr())",
12 | "name": "JSON - Template Script to Display All Leaf JSON Attribute Values (Deduplicated) From Responses"
13 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/15302930-aa43-4d7a-88c5-434bc4b9f763.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "burp_professional_only": false,
9 | "uuid": "15302930-aa43-4d7a-88c5-434bc4b9f763",
10 | "version": "v1.3",
11 | "script": "\"\"\"\nThis script searches in-scope HTTP requests and responses for information based on regular expressions\nthat are stored in dictionary session[\"relist\"] (see Lines 13+) and if found, adds the identified value\r\nto the table above.\n\nNote that each regular expression must contain the named group \"value\" whose content will be extracted\nand added to the table.\n\"\"\"\nimport re\n\n# Do the initial setup\nif ref == 1 or \"relist\" not in session:\n\tsession[\"relist\"] = {\n\t\t\"guid\": re.compile(\"[^0-9a-zA-Z](?P[0-9a-zA-Z]{8,8}(-[0-9a-zA-Z]{4,4}){3,3}-[0-9a-zA-Z]{12,12})[^0-9a-zA-Z]\"),\n\t\t# \"meta-tag\": re.compile(\"(?P)\"),\r\n\t\t# \"external-resources\": re.compile(\"[\\\"'](?Phttps?://[^\\\"']+?\\.((css)|(js)))[\\\"']\", re.IGNORECASE)\n\t}\n\theader = [\"Ref.\", \"Host\", \"Path\", \"Extracted From\", \"Category\", \"Value\"]\n\ndef parse_content(content, regexes):\n\t\"\"\"\n\tThis method implements the core functionality to extract information\n\tfrom requests or responses based on the given regular expressions.\n\t\"\"\"\n\tresult = {}\n\tfor key, value in regexes.items():\n\t\tfor match in value.finditer(content):\r\n\t\t\tif has_stopped():\r\n\t\t\t\treturn result\n\t\t\telif key in result:\n\t\t\t\tresult[key].append(match.group(\"value\"))\n\t\t\telse:\n\t\t\t\tresult[key] = [match.group(\"value\")]\n\treturn result\n\n# Process only in-scope requests and responses\nif in_scope:\n\t# Search request content\n\trequest = message_info.getRequest()\n\trequest_string = helpers.bytesToString(request).encode(\"utf-8\")\n\trequest_list = parse_content(request_string, session[\"relist\"])\n\tfor key, values in request_list.items():\r\n\t\tif has_stopped():\r\n\t\t\tbreak\n\t\trows.extend([[ref, get_hostname(url), url.getPath(), \"Request\", key, item] for item in values if item != url.getHost()])\n\n\t# Search response content\n\tresponse = message_info.getResponse()\n\tif response:\n\t\tresponse_string = helpers.bytesToString(response).encode(\"utf-8\") if response else \"\"\n\t\tresponse_list = parse_content(response_string, session[\"relist\"])\n\t\tfor key, values in response_list.items():\r\n\t\t\tif has_stopped():\r\n\t\t\t\tbreak\n\t\t\trows.extend([[ref, get_hostname(url), url.getPath(), \"Response\", key, item] for item in values if item != url.getHost()])",
12 | "name": "Misc - Template Script to Extract Information From In-Scope Requests and Responses"
13 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/f6aa595e-b675-427f-b466-cf36149521d7.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 3
5 | ],
6 | "uuid": "f6aa595e-b675-427f-b466-cf36149521d7",
7 | "version": "v1.0",
8 | "script": "\"\"\"\r\nThis script adds the header X-Forwarded-For with different IPv4 addresses to each HTTP request.\r\n\r\nNote: If the X-Forward-For header already exists, then it is updated. In addition, with each new\r\nstart of this script, the script uses the starting IP address again.\r\n\"\"\"\r\nimport re\r\nfrom burp import IBurpExtenderCallbacks\r\n\r\n# Do the initial setup\r\nif ref == 1 or \"header\" not in session or \"original_address\" not in session or \"tools\" not in session or \"current_address\" not in session:\r\n\tsession[\"original_address\"] = \"10.0.0.1\"\r\n\tsession[\"header\"] = \"X-Forwarded-For\"\r\n\tsession[\"tools\"] = [IBurpExtenderCallbacks.TOOL_PROXY, IBurpExtenderCallbacks.TOOL_SCANNER, IBurpExtenderCallbacks.TOOL_INTRUDER, IBurpExtenderCallbacks.TOOL_REPEATER, IBurpExtenderCallbacks.TOOL_SEQUENCER]\r\n\tsession[\"current_address\"] = session[\"original_address\"]\r\n\tif not re.match(\"^\\d{1,3}(\\.\\d{1,3}){3,3}$\", session[\"original_address\"]):\r\n\t\traise ValueError(\"Given IP address is invalid.\")\r\n\r\ndef get_next_ip():\r\n\t\"\"\"\r\n\tThis function returns the next IPv4 address.\r\n\t\"\"\"\r\n\tglobal session\r\n\tresult = session[\"current_address\"]\r\n\tbyte1, byte2, byte3, byte4 = [int(item) for item in session[\"current_address\"].split(\".\")]\r\n\tbyte4 += 1\r\n\tif byte4 > 255:\r\n\t\tbyte4 = 1\r\n\t\tbyte3 += 1\r\n\tif byte3 > 255:\r\n\t\tbyte3 = 1\r\n\t\tbyte2 += 1\r\n\tif byte2 > 255:\r\n\t\tbyte2 = 1\r\n\t\tbyte1 += 1\r\n\tif byte1 > 255:\r\n\t\tbyte1 = int(session[\"original_address\"].split(\".\")[0])\r\n\tsession[\"current_address\"] = \"{}.{}.{}.{}\".format(byte1, byte2, byte3, byte4)\r\n\treturn result\r\n\r\n# Process only in-scope HTTP requests\r\nif in_scope and is_request and tool_flag in session[\"tools\"]:\r\n\theader = session[\"header\"]\r\n\trequest = message_info.getRequest()\r\n\trequest_info = helpers.analyzeRequest(request)\r\n\theaders = [item for item in request_info.getHeaders() if header != item]\r\n\tbody_offset = request_info.getBodyOffset()\r\n\tbody_bytes = request[body_offset:]\r\n\tbody_content = helpers.bytesToString(body_bytes)\r\n\r\n\t# Add X-Forward-For header\r\n\tip_address = get_next_ip()\r\n\theaders.append(\"{}: https://{}\".format(header, ip_address))\r\n\r\n\t# Build and update new request\r\n\trequest = helpers.buildHttpMessage(headers, body_content)\r\n\tmessage_info.setRequest(request)",
9 | "name": "Template Script to Add the Header X-Forwarded-For with Different IPv4 Addresses to Each HTTP Request"
10 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/fcfbfcaa-e902-4469-bc75-0c5634d4571a.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "burp_professional_only": false,
9 | "uuid": "fcfbfcaa-e902-4469-bc75-0c5634d4571a",
10 | "version": "v1.0",
11 | "script": "\"\"\"\nThis script searches all in-scope HTTP responses for JWTs. If a JWT is found, then it determines if one of the\nHTTP request parameters is located inside the JWT.\n\nUse this script to identify HTTP request parameters that might allow tampering the content of the JWT content.\n\"\"\"\nimport re\nimport os\nimport json\nimport traceback\n\n# Do the initial setup\nif ref == 1 or \"jwt_regex\" not in session or \"dedup\" not in session:\n\tsession[\"jwt_regex\"] = re.compile(\"(?PeyJ\\w+\\.eyJ\\w+\\.\\w+)\")\n\theader = [\"Ref.\", \"URL\", \"Type\", \"Name\", \"Value\", \"JWT\", \"Path to JWT attribute\"]\n\tsession[\"dedup\"] = {}\n\ndef get_jwts(content):\n\t\"\"\"\n\tThis method implements the core functionality to extract information from requests or responses based on\n\tthe given regular expressions.\n\t\"\"\"\n\tglobal session\n\tresult = []\n\tfor match in session[\"jwt_regex\"].finditer(content):\r\n\t\tif has_stopped():\r\n\t\t\tbreak\n\t\tresult.append(match.group(\"jwt\"))\n\treturn result\n\ndef search_json(content, search_string, path = \"/\"):\n\t\"\"\"\n\tThis method recursively searches the given JSON object for the given value.\n\t\"\"\"\n\tresult = []\n\tif isinstance(content, dict):\n\t\tfor key, value in content.items():\r\n\t\t\tif has_stopped():\r\n\t\t\t\tbreak\n\t\t\tresult += search_json(value, search_string, os.path.join(path, unicode(key)))\n\telif isinstance(content, list):\n\t\tfor item in content:\r\n\t\t\tif has_stopped():\r\n\t\t\t\tbreak\n\t\t\tresult += search_json(item, search_string, path)\n\telif search_string in content:\n\t\tresult.append(unicode(path))\n\treturn result\n\n# Process only in-scope HTTP requests and responses\nresponse = message_info.getResponse()\nif in_scope and response:\n\ttry:\n\t\tjwts = get_jwts(unicode(helpers.bytesToString(response)))\n\t\tfor jwt in jwts:\n\t\t\ttoken = decode_jwt(jwt)\n\t\t\tjwt_payload = token[1]\r\n\t\t\tif has_stopped():\r\n\t\t\t\tbreak\n\t\t\telif jwt_payload:\n\t\t\t\tjson_object = json.JSONDecoder().decode(jwt_payload)\n\t\t\t\tfor parameter in request_info.getParameters():\r\n\t\t\t\t\tif has_stopped():\r\n\t\t\t\t\t\tbreak\n\t\t\t\t\tfor path in search_json(json_object, parameter.getValue()):\r\n\t\t\t\t\t\tif has_stopped():\r\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\trows.append([ref, url, get_parameter_name(parameter.getType()), parameter.getName(), parameter.getValue(), jwt, path])\n\texcept:\n\t\ttraceback.print_exc(file=callbacks.getStderr())",
12 | "name": "JWT - Template Script to Identify Potential Request Parameters That Allow JWT Tampering"
13 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/12881ce6-2477-4b0c-a633-9d170f8b07ab.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "burp_professional_only": false,
9 | "uuid": "12881ce6-2477-4b0c-a633-9d170f8b07ab",
10 | "version": "v1.1",
11 | "script": "\"\"\"\r\nThis script searches all in-scope HTTP responses for JWTs using the regular expression specified by\r\nsession[\"jwt_regex\"] (see Line 15). If a JWT is found, then it decodes the payload and displays\r\neach leaf JSON attribute in the table above. Thereby, the rows of the table are deduplicated.\n\nUse this script to identify sensitive information in JWTs.\n\"\"\"\nimport re\nimport os\nimport json\nimport traceback\n\n# Do the initial setup\nif ref == 1 or \"jwt_regex\" not in session or \"dedup\" not in session:\n\tsession[\"jwt_regex\"] = re.compile(\"(?PeyJ\\w+\\.eyJ\\w+\\.\\w+)\")\n\theader = [\"Ref.\", \"Host\", \"URL\", \"Path\", \"Name\", \"Value\", \"Depth\"]\n\tsession[\"dedup\"] = {}\n\ndef get_jwts(content):\n\t\"\"\"\n\tThis method implements the core functionality to extract information from requests or responses based on\n\tthe given regular expressions.\n\t\"\"\"\n\tglobal session\n\tresult = []\n\tfor match in session[\"jwt_regex\"].finditer(content):\r\n\t\tif has_stopped():\r\n\t\t\tbreak\n\t\tresult.append(match.group(\"jwt\"))\n\treturn result\n\ndef get_items(content, path=\"/\"):\n\t\"\"\"\n\tThis method recursively parses the given JSON object tag and returns the results in a two-dimensional list.\n\t\"\"\"\n\tglobal url\n\tglobal ref\n\tresult = []\n\tif isinstance(content, dict):\n\t\tfor key, value in content.items():\n\t\t\tresult += get_items(value, os.path.join(path, unicode(key)))\n\telif isinstance(content, list):\n\t\tfor item in content:\n\t\t\tresult += get_items(item, path)\n\telse:\n\t\tunicode_path = unicode(path)\n\t\tpath_items = unicode_path.split(\"/\")\n\t\tresult = [[ref, get_hostname(url), url.getPath(), unicode_path, path_items[-1], unicode(content), len(path_items) - 1]]\n\treturn result\n\n# Process only in-scope HTTP requests and responses\nresponse = message_info.getResponse()\nif in_scope and response:\n\ttry:\n\t\tjwts = get_jwts(unicode(helpers.bytesToString(response)))\n\t\tfor jwt in jwts:\r\n\t\t\tif has_stopped():\r\n\t\t\t\tbreak\n\t\t\ttoken = decode_jwt(jwt)\n\t\t\tif token[1]:\n\t\t\t\tjson_object = json.JSONDecoder().decode(token[1])\n\t\t\t\tresults = get_items(json_object)\n\t\t\t\tif \"dedup\" in session:\n\t\t\t\t\tfor row in results:\n\t\t\t\t\t\tkey = \":\".join([unicode(item) for item in row[2:]])\n\t\t\t\t\t\tif key not in session[\"dedup\"]:\n\t\t\t\t\t\t\trows.append(row)\n\t\t\t\t\t\t\tsession[\"dedup\"][key] = None\n\texcept:\n\t\ttraceback.print_exc(file=callbacks.getStderr())",
12 | "name": "JWT - Template Script to Extract and Display All JWT Attributes from HTTP Responses (One JSON Attribute per Table Row)"
13 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/45516c08-5289-4c66-ae30-8e7c69319e0a.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "burp_professional_only": false,
9 | "uuid": "45516c08-5289-4c66-ae30-8e7c69319e0a",
10 | "version": "v1.1",
11 | "script": "\"\"\"\nThis script adds from/to relationships to the above table by analysing each IHttpRequestResponse item's Referer and Location\r\nheader. This information (especially first column of last row) can then be used to plot a relationship map using Python's\r\nmatplotlib and networkx libraries by executing the following steps:\n1. Run the Turbo Data Miner Script\n\n2. Install necessary Python libraries\n$ pip install igviz matplotlib networkx numpy\n\n3. Setup Python environment\n>>> import matplotlib.pyplot as plt\n>>> import networkx as nx\n>>> import igviz as ig\n>>> DG = nx.DiGraph()\n\n4. Click into the first column of the last row, right click, and select 'Copy Cell Value'\n\n5. Select the below PLACEHOLDER and replace it with the content of the clipboard\n>>> DG.add_weighted_edges_from([PLACEHOLDER])\n\n6a. Plot the directed graph using matplotlib\n>>> nx.draw(DG, with_labels=True, font_weight='bold')\n>>> plt.show()\n\n6b. Plot the directed graph using igviz\n>>> fig = ig.plot(DG)\n>>> fig.show()\n\"\"\"\nimport re\nfrom java.net import URL\n\n# Do the initial setup\nif ref == 1 or \"dedup\" not in session:\n\theader = [\"From\", \"To\"]\n\tsession[\"dedup\"] = {}\n\tsession[\"rows\"] = []\n\nrequest = message_info.getRequest()\nresponse = message_info.getResponse()\nif response:\n\thost_name = get_hostname(url)\n\trequest_info = helpers.analyzeRequest(request)\n\tresponse_info = helpers.analyzeResponse(response)\n\treferer = get_header(request_info.getHeaders(), \"Referer\")[1]\n\tlocation = get_header(response_info.getHeaders(), \"Location\")[1]\n\t# prepare referer and location\n\treferer_str = get_hostname(URL(referer)) if referer else None\n\tlocation_str = get_hostname(URL(location)) if location and re.match(\"https?://\", location) else None\n\t# Add referer and location headers to table\n\tif referer_str and referer_str != host_name:\n\t\tkey = unicode(referer_str) + unicode(host_name)\n\t\tif key not in session[\"dedup\"]:\n\t\t\trows.append([referer_str, host_name])\n\t\t\tsession[\"rows\"].append((unicode(referer_str), unicode(host_name), 0.125))\n\t\t\tsession[\"dedup\"][key] = None\n\tif location_str and location_str != host_name:\r\n\t\tkey = unicode(referer_str) + unicode(host_name)\n\t\tif key not in session[\"dedup\"]:\n\t\t\trows.append([host_name, location_str])\n\t\t\tsession[\"rows\"].append((unicode(host_name), unicode(location_str), 0.125))\n\t\t\tsession[\"dedup\"][key] = None\n# Add the list for the Python library networkx to the last row\nif ref == row_count:\n\trows.append([session[\"rows\"]])",
12 | "name": "Misc - Template Script to Visualize Web Site Relationships"
13 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/ad285305-2207-42e1-b56c-77a7dbd862a2.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "burp_professional_only": false,
9 | "uuid": "ad285305-2207-42e1-b56c-77a7dbd862a2",
10 | "version": "v1.1",
11 | "script": "\"\"\"\nThis script extracts JWTs from Authorization headers using the regular expression specified\r\nby session[\"jwt_regex\"] (see Line 17) Afterwards, it decodes the payload of each JWT and\r\nconverts it into a list, which is then displayed in the table above.\n\nUse this script to analyze the different values per JSON attribute to, for example, determine\ninconsistencies.\n\"\"\"\nimport re\nimport json\nimport traceback\n\n# Do the initial setup\nif ref == 1 or \"header\" not in session:\n\tsession = {}\n\tsession[\"header\"] = [\"Ref.\", \"Host\", \"URL\"]\r\n\tsession[\"jwt_header\"] = []\r\n\tsession[\"jwt_regex\"] = \"^Authorization:\\s+Bearer\\s+(?PeyJ\\w+?\\.eyJ\\w+?\\..+?)$\"\n\ndef add_json_object(json_object):\n\t\"\"\"\n\tThis method iterates through the given json_object and adds it to the rows list.\n\t\"\"\"\n\tglobal session\n\tglobal rows\n\tglobal url\n\tglobal ref\n\trow = [ref, get_hostname(url), url.getPath()]\n\tfor item in json_object.keys():\r\n\t\tif has_stopped():\r\n\t\t\tbreak\n\t\telif item not in session[\"jwt_header\"]:\n\t\t\tsession[\"jwt_header\"].append(item)\n\tfor item in session[\"jwt_header\"]:\r\n\t\tif has_stopped():\r\n\t\t\tbreak\n\t\telif item in json_object:\n\t\t\trow.append(json_object[item])\n\t\telse:\n\t\t\trow.append(None)\n\tif row:\n\t\trows = [row]\n\n# Process only in-scope HTTP requests and responses\nif in_scope:\n\trequest_info = helpers.analyzeRequest(message_info.getRequest())\n\ttry:\n\t\tid_tokens = []\n\t\t# Extract authorization header\n\t\tauthorization_header = get_jwt(request_info.getHeaders(), re_header=session[\"jwt_regex\"])\n\t\tif authorization_header:\n\t\t\tid_tokens.append(authorization_header)\n\t\t# TODO: Identify additional JWTs (e.g., from specific parameters) and add them to the id_tokens\n\t\t# list for further analysis. \n\t\tfor token in id_tokens:\r\n\t\t\tif has_stopped():\r\n\t\t\t\tbreak\n\t\t\t# In this case, we assume that the current token has already been decoded (e.g., by API method get_jwt).\n\t\t\telif not isinstance(token, list):\n\t\t\t\ttoken = decode_jwt(token)\n\t\t\tif token[1]:\n\t\t\t\tjson_object = json.JSONDecoder().decode(token[1])\r\n\t\t\t\tif has_stopped():\r\n\t\t\t\t\tbreak\n\t\t\t\telif json_object:\n\t\t\t\t\tadd_json_object(json_object)\n\texcept:\n\t\ttraceback.print_exc(file=callbacks.getStderr())\n\n# Only at the end, when all different JWT attributes are known, update the UI table's header\nif ref == row_count:\n\theader = session[\"header\"]\r\n\theader += session[\"jwt_header\"]",
12 | "name": "JWT - Template Script to Display the Payload Attributes of Authorization Headers (One JWT per Table Row)"
13 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Turbo Data Miner
2 |
3 | Turbo Data Miner is a [Burp Suite Extension](https://portswigger.net/bappstore/84350b8f090a4388a9d12cca141f201b), which
4 | adds a new tab `Turbo Miner` to Burp Suite's UI as well as a new entry `Turbo Data Miner`
5 | to Burp Suite's `Extensions` context menu entry. In the new tab, you are able to write new or select
6 | existing Python scripts that are executed on each request/response item currently stored in the Proxy History, Side
7 | Map, or on each request/response item that is sent or received by Burp Suite.
8 |
9 | The objective of these Python scripts is the flexible and dynamic extraction, correlation, and structured
10 | presentation of information from the Burp Suite state as well as the flexible and dynamic on-the-fly modification
11 | of outgoing or incoming HTTP requests. Thus, Turbo Data Miner shall aid in gaining a better and faster understanding of
12 | the data collected and processed by Burp Suite.
13 |
14 | The following screenshot provides an example how Turbo Data Miner can be used to obtain a structured presentation of all
15 | cookies (and their attributes) that are stored in the current Burp Suite project. At the bottom, we select the
16 | corresponding Python script in the dropdown menu (see 1), which automatically loads the selected Python script into the
17 | IDE text area (see 2) and there, we can customize it, if needed. Alternatively, we can create our own script by
18 | clicking button `New Script`. The analysis is started by clicking button `Start` (see 3). Afterwards, Turbo Data Miner
19 | executes the compiled Python script on each Request/Response item. Thereby, the script extracts cookie
20 | information from each response (see source code in 2) and adds it to the table (see 4). Finally, in the table, we
21 | can sort per column to gain a better understanding of each cookie attribute or we can perform additional operations
22 | via the table's context menu (see 5).
23 |
24 | 
25 |
26 | As we can see, with Python skills, an understanding of the
27 | [Burp Suite Extender API](https://portswigger.net/Burp/extender/api/index.html) as well as an understanding of Turbo
28 | Miner's API (see Turbo Data Miner tab `About` or directly the
29 | [HTML page](https://github.com/chopicalqui/TurboDataMiner/blob/master/turbodataminer/about.html) used by the `About` tab),
30 | we can extract and structure any information available in the current Burp Suite project.
31 |
32 | # Usage
33 |
34 | Refer to [Turbo Data Miner's Wiki](https://github.com/chopicalqui/TurboDataMiner/wiki).
35 |
36 | # Author
37 |
38 | **Lukas Reiter** ([@chopicalquy](https://twitter.com/chopicalquy)) -
39 | [Turbo Data Miner](https://github.com/chopicalqui/TurboDataMiner)
40 |
41 | # License
42 |
43 | This project is licensed under the GPLv3 License - see the
44 | [license](https://github.com/chopicalqui/TurboDataMiner/blob/master/LICENSE) file for details.
--------------------------------------------------------------------------------
/turbodataminer/scripts/4a70691d-14fd-4fea-815c-9eef43c560a9.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "burp_professional_only": false,
9 | "uuid": "4a70691d-14fd-4fea-815c-9eef43c560a9",
10 | "version": "v1.1",
11 | "script": "\"\"\"\nThis script parses the HTTP response body for XML objects and displays each leaf tag together with its\nattributes and values in the table above. Thereby, the rows of the table are deduplicated.\n\nUse this script to identify the location of a specific value within the XML object or to reduce the\r\ncomplexity of the XML object during a review.\n\"\"\"\nimport os\nimport re\nimport traceback\nfrom java.lang import Thread\n\n# Due to the following issue, we have to manually load our own local Apache Xerces library:\n# https://forum.portswigger.net/thread/saxparser-dependency-delimma-499c057a\nThread.currentThread().setContextClassLoader(xerceslib)\nimport xml.etree.ElementTree as ET\n\n# Do the initial setup\nif ref == 1 or \"dedup\" not in session:\n\theader = [\"Ref.\", \"Host\", \"URL\", \"Path\", \"Type\", \"Name\", \"Value\", \"Depth\"]\n\t# If you want to disable deduplication, remove the following line and press button \"Clear Session\" to \n\t# reset the content of the session variable\n\tsession[\"dedup\"] = {}\n\ndef get_items(tag, url, ref, path=\"/\", depth=1):\n\t\"\"\"\n\tThis method recursively parses the given XML tag and returns the results in a two-dimensional list.\n\t\"\"\"\n\ttag_name = re.sub(\"^\\{http://.*?\\}\", \"\", tag.tag)\n\tresult = []\n\tnew_path = os.path.join(path, tag_name)\n\tif len(list(tag)) == 0:\r\n\t\thost_name = get_hostname(url)\r\n\t\tpath = url.getPath()\n\t\tresult.append([ref, host_name, path, new_path, \"Tag\", unicode(tag_name), unicode(tag.text), depth])\n\t\tfor attribute in tag.items():\r\n\t\t\tif has_stopped():\r\n\t\t\t\tbreak\n\t\t\tresult.append([ref, host_name, path, \"{}/@{}\".format(new_path, attribute[0]), \"Attribute\", unicode(attribute[0]), unicode(attribute[1]), depth])\n\telse:\n\t\tfor item in list(tag):\r\n\t\t\tif has_stopped():\r\n\t\t\t\tbreak\n\t\t\tresult += get_items(item, url, ref, new_path, depth + 1)\n\treturn result\n\n# Process only in-scope HTTP responses\nresponse = message_info.getResponse()\nif in_scope and response:\n\tresponse_info = helpers.analyzeResponse(response)\n\tbody_offset = response_info.getBodyOffset()\n\tbody_bytes = response[body_offset:]\n\tbody_content = helpers.bytesToString(body_bytes)\n\t\n\ttry:\n\t\troot = ET.fromstring(body_content.encode(\"utf-8\"))\n\t\tresults = get_items(root, url, ref)\n \t# perform deduplication\n\t\tif \"dedup\" in session:\n\t\t\tfor row in results:\n\t\t\t\tkey = \":\".join([unicode(item) for item in row[1:]])\r\n\t\t\t\tif has_stopped():\r\n\t\t\t\t\tbreak\n\t\t\t\telif key not in session[\"dedup\"]:\n\t\t\t\t\trows.append(row)\n\t\t\t\t\tsession[\"dedup\"][key] = None\n\t\telse:\n\t\t\trows = results\n\texcept:\n\t\ttraceback.print_exc(file=callbacks.getStderr())",
12 | "name": "XML - Template Script to Display All XML Leaf Tag and Attribute Values (Deduplicated) From Responses"
13 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/7d0eb667-cca4-427a-8ca0-57d98c4177fd.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "burp_professional_only": false,
9 | "uuid": "7d0eb667-cca4-427a-8ca0-57d98c4177fd",
10 | "version": "v1.3",
11 | "script": "\"\"\"\nThis script extracts the values of all in-scope HTTP response headers, which are specified in variable \nsession[\"re_header_names\"] (see Lines 9+) and displays them in the table above.\n\"\"\"\nimport re\n\n# Do the initial setup\nif ref == 1 or \"header_names\" not in session or \"case_sensitive\" not in session:\n\tsession[\"re_header_names\"] = [\r\n\t\tre.compile(\"^Server$\", re.IGNORECASE),\n\t\tre.compile(\"^X-Powered-By$\", re.IGNORECASE),\r\n\t\tre.compile(\"^X-Content-Security-Policy$\", re.IGNORECASE),\n\t\tre.compile(\"^Content-Security-Policy$\", re.IGNORECASE),\n\t\tre.compile(\"^X-Frame-Options$\", re.IGNORECASE),\n\t\tre.compile(\"^Strict-Transport-Security$\", re.IGNORECASE),\n\t\tre.compile(\"^X-XSS-Protection$\", re.IGNORECASE),\n\t\tre.compile(\"^X-Content-Type-Options$\", re.IGNORECASE),\n\t\tre.compile(\"^Access-Control-Allow-Origin$\", re.IGNORECASE),\n\t\tre.compile(\"^Access-Control-Allow-Methods$\", re.IGNORECASE),\n\t\tre.compile(\"^Access-Control-Allow-Headers$\", re.IGNORECASE),\n\t\tre.compile(\"^Access-Control-Expose-Headers$\", re.IGNORECASE),\n\t\tre.compile(\"^Access-Control-Allow-Credentials$\", re.IGNORECASE),\n\t\tre.compile(\"^Access-Control-Max-Age$\", re.IGNORECASE),\n\t\tre.compile(\"^Origin$\", re.IGNORECASE),\n\t\tre.compile(\"^Referrer-Policy$\", re.IGNORECASE),\r\n\t\tre.compile(\"^Cache-control$\", re.IGNORECASE),\n\t\tre.compile(\"^Pragma$\", re.IGNORECASE),\n\t\tre.compile(\"^Expires$\", re.IGNORECASE),\r\n\t\tre.compile(\"^Age$\", re.IGNORECASE),\r\n\t\tre.compile(\"^X-Cache$\", re.IGNORECASE),\r\n\t\tre.compile(\"^CF-Cache-Status$\", re.IGNORECASE),\r\n\t\tre.compile(\"^Vary$\", re.IGNORECASE)]\n\theader = [\"Ref.\", \"URL\", \"Path\", \"Extension\", \"Status Code\", \"Has Params\", \"Content-Type\"]\n\textension = url.getPath().split(\".\")[-1]\n\textension = extension if \"/\" not in extension else \"\"\n\tfor item in session[\"re_header_names\"]:\n\t\theader.append(item.pattern)\n\n# Process only in-scope HTTP responses\nresponse = message_info.getResponse()\nif in_scope and response:\n\tresponse_info = helpers.analyzeResponse(response)\n\tcontent_type = get_content_type(response_info.getHeaders())\n\tresults = get_headers(response_info.getHeaders(), session[\"re_header_names\"])\n\tparameter_count = len(request_info.getParameters())\n\tcount = 0\n\trow = []\n\tfor key in session[\"re_header_names\"]:\n\t\tvalue = results[key.pattern]\n\t\tvalue = \";\".join(value) if value is not None else \"\"\r\n\t\tif has_stopped():\r\n\t\t\tbreak\n\t\telif value:\n\t\t\tcount = count + 1\n\t\t\trow.append(value)\n\t\telse:\n\t\t\trow.append(\"\")\n\tif count > 0:\n\t\ttmp = [ref, get_hostname(url), url.getPath(), extension, response_info.getStatusCode(), parameter_count>0, content_type]\n\t\ttmp.extend(row)\n\t\trows.append(tmp)",
12 | "name": "Header - Template Script to Extract HTTP Response Header Values via Regular Expressions"
13 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/c5b76994-90ff-4e0d-8419-434e8365cdeb.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 8
5 | ],
6 | "burp_professional_only": true,
7 | "uuid": "c5b76994-90ff-4e0d-8419-434e8365cdeb",
8 | "version": "v0.1",
9 | "script": "def do_passive_scan(message_info, session):\r\n\t\"\"\"\r\n\tThe Scanner invokes this method for each base request / response that is\r\n passively scanned. Note: Extensions should only analyze the HTTP messages\r\n\tprovided during passive scanning, and should not make any new HTTP\r\n\trequests of their own.\r\n \r\n\t:param request (IRequestResponse): The base HTTP request / response that\r\n\tshould be passively scanned.\r\n\t:param session (dict): The dictionary allows storing information accross\r\n\tmethod calls.\r\n\t:return A list of IScanIssue objects, or null if no issues are identified.\r\n\t\"\"\"\r\n\tprint(\"Passive Scan\")\r\n\treturn None\r\n\r\ndef do_active_scan(message_info, insertion_point, session):\r\n\t\"\"\"\r\n\tThe Scanner invokes this method for each insertion point that is actively\r\n\tscanned. Extensions may issue HTTP requests as required to carry out\r\n\tactive scanning, and should use the IScannerInsertionPoint object provided\r\n\tto build scan requests for particular payloads.\r\n Note:\r\n Scan checks should submit raw non-encoded payloads to insertion points,\r\n and the insertion point has responsibility for performing any data\r\n encoding that is necessary given the nature and location of the insertion\r\n point.\r\n\r\n\t:param request(IRequestResponse): The base HTTP request / response that\r\n\tshould be actively scanned.\r\n\t:param insertion_point: An IScannerInsertionPoint object that can be\r\n\tqueried to obtain details of the insertion point being tested, and can be\r\n\tused to build scan requests for particular payloads.\r\n\t:param session (dict): The dictionary allows storing information accross\r\n\tmethod calls.\r\n \t:return A list of IScanIssue objects, or null if no issues are identified.\r\n\t\"\"\"\r\n\tprint(\"Active Scan\")\r\n\treturn None\r\n\r\ndef consolidate_duplicate_issues(existing_issue, new_issue):\r\n\t\"\"\"\r\n\tThe Scanner invokes this method when the custom Scanner check has\r\n\treported multiple issues for the same URL path. This can arise either\r\n\tbecause there are multiple distinct vulnerabilities, or because the same\r\n\t(or a similar) request has been scanned more than once. The custom check\r\n\tshould determine whether the issues are duplicates. In most cases, where\r\n\ta check uses distinct issue names or descriptions for distinct issues,\r\n\tthe consolidation process will simply be a matter of comparing these\r\n\tfeatures for the two issues.\r\n\r\n\t:param existing_issue: An issue that was previously reported by this\r\n\tScanner check.\r\n\t:param new_issue: An issue at the same URL path that has been newly\r\n\treported by this Scanner check.\r\n\t:return An indication of which issue(s) should be reported in the main\r\n\tScanner results. The method should return -1 to report the existing\r\n\tissue only, 0 to report both issues, and 1 to report the new issue only.\r\n\t\"\"\"\r\n\treturn -1",
10 | "name": "Template Script to Implement a Scanner Check"
11 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/83e9f980-83e2-42da-b602-6b7741d13bcf.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "burp_professional_only": false,
9 | "uuid": "83e9f980-83e2-42da-b602-6b7741d13bcf",
10 | "version": "v1.2",
11 | "script": "\"\"\"\nThis script checks whether session cookie values are disclosed in Referer headers, URLs, or response bodies\r\nand if this is the case, then the location of the disclosure is displayed in the table above. Thereby, the\r\nrows of the table are deduplicated.\r\n\r\nNote that you have to specify the list of all cookie names that are used for authorization and whose values\r\nshould be investigated in list session[\"in_scope_cookie_names\"] (see Line 13). If you leave the list empty,\r\nthen all cookies issued by in-scope HTTP responses are used.\n\"\"\"\nimport re\n\n# Do the initial setup\nif ref == 1 or \"in_scope_cookie_names\" not in session or \"cookie_values\" not in session or \"dedup\" not in session:\n\t# TODO: Specify all session cookie names, which are used for authorization, in the following list.\n\tsession[\"in_scope_cookie_names\"] = [] # [\"JSESSIONID\"]\n\tsession[\"cookie_values\"] = {}\n\tsession[\"dedup\"] = {}\n\theader = [\"Ref.\", \"Host\", \"URL\", \"Disclosed Cookie\", \"In Path\", \"In Referer\", \"In Response Body\"]\n\ndef check_cookie(url, referer = None, body_content = None):\n\t\"\"\"\n\tThis method checks whether the given URL, Referer header value, or response body contains one of the already used session cookie values.\n\t\"\"\"\r\n\thost = unicode(get_hostname(url))\r\n\tpath = url.getPath()\n\n\tfor cookie_value, cookie_regex in session[\"cookie_values\"].items():\n\t\tin_path = len(cookie_regex.findall(path)) > 0\n\t\tin_referer = len(cookie_regex.findall(referer)) > 0 if referer else False\n\t\tin_body = len(cookie_regex.findall(body_content)) > 0 if body_content else False\n\t\tkey = host + path + unicode(in_path) + unicode(in_referer) + unicode(in_body)\n\r\n\t\tif has_stopped():\r\n\t\t\tbreak\n\t\telif (in_path or in_body) and key not in session[\"dedup\"]:\n\t\t\tsession[\"dedup\"][key] = None\n\t\t\trows.append([ref, host, path, cookie_value, in_path, in_referer, in_body])\n\n_, referer_value = get_header(request_info.getHeaders(), \"Referer\")\n# Process only in-scope HTTP responses\nif in_scope:\n\tresponse = message_info.getResponse()\n\tif response:\n\t\tresponse_info = helpers.analyzeResponse(response)\n\t\tbody_offset = response_info.getBodyOffset()\n\t\tbody_bytes = response[body_offset:]\n\t\tbody_content = helpers.bytesToString(body_bytes)\n\t\tcookies = get_cookies(response_info)\n\n\t\t# Add issued cookies to list of known cookies\n\t\tfor cookie in cookies:\r\n\t\t\tif has_stopped():\r\n\t\t\t\tprint(\"exit\")\r\n\t\t\t\tbreak\n\t\t\telif (not session[\"in_scope_cookie_names\"] or cookie[\"name\"] in session[\"in_scope_cookie_names\"]) and cookie[\"value\"]:\n\t\t\t\tcookie_value = re.escape(cookie[\"value\"])\n\t\t\t\tsession[\"cookie_values\"][cookie_value] = re.compile(cookie_value, re.IGNORECASE)\n\n\t\t# Check current URL and response body for known cookies\n\t\tcheck_cookie(url, referer_value, body_content)\n\telse:\n\t\tcheck_cookie(url, referer_value)\nelse:\n\tcheck_cookie(url, referer_value)",
12 | "name": "Cookie - Template Script to Detect Session Cookie Disclosure in Referer, URL, and HTML Page"
13 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/1457a809-ec9e-45d6-a087-301e101f6e55.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "burp_professional_only": false,
9 | "uuid": "1457a809-ec9e-45d6-a087-301e101f6e55",
10 | "version": "v1.2",
11 | "script": "\"\"\"\nThis script extracts all HTTP headers from in-scope HTTP requests and responses and\nadds them to the table above. Thereby, the rows of the table are deduplicated.\n\"\"\"\nimport traceback\n\n# Do the initial setup\nif ref == 1 or \"dedup\" not in session:\n\tsession[\"dedup\"] = {}\n\theader = [\"Ref.\", \"Source\", \"Host\", \"URL\", \"Content-Length\", \"Header Name\", \"Header Value\", \"Reflected\", \"Status\", \"Content-Type (Response)\"]\n\ndef analyze_headers(message_info, is_request):\n\t\"\"\"\n\tThis method implements the core functionality to extract the headers from the\n\tgiven IHttpRequestResponse object.\n\t\"\"\"\n\tresult = []\n\trequest = message_info.getRequest()\n\tresponse = message_info.getResponse()\n\trequest_info = helpers.analyzeRequest(request)\n\n\t# Extract relevant information from HTTP response\n\tif response:\n\t\tresponse_info = helpers.analyzeResponse(response)\n\t\tcontent_type = get_content_type(response_info.getHeaders())\n\t\tcontent_length = get_content_length(response_info.getHeaders())\n\t\tstatus_code = response_info.getStatusCode()\n\t\tcontent_type = content_type if content_type else \"\"\n\t\tcontent_length = content_length if content_length else -1\n\telse:\n\t\tstatus_code = -1\n\t\tcontent_type = \"\"\n\t\tcontent_length = -1\n\n\tif is_request:\n\t\tif response:\n\t\t\tresponse_string = unicode(helpers.bytesToString(response), errors=\"ignore\")\n\t\telse:\n\t\t\tresponse_string = \"\"\n\t\tfor header in request_info.getHeaders():\n\t\t\theader_name, header_value = split_http_header(header)\r\n\t\t\tif has_stopped():\r\n\t\t\t\tbreak\n\t\t\telif header_name:\n\t\t\t\treflected = header_value in response_string\r\n\t\t\t\thost = get_hostname(url)\n\t\t\t\tkey = unicode(host, errors=\"ignore\") + header_name + unicode(content_length) + header_value + unicode(reflected) + unicode(status_code) + content_type\n\t\t\t\tif key not in session[\"dedup\"]:\n\t\t\t\t\tsession[\"dedup\"][key] = None\n\t\t\t\t\trows.append([ref, \"Request\", host, url.getPath(), content_length, header_name, header_value, reflected, status_code, content_type])\n\telif response:\n\t\ttry:\n\t\t\tresponse_info = helpers.analyzeResponse(response)\n\t\t\trequest_string = unicode(helpers.bytesToString(request), errors=\"ignore\")\n\t\t\tfor header in response_info.getHeaders():\n\t\t\t\theader_name, header_value = split_http_header(header)\r\n\t\t\t\tif has_stopped():\r\n\t\t\t\t\tbreak\r\n\t\t\t\telif header_name:\n\t\t\t\t\treflected = header_value in request_string\r\n\t\t\t\t\thost = get_hostname(url)\n\t\t\t\t\tkey = unicode(host, errors=\"ignore\") + header_name + unicode(content_length) + header_value + unicode(reflected) + unicode(status_code) + content_type\n\t\t\t\t\tif key not in session[\"dedup\"]:\n\t\t\t\t\t\tsession[\"dedup\"][key] = None\n\t\t\t\t\t\trows.append([ref, \"Response\", host, url.getPath(), content_length, header_name, header_value, reflected, status_code, content_type])\t\n\t\texcept:\n\t\t\ttraceback.print_exc(file=callbacks.getStderr())\n\treturn result\n\n# Process only in-scope HTTP requests and responses\nif in_scope:\n\t# Extract headers from HTTP requests\n\tanalyze_headers(message_info, True)\n\t# Extract headers from HTTP responses\n\tanalyze_headers(message_info, False)",
12 | "name": "Header - Template Script to Extract All Headers From HTTP Requests and Responses"
13 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/d9f5c0b0-1da9-4a82-b572-ad47e70f92b0.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 0,
5 | 6,
6 | 7
7 | ],
8 | "burp_professional_only": false,
9 | "uuid": "d9f5c0b0-1da9-4a82-b572-ad47e70f92b0",
10 | "version": "v1.2",
11 | "script": "\"\"\"\nThis script extracts all parameters from in-scope HTTP requests and adds them to the table above\nfor further analysis. Thereby, the rows of the table are deduplicated.\n\nNotes: Update the content of the session[\"filter\"] list (see Lines 15+) to limit your analysis\r\nto certain parameter types.\n\"\"\"\r\nimport base64\nimport traceback\nfrom burp import IParameter\nfrom HTMLParser import HTMLParser\n\n# Do the initial setup\nif ref == 1 or \"dedup\" not in session:\n\tsession[\"dedup\"] = {}\n\t# TODO: Update to limit analysis to certain parameter types.\n\tsession[\"filter\"] = [\r\n\t\tIParameter.PARAM_URL,\n\t\tIParameter.PARAM_BODY,\n\t\tIParameter.PARAM_COOKIE,\n\t\tIParameter.PARAM_XML,\n\t\tIParameter.PARAM_XML_ATTR,\n\t\tIParameter.PARAM_MULTIPART_ATTR,\n\t\tIParameter.PARAM_JSON\r\n\t]\n\theader = [\"Ref.\", \"Host\", \"Path\", \"Content-Length\", \"Type\", \"Parameter\", \"Value\", \"Reflected\", \"Value (URL decoded)\", \"Value (Base64 decoded)\", \"Status\", \"Content-Type (Response)\"]\n\n# Process only in-scope HTTP requests and responses\nif in_scope:\n\thtml_parser = HTMLParser()\n\trequest = message_info.getRequest()\n\tresponse = message_info.getResponse()\n\trequest_info = helpers.analyzeRequest(request)\n\n\t# Extract relevant information from HTTP response\n\tif response:\n\t\tresponse_info = helpers.analyzeResponse(response)\n\t\tcontent_type = get_content_type(response_info.getHeaders())\n\t\tcontent_length = get_content_length(response_info.getHeaders())\n\t\tstatus_code = response_info.getStatusCode()\n\t\tresponse_string = unicode(helpers.bytesToString(response), errors=\"ignore\")\n\t\tcontent_type = content_type if content_type else \"\"\n\t\tcontent_length = content_length if content_length else -1\n\telse:\n\t\tresponse_string = \"\"\n\t\tcontent_type = \"\"\n\t\tstatus_code = \"\"\n\t\tcontent_length = -1\n\n\t# Start analysis\n\tfilter = session[\"filter\"]\n\tfor param in request_info.getParameters():\r\n\t\tif has_stopped():\r\n\t\t\tbreak\n\t\telif param.getType() in filter:\n\t\t\tparameter_type = unicode(get_parameter_name(param.getType()), errors=\"ignore\")\n\t\t\tname = unicode(param.getName(), errors=\"ignore\")\n\t\t\tvalue = unicode(param.getValue(), errors=\"ignore\")\n\t\t\tdecoded_value = unicode(html_parser.unescape(helpers.urlDecode(value)), errors='ignore')\n\t\t\treflected = value in response_string or decoded_value in response_string if value else False\n\t\t\thost_name = unicode(get_hostname(url), errors=\"ignore\")\n\t\t\tpath = unicode(url.getPath(), errors=\"ignore\")\n\t\t\tkey = host_name + path + parameter_type + name + value + unicode(content_length) + unicode(reflected, errors=\"ignore\") + content_type + unicode(status_code, errors=\"ignore\")\n\t\t\turl_decoded_value = url_decode(helpers.urlDecode(value))\n\t\t\ttry:\n\t\t\t\tbase64_decoded_value = helpers.bytesToString(base64.b64decode(url_decoded_value.lstrip(\"{\\\"\").lstrip(\"{'\").rstrip(\"\\\"}\").rstrip(\"\\'}\")))\n\t\t\t\t# We print base64 values that can be fully ASCII decoded. We could also use unicode instead of str.\n\t\t\t\tbase64_decoded_value = unicode(base64_decoded_value)\n\t\t\texcept:\n\t\t\t\tbase64_decoded_value = \"\"\n\t\t\tif key not in session[\"dedup\"]:\n\t\t\t\trows.append([ref, host_name, path, content_length, parameter_type, name, value, reflected, url_decoded_value, base64_decoded_value, status_code, content_type])\n\t\t\t\tsession[\"dedup\"][key] = None",
12 | "name": "Parameter - Template Script to Extract Parameter Names and Values from All In-Scope Requests"
13 | }
--------------------------------------------------------------------------------
/turbodataminer/scripts/c72570de-2a0d-4288-bc1c-730c2e1149db.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 5
5 | ],
6 | "uuid": "c72570de-2a0d-4288-bc1c-730c2e1149db",
7 | "version": "v1.1",
8 | "script": "\"\"\"\nThis script implements an custom editor tab via the IMessageEditorTab interface allowing \nthe convenient de- and encoding of Bearer Authentication headers like the following \n(source: https://jwt.io/):\n\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c\n\"\"\"\nimport re\n\ndef is_enabled(content, is_request, session):\n\t\"\"\"\n\tThis method is invoked before an HTTP message is displayed in an custom editor tab, so that this custom \n\ttab can indicate whether it should be enabled for that message.\n\t\n\tFor more information, refer to the Burp Suite API, IMessageEditorTab interface, method isEnabled.\n\t:param content (List[bytes]): The message that is about to be displayed by this custom editor tab, or a \n\tzero-length array if the existing message is to be cleared.\n\t:param is_request (bool): Indicates whether the message is a request or a response.\n\t:param session (dict): The dictionary allows storing information accross method calls.\n\t:return (bool) If the custom tab is able to handle the specified message, and so will be displayed within the \n\teditor. Otherwise, the tab will be hidden while this message is displayed.\n\t\"\"\"\n\t# Do the initial setup\n\tif \"re_header\" not in session or \"header\" not in session:\n\t\tsession[\"jwt_encoded\"] = re.compile(\"^Authorization:\\s+Bearer\\s+(?PeyJ\\w+?\\.eyJ\\w+?\\..+?)$\", re.IGNORECASE)\n\t\tsession[\"jwt_decoded\"] = re.compile(\"^Authorization:\\s+Bearer\\s+(?P\\{.+?\\})\\.(?P\\{.+?\\})\\.(?P.+?)$\", re.IGNORECASE)\n\t\tsession[\"header\"] = \"Authorization: Bearer\"\n\tresult = False\n\tif is_request:\n\t\trequest_info = helpers.analyzeRequest(content)\n\t\tjwt = get_jwt(request_info.getHeaders(), session[\"jwt_encoded\"].pattern)\n\t\tresult = (len(jwt) == 3 and jwt[0] and jwt[1])\n\treturn result\n\ndef set_message(content, is_request, session):\n\t\"\"\"\n\tThis method compiles the message to be displayed in this custom editor tab.\n\t\n\tFor more information, refer to the Burp Suite API, IMessageEditorTab interface, method set_message.\n\t:param content (List[bytes]): The original message based on which the new message, which is going to be \n\tdisplayed by this custom editor tab, is created.\n\t:param is_request (bool): Indicates whether the message is a request or a response.\n\t:param session (dict): The dictionary allows storing information accross method calls.\n\t:return (List[bytes]) Returns the modified content of variable content.\n\t\"\"\"\n\trequest_info = helpers.analyzeRequest(content)\n\tbody_offset = request_info.getBodyOffset()\n\tbody_bytes = content[body_offset:]\n\tjwt = get_jwt(request_info.getHeaders(), session[\"jwt_encoded\"].pattern)\n\theaders = []\n\tfor header in request_info.getHeaders():\n\t\tif header.startswith(session[\"header\"]):\n\t\t\theaders.append(\"{} {}\".format(session[\"header\"], \".\".join(jwt)))\n\t\telse:\n\t\t\theaders.append(header)\n\treturn helpers.buildHttpMessage(headers, body_bytes)\n\t\ndef get_message(content, session):\n\t\"\"\"\n\tThis method converts back the currently displayed message.\n\n\tFor more information, refer to the Burp Suite API, IMessageEditorTab interface, method set_message.\n\t:param content (List[bytes]): The original message based on which the new message, which is going to be \n\tdisplayed by this custom editor tab, is created.\n\t:param session (dict): The dictionary allows storing information accross method calls.\n\t:return (List[bytes]) Returns the modified content of variable content.\n\t\"\"\"\n\trequest_info = helpers.analyzeRequest(content)\n\tbody_offset = request_info.getBodyOffset()\n\tbody_bytes = content[body_offset:]\n\theaders = []\n\tfor header in request_info.getHeaders():\n\t\tmatch = session[\"jwt_decoded\"].match(header)\n\t\tif match:\n\t\t\theader = match.group(\"header\")\n\t\t\tpayload = match.group(\"payload\")\n\t\t\tsignature = match.group(\"signature\")\n\t\t\tjwt = encode_jwt(header, payload, signature)\n\t\t\theaders.append(\"{} {}\".format(session[\"header\"], jwt))\n\t\telse:\n\t\t\theaders.append(header)\n\treturn helpers.buildHttpMessage(headers, body_bytes)\n",
9 | "name": "JWT - Template Custom Message Tab to En- and Decoding Authentication Bearer Tokens"
10 | }
--------------------------------------------------------------------------------
/turbodataminer/turbodataminer/model/scope.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | This module implements the functionality to provide better scoping capabilities.
4 | """
5 |
6 | __author__ = "Lukas Reiter"
7 | __license__ = "GPL v3.0"
8 | __copyright__ = """Copyright 2022 Lukas Reiter
9 |
10 | This program is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | This program is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with this program. If not, see .
22 | """
23 | __version__ = 1.0
24 |
25 | import time
26 | import traceback
27 | from java.lang import Float
28 | from java.lang import String
29 | from java.lang import Integer
30 | from java.lang import Boolean
31 | from javax.swing.table import AbstractTableModel
32 |
33 |
34 | class BaseScopeDataModel(AbstractTableModel):
35 | """
36 | The data model used by class ScopeTable
37 |
38 | This class implements the data model to display scope information in the ScopeTable. This class maintains a list
39 | of rows.
40 | """
41 |
42 | def __init__(self, header=[], content=[]):
43 | self._header = ["Process"]
44 | self._header += header
45 | self._content = []
46 | for item in content:
47 | row = [False]
48 | row += list(item)
49 | self._content.append(row)
50 | self._column_count = len(self._header)
51 | self._row_count = len(self._content)
52 | self.fireTableStructureChanged()
53 | if self._row_count > 0:
54 | self.fireTableRowsInserted(0, self._row_count - 1)
55 |
56 | def set_header(self, columns):
57 | self._header = ["Process"]
58 | self._header += columns
59 | self._column_count = len(self._header)
60 | self.fireTableStructureChanged()
61 |
62 | def set_content(self, rows):
63 | self._content = []
64 | for item in rows:
65 | row = [False]
66 | row += list(item)
67 | self._content.append(row)
68 | self._row_count = len(self._content)
69 | if self._row_count > 0:
70 | self.fireTableRowsInserted(0, self._row_count - 1)
71 |
72 | def getRowCount(self):
73 | """Returns the total number of rows managed by the data model"""
74 | try:
75 | return self._row_count
76 | except:
77 | print(traceback.format_exc())
78 | return 0
79 |
80 | def getColumnCount(self):
81 | """Returns the total number of columns managemed by the data model"""
82 | try:
83 | return self._column_count
84 | except:
85 | print(traceback.format_exc())
86 | return 0
87 |
88 | def getColumnName(self, column_index):
89 | """Returns the column name at position column_index"""
90 | try:
91 | if column_index < len(self._header):
92 | return self._header[column_index]
93 | except:
94 | print(traceback.format_exc())
95 | return None
96 |
97 | def getValueAt(self, row_index, column_index):
98 | """Returns the element at row row_index and column column_index"""
99 | return self._content[row_index][column_index]
100 |
101 | def isCellEditable(self, row_index, column_index):
102 | """Returns true if the cell is editable."""
103 | return column_index == 0
104 |
105 | def setValueAt(self, value, row_index, column_index):
106 | """Updates the value at the given position"""
107 | self._content[row_index][column_index] = value
108 | self.fireTableCellUpdated(row_index, column_index)
109 |
110 | def get_type_at(self, column_index):
111 | """Returns the element type at position i"""
112 | result = String
113 | if self._row_count >= 1:
114 | value = self._content[0][column_index]
115 | if isinstance(value, bool):
116 | result = Boolean
117 | elif isinstance(value, float):
118 | result = Float
119 | elif isinstance(value, int):
120 | result = Integer
121 | elif isinstance(value, time):
122 | result = Date
123 | return result
124 |
125 | def getColumnClass(self, column_index):
126 | """Returns the column type at column_index"""
127 | return self.get_type_at(column_index)
128 |
--------------------------------------------------------------------------------
/turbodataminer/turbodataminer/ui/core/analyzers.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | This module implements core functionality for all analyzers.
4 | """
5 |
6 | __author__ = "Lukas Reiter"
7 | __license__ = "GPL v3.0"
8 | __copyright__ = """Copyright 2020 Lukas Reiter
9 |
10 | This program is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | This program is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with this program. If not, see .
22 | """
23 | __version__ = 1.0
24 |
25 | from threading import Lock
26 | from javax.swing import JMenu
27 | from javax.swing import JMenuItem
28 | from burp import IContextMenuFactory
29 | from burp import IContextMenuInvocation
30 | from turbodataminer.ui.core.jtabbedpaneclosable import JTabbedPaneClosable
31 |
32 |
33 | class ContextMenuAnalyzerMenuItem(JMenuItem):
34 | def __init__(self, analyzer, title, actionPerformed):
35 | JMenuItem.__init__(self, title, actionPerformed=actionPerformed)
36 | self.analyzer = analyzer
37 |
38 |
39 | class JTabbedPaneClosableContextMenuAnalyzer(JTabbedPaneClosable, IContextMenuFactory):
40 | """
41 | Implements a JTabbedPane which allows users to add and close tabs.
42 | """
43 |
44 | def __init__(self, **kwargs):
45 | JTabbedPaneClosable.__init__(self, **kwargs)
46 | self._context_menu_invocation_lock = Lock()
47 | self.__context_menu_invocation = None
48 | self.extender.callbacks.registerContextMenuFactory(self)
49 |
50 | @property
51 | def _context_menu_invocation(self):
52 | with self._context_menu_invocation_lock:
53 | result = self.__context_menu_invocation
54 | return result
55 |
56 | @_context_menu_invocation.setter
57 | def _context_menu_invocation(self, value):
58 | with self._context_menu_invocation_lock:
59 | self.__context_menu_invocation = value
60 |
61 | def createMenuItems(self, invocation):
62 | """
63 | This method will be called by Burp Suite when the user invokes a context menu anywhere within Burp Suite. The
64 | factory can then provide any custom context menu items that should be displayed in the context menu, based on
65 | the details of the menu invocation.
66 |
67 | :param invocation An object that implements the IMessageEditorTabFactory interface, which the extension can
68 | query to obtain details of the context menu invocation.
69 | :return: A list of custom menu items (which may include sub-menus, checkbox menu items, etc.) that should be
70 | displayed. Extensions may return null from this method, to indicate that no menu items are required.
71 | """
72 | self._context_menu_invocation = invocation
73 | menu_items = []
74 | if invocation.getInvocationContext() in [
75 | IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_REQUEST,
76 | IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_RESPONSE,
77 | IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_REQUEST,
78 | IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_RESPONSE,
79 | IContextMenuInvocation.CONTEXT_TARGET_SITE_MAP_TREE,
80 | IContextMenuInvocation.CONTEXT_TARGET_SITE_MAP_TABLE,
81 | IContextMenuInvocation.CONTEXT_PROXY_HISTORY,
82 | IContextMenuInvocation.CONTEXT_SCANNER_RESULTS,
83 | IContextMenuInvocation.CONTEXT_INTRUDER_PAYLOAD_POSITIONS,
84 | IContextMenuInvocation.CONTEXT_INTRUDER_ATTACK_RESULTS,
85 | IContextMenuInvocation.CONTEXT_SEARCH_RESULTS]:
86 | pha_menu = JMenu("Send to Context Menu Analyzer")
87 | # Obtain all tab names
88 | for index in range(0, self.getTabCount() - 1):
89 | tab_component = self.getTabComponentAt(index)
90 | component = self.getComponentAt(index)
91 | title = tab_component.get_title()
92 | pha_menu.add(ContextMenuAnalyzerMenuItem(component,
93 | "Tab: {}".format(title),
94 | actionPerformed=self.menu_invocation_pressed))
95 | menu_items.append(pha_menu)
96 | return menu_items
97 |
98 | def menu_invocation_pressed(self, event):
99 | """This method will be called when one of the menu items are pressed."""
100 | event.getSource().analyzer.menu_invocation_pressed(self._context_menu_invocation)
101 |
--------------------------------------------------------------------------------
/turbodataminer/turbodataminer/ui/core/messageviewpane.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | This module implements the UI element that displays a single IHttpRequestResponse item.
4 | """
5 |
6 | __author__ = "Lukas Reiter"
7 | __license__ = "GPL v3.0"
8 | __copyright__ = """Copyright 2020 Lukas Reiter
9 |
10 | This program is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | This program is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with this program. If not, see .
22 | """
23 | __version__ = 1.0
24 |
25 |
26 | from javax.swing import JPanel
27 | from javax.swing import JSplitPane
28 | from javax.swing import JTabbedPane
29 | from java.awt import BorderLayout
30 | from burp import IHttpRequestResponse
31 |
32 |
33 | class MessageViewPane(JPanel):
34 | """
35 | This class implements a single message information tab
36 | """
37 |
38 | def __init__(self, extender, message_editor_controller):
39 | JPanel.__init__(self)
40 | self.setLayout(BorderLayout())
41 | self._split_pane = JSplitPane(JSplitPane.HORIZONTAL_SPLIT)
42 | self._request_details = extender.callbacks.createMessageEditor(message_editor_controller, False)
43 | self._response_details = extender.callbacks.createMessageEditor(message_editor_controller, False)
44 | self._split_pane.setTopComponent(self._request_details.getComponent())
45 | self._split_pane.setBottomComponent(self._response_details.getComponent())
46 | self.add(self._split_pane)
47 | self._split_pane.setResizeWeight(0.5)
48 | self._visible = True
49 |
50 | def set_message_info(self, value):
51 | if value:
52 | self.set_visible(True)
53 | self.set_request(value.getRequest())
54 | self.set_response(value.getResponse())
55 | else:
56 | self.set_visible(True)
57 |
58 | def set_request(self, request):
59 | if request:
60 | self._request_details.getComponent().setVisible(True)
61 | self._request_details.setMessage(request, True)
62 | else:
63 | self._request_details.getComponent().setVisible(False)
64 |
65 | def set_response(self, response):
66 | if response:
67 | self._response_details.setMessage(response, False)
68 | self._split_pane.setDividerLocation(0.5)
69 | self._response_details.getComponent().setVisible(True)
70 | else:
71 | self._response_details.getComponent().setVisible(False)
72 |
73 | def set_visible(self, visible):
74 | if self._visible != visible:
75 | self._visible = visible
76 | self.setVisible(visible)
77 |
78 |
79 | class DynamicMessageViewer(JTabbedPane):
80 | """
81 | This class dynamically adds and removes message information in the IdePane
82 | """
83 |
84 | def __init__(self, extender, message_editor_controller):
85 | self._message_infos = {}
86 | self._extender = extender
87 | self._message_editor_controller = message_editor_controller
88 |
89 | @property
90 | def message_infos(self):
91 | return self._message_infos
92 |
93 | @message_infos.setter
94 | def message_infos(self, value):
95 | if isinstance(value, dict):
96 | # Remove invalid elements
97 | value = {key: message_info for key, message_info in value.items()
98 | if isinstance(message_info, IHttpRequestResponse) and isinstance(key, str)}
99 | # Remove unneeded tabs from UI
100 | original_titles = set([item for item in self._message_infos.keys()])
101 | new_titles = set([item for item in value.keys()])
102 | for tab_title in original_titles - new_titles:
103 | tab_index = self.indexOfTab(tab_title)
104 | if 0 <= tab_index:
105 | pane = self.getComponentAt(tab_index)
106 | self.remove(pane)
107 | # Update existing tabs
108 | self._message_infos = value
109 | for key, message_info in self._message_infos.items():
110 | tab_index = self.indexOfTab(key)
111 | if 0 <= tab_index:
112 | pane = self.getComponentAt(tab_index)
113 | pane.set_message_info(message_info)
114 | else:
115 | pane = MessageViewPane(self._extender, self._message_editor_controller)
116 | pane.set_message_info(message_info)
117 | self.addTab(key, pane)
118 |
--------------------------------------------------------------------------------
/turbodataminer/data/error-signatures.json:
--------------------------------------------------------------------------------
1 | {
2 | "source": "https://raw.githubusercontent.com/augustd/burp-suite-error-message-checks/master/src/main/resources/burp/match-rules.tab",
3 | "rules": [
4 | {"regex": "([A-Za-z]{1,32}\\.)+[A-Za-z]{0,32}\\(([A-Za-z0-9]+\\s+[A-Za-z0-9]+[,\\s]*)*\\)\\s+\\+{1}\\d+", "group": 0, "type": "ASP.Net", "severity": "Low", "confidence": "Certain"},
5 | {"regex": "\"Message\":\"Invalid web service call", "group": 0, "type": "ASP.Net", "severity": "Low", "confidence": "Certain"},
6 | {"regex": "Exception of type", "group": 0, "type": "ASP.Net", "severity": "Low", "confidence": "Certain"},
7 | {"regex": "--- End of inner exception stack trace ---", "group": 0, "type": "ASP.Net", "severity": "Low", "confidence": "Certain"},
8 | {"regex": "Microsoft OLE DB Provider", "group": 0, "type": "ASP.Net", "severity": "Low", "confidence": "Certain"},
9 | {"regex": "Error ([\\d-]+) \\([\\dA-Fa-f]+\\)", "group": 0, "type": "ASP.Net", "severity": "Low", "confidence": "Certain"},
10 | {"regex": "\\bat ([a-zA-Z0-9_]*\\.)*([a-zA-Z0-9_]+)\\([a-zA-Z0-9, \\[\\]\\&\\;]*\\)", "group": 0, "type": "ASP.Net", "severity": "Low", "confidence": "Certain"},
11 | {"regex": "([A-Za-z]{1,32}\\.)+[A-Za-z]{0,32}Exception:", "group": 0, "type": "ASP.Net", "severity": "Low", "confidence": "Certain"},
12 | {"regex": "in [A-Za-z]:\\\\([A-Za-z0-9_]+\\\\)+[A-Za-z0-9_\\-]+(\\.aspx)?\\.cs:line [\\d]+", "group": 0, "type": "ASP.Net", "severity": "Low", "confidence": "Certain"},
13 | {"regex": "Syntax error in string in query expression", "group": 0, "type": "ASP.Net", "severity": "Medium", "confidence": "Certain"},
14 | {"regex": "\\.java:[0-9]+", "group": 0, "type": "Java", "severity": "Low", "confidence": "Certain"},
15 | {"regex": "\\.java\\((Inlined )?Compiled Code\\)", "group": 0, "type": "Java", "severity": "Low", "confidence": "Certain"},
16 | {"regex": "\\.invoke\\(Unknown Source\\)", "group": 0, "type": "Java", "severity": "Low", "confidence": "Certain"},
17 | {"regex": "nested exception is", "group": 0, "type": "Java", "severity": "Low", "confidence": "Firm"},
18 | {"regex": "\\.js:[0-9]+:[0-9]+", "group": 0, "type": "Javascript", "severity": "Low", "confidence": "Certain"},
19 | {"regex": "JBWEB[0-9]{6}:", "group": 0, "type": "JBoss", "severity": "Low", "confidence": "Firm"},
20 | {"regex": "((dn|dc|cn|ou|uid|o|c)=[\\w\\d]*,\\s?){2,}", "group": 0, "type": "LDAP", "severity": "Low", "confidence": "Firm"},
21 | {"regex": "\\[(ODBC SQL Server Driver|SQL Server|ODBC Driver Manager)\\]", "group": 0, "type": "Microsoft SQL Server", "severity": "Low", "confidence": "Certain"},
22 | {"regex": "Cannot initialize the data source object of OLE DB provider \"[\\w]*\" for linked server \"[\\w]*\"", "group": 0, "type": "Microsoft SQL Server", "severity": "Low", "confidence": "Certain"},
23 | {"regex": "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near", "group": 0, "type": "MySQL", "severity": "Medium", "confidence": "Certain"},
24 | {"regex": "Illegal mix of collations \\([\\w\\s\\,]+\\) and \\([\\w\\s\\,]+\\) for operation", "group": 0, "type": "MySQL", "severity": "Medium", "confidence": "Certain"},
25 | {"regex": "at (\\/[A-Za-z0-9\\.]+)*\\.pm line [0-9]+", "group": 0, "type": "Perl", "severity": "Low", "confidence": "Certain"},
26 | {"regex": "\\.php on line [0-9]+", "group": 0, "type": "PHP", "severity": "Low", "confidence": "Certain"},
27 | {"regex": "\\.php on line [0-9]+", "group": 0, "type": "PHP", "severity": "Low", "confidence": "Certain"},
28 | {"regex": "Fatal error:", "group": 0, "type": "PHP", "severity": "Low", "confidence": "Certain"},
29 | {"regex": "\\.php:[0-9]+", "group": 0, "type": "PHP", "severity": "Low", "confidence": "Certain"},
30 | {"regex": "Traceback \\(most recent call last\\):", "group": 0, "type": "Python", "severity": "Low", "confidence": "Certain"},
31 | {"regex": "File \\\"[A-Za-z0-9\\-_\\./]*\\\", line [0-9]+, in", "group": 0, "type": "Python", "severity": "Low", "confidence": "Certain"},
32 | {"regex": "\\.rb:[0-9]+:in", "group": 0, "type": "Ruby", "severity": "Low", "confidence": "Certain"},
33 | {"regex": "\\.scala:[0-9]+", "group": 0, "type": "Scala", "severity": "Low", "confidence": "Certain"},
34 | {"regex": "\\(generated by waitress\\)", "group": 0, "type": "Waitress Python server", "severity": "Information", "confidence": "Certain"},
35 | {"regex": "132120c8|38ad52fa|38cf013d|38cf0259|38cf025a|38cf025b|38cf025c|38cf025d|38cf025e|38cf025f|38cf0421|38cf0424|38cf0425|38cf0427|38cf0428|38cf0432|38cf0434|38cf0437|38cf0439|38cf0442|38cf07aa|38cf08cc|38cf04d7|38cf04c6|websealerror", "group": 0, "type": "WebSEAL", "severity": "Low", "confidence": "Certain"},
36 | {"regex": "\\.groovy:[0-9]+", "group": 0, "type": "Groovy", "severity": "High", "confidence": "Certain"},
37 | {"regex": "\\.lang\\.([A-Za-z0-9_])+\\.([A-Za-z0-9_]+)Exception", "group": 0, "type": "Java", "severity": "Medium", "confidence": "Firm"},
38 | {"regex": "\\.lang\\.([A-Za-z0-9_]+)Exception", "group": 0, "type": "Java", "severity": "Medium", "confidence": "Firm"}
39 | ]
40 | }
--------------------------------------------------------------------------------
/turbodataminer/turbodataminer/ui/scoping/scopetable.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | This module implements the UI component to display scope information.
4 | """
5 |
6 | __author__ = "Lukas Reiter"
7 | __license__ = "GPL v3.0"
8 | __copyright__ = """Copyright 2022 Lukas Reiter
9 |
10 | This program is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | This program is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with this program. If not, see .
22 | """
23 | __version__ = 1.0
24 |
25 | import traceback
26 | from javax.swing import JMenu
27 | from javax.swing import JTable
28 | from javax.swing import JMenuItem
29 | from javax.swing import JPopupMenu
30 | from javax.swing import JOptionPane
31 | from java.lang import Float
32 | from java.lang import Double
33 | from java.lang import String
34 | from java.lang import Integer
35 | from javax.swing.table import DefaultTableCellRenderer
36 | from turbodataminer.model.scope import BaseScopeDataModel
37 |
38 |
39 | class ScopeDefaultTableCellRenderer(DefaultTableCellRenderer):
40 | """
41 | This class implements the default JTable background.
42 | """
43 |
44 | def __init__(self):
45 | DefaultTableCellRenderer.__init__(self)
46 |
47 | def getTableCellRendererComponent(self, table, value, is_selected, has_focus, row, column):
48 | """This method is called by the UI table to calculate a row's background color."""
49 | result = DefaultTableCellRenderer.getTableCellRendererComponent(self, table, value, is_selected, has_focus, row, column)
50 | if not is_selected:
51 | result.setBackground(None)
52 | return result
53 |
54 |
55 | class ScopeTable(JTable):
56 | """
57 | The component shows scope information in the graphical user interface in a JTable.
58 | """
59 | def __init__(self, data_model=BaseScopeDataModel()):
60 | JTable.__init__(self, data_model)
61 | self.setDefaultRenderer(Integer, ScopeDefaultTableCellRenderer())
62 | self.setDefaultRenderer(String, ScopeDefaultTableCellRenderer())
63 | self.setDefaultRenderer(Float, ScopeDefaultTableCellRenderer())
64 | self.setDefaultRenderer(Double, ScopeDefaultTableCellRenderer())
65 | self.setAutoCreateRowSorter(True)
66 | self.data_model = data_model
67 | # table pop menu
68 | self._popup_menu = JPopupMenu()
69 | # Clear Table
70 | self._popup_menu.add(JMenuItem("Check all rows",
71 | actionPerformed=self._select_all_menu_pressed))
72 | self._popup_menu.add(JMenuItem("Uncheck all rows",
73 | actionPerformed=self._unselect_all_menu_pressed))
74 | self._popup_menu.addSeparator()
75 | self._popup_menu.add(JMenuItem("Check all selected rows",
76 | actionPerformed=self._select_all_selected_menu_pressed))
77 | self._popup_menu.add(JMenuItem("Uncheck all selected rows",
78 | actionPerformed=self._unselect_all_selected_menu_pressed))
79 | self._popup_menu.addSeparator()
80 | self._popup_menu.add(JMenuItem("Invert selection",
81 | actionPerformed=self._invert_selection_menu_pressed))
82 | self.setComponentPopupMenu(self._popup_menu)
83 |
84 | def set_model(self, header, rows):
85 | self.data_model.set_header(header)
86 | self.data_model.set_content(rows)
87 |
88 | def _select_all_menu_pressed(self, event):
89 | self._update_all_rows(True)
90 |
91 | def _unselect_all_menu_pressed(self, event):
92 | self._update_all_rows(False)
93 |
94 | def _select_all_selected_menu_pressed(self, event):
95 | self._update_selected_rows(True)
96 |
97 | def _unselect_all_selected_menu_pressed(self, event):
98 | self._update_selected_rows(False)
99 |
100 | def _invert_selection_menu_pressed(self, event):
101 | row_count = self.data_model.getRowCount()
102 | for i in range(0, row_count):
103 | value = self.data_model.getValueAt(i, 0)
104 | self.data_model.setValueAt(not value, i, 0)
105 |
106 | def _update_all_rows(self, value):
107 | """This method updates the process column of all rows to value."""
108 | row_count = self.data_model.getRowCount()
109 | for i in range(0, row_count):
110 | self.data_model.setValueAt(value, i, 0)
111 |
112 | def _update_selected_rows(self, value):
113 | """This method updates the process column of all selected rows to value."""
114 | selected_rows = self.getSelectedRows()
115 | for selected_row in selected_rows:
116 | model_row = self.convertRowIndexToModel(selected_row)
117 | self.data_model.setValueAt(value, model_row, 0)
118 |
--------------------------------------------------------------------------------
/turbodataminer/scripts/cadf16a4-0743-4359-a6a8-f011659571b3.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 5
5 | ],
6 | "uuid": "cadf16a4-0743-4359-a6a8-f011659571b3",
7 | "version": "v1.0",
8 | "script": "\"\"\"\nThis script implements an custom editor tab via the IMessageEditorTab interface allowing \nthe convenient URL de- and encoding of the parameter names and types specified in list\nsession[\"filter\"] (see Line 30). Each element in session[\"filter\"] is a tuple. The tuple's first element\nis the parameter name and the second element is the parameter type that shall be decoded.\n\nUpdate the GET parameter name and type specified session[\"filter\"] according to your needs.\n\"\"\"\nimport re\nfrom burp import IParameter\n\ndef is_enabled(content, is_request, session):\n\t\"\"\"\n\tThis method is invoked before an HTTP message is displayed in an custom editor tab, so that this custom \n\ttab can indicate whether it should be enabled for that message.\n\t\n\tFor more information, refer to the Burp Suite API, IMessageEditorTab interface, method isEnabled.\n\t:param content (List[bytes]): The message that is about to be displayed by this custom editor tab, or a \n\tzero-length array if the existing message is to be cleared.\n\t:param is_request (bool): Indicates whether the message is a request or a response.\n\t:param session (dict): The dictionary allows storing information accross method calls.\n\t:return (bool) If the custom tab is able to handle the specified message, and so will be displayed within the \n\teditor. Otherwise, the tab will be hidden while this message is displayed.\n\t\"\"\"\n\t# Do the initial setup\n\tsession[\"parameters\"] = []\n\tif \"filter\" not in session or \"request\" not in session:\n\t\tsession[\"request\"] = None\n\t\t# TODO: Update parameter names and types\n\t\tsession[\"filter\"] = [(\"PARAMETER NAME\", IParameter.PARAM_URL)]\n\tresult = False\n\n\t# Search for the parameter that should be decoded\n\tif is_request:\n\t\trequest_info = helpers.analyzeRequest(content)\n\t\tfor parameter in request_info.getParameters():\n\t\t\tfor parameter_name, parameter_type in session[\"filter\"]:\n\t\t\t\tif (not parameter_type or parameter_type == parameter.getType()) and unicode(parameter.getName(), errors=\"ignore\") == parameter_name:\n\t\t\t\t\tresult = True\n\t\t\t\t\tsession[\"parameters\"].append(parameter)\n\t\t\t\t\tif not session[\"request\"]:\n\t\t\t\t\t\tsession[\"request\"] = content\n\treturn result\n\ndef set_message(content, is_request, session):\n\t\"\"\"\n\tThis method compiles the message to be displayed in this custom editor tab.\n\t\n\tFor more information, refer to the Burp Suite API, IMessageEditorTab interface, method set_message.\n\t:param content (List[bytes]): The original message based on which the new message, which is going to be \n\tdisplayed by this custom editor tab, is created.\n\t:param is_request (bool): Indicates whether the message is a request or a response.\n\t:param session (dict): The dictionary allows storing information accross method calls.\n\t:return (List[bytes]) Returns the modified content of variable content.\n\t\"\"\"\n\tresult = content\n\tif \"filter\" not in session:\n\t\tsession[\"filter\"] = {}\n\tif is_request:\n\t\t# Extract body for building the new request\n\t\trequest_info = helpers.analyzeRequest(content)\n\t\tbody_offset = request_info.getBodyOffset()\n\t\tbody_bytes = content[body_offset:]\n\t\t\n\t\t# Obtain and URL decode the relevant parameter\n\t\tnew_content = content\n\t\tfor parameter in request_info.getParameters():\n\t\t\tfor parameter_name, parameter_type in session[\"filter\"]:\n\t\t\t\tif (not parameter_type or parameter_type == parameter.getType()) and unicode(parameter.getName(), errors=\"ignore\") == parameter_name:\n\t\t\t\t\tvalue = helpers.urlDecode(parameter.getValue())\n\t\t\t\t\tnew_parameter = helpers.buildParameter(parameter.getName(), value, parameter.getType())\n\t\t\t\t\tnew_content = helpers.updateParameter(new_content, new_parameter)\n\n\t\t# Build new request\n\t\tnew_request_info = helpers.analyzeRequest(new_content)\n\t\tcontent = helpers.buildHttpMessage(new_request_info.getHeaders(), body_bytes)\n\treturn content\n\t\ndef get_message(content, session):\n\t\"\"\"\n\tThis method converts back the currently displayed message.\n\n\tFor more information, refer to the Burp Suite API, IMessageEditorTab interface, method set_message.\n\t:param content (List[bytes]): The original message based on which the new message, which is going to be \n\tdisplayed by this custom editor tab, is created.\n\t:param session (dict): The dictionary allows storing information accross method calls.\n\t:return (List[bytes]) Returns the modified content of variable content.\n\t\"\"\"\n\tif \"filter\" not in session:\n\t\tsession[\"filter\"] = {}\n\t# Extract body for building the new request\n\trequest_info = helpers.analyzeRequest(content)\n\tbody_offset = request_info.getBodyOffset()\n\tbody_bytes = content[body_offset:]\n\n\t# URL encode in-scope parameters\n\tnew_content = content\n\tfor parameter in request_info.getParameters():\n\t\tfor parameter_name, parameter_type in session[\"filter\"]:\n\t\t\tif (not parameter_type or parameter_type == parameter.getType()) and unicode(parameter.getName(), errors=\"ignore\") == parameter_name:\n\t\t\t\tvalue = helpers.urlEncode(parameter.getValue())\n\t\t\t\tnew_parameter = helpers.buildParameter(parameter.getName(), value, parameter.getType())\n\t\t\t\tnew_content = helpers.updateParameter(new_content, new_parameter)\t\n\t# Build new request\n\tnew_request_info = helpers.analyzeRequest(new_content)\n\treturn helpers.buildHttpMessage(new_request_info.getHeaders(), body_bytes)\n",
9 | "name": "Parameter - Template Script to URL En- and Decode Parameter Values"
10 | }
--------------------------------------------------------------------------------
/turbodataminer/turbodataminer/model/messaging.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | This module implements all functionality for async messaging
4 | """
5 |
6 | __author__ = "Lukas Reiter"
7 | __license__ = "GPL v3.0"
8 | __copyright__ = """Copyright 2020 Lukas Reiter
9 |
10 | This program is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | This program is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with this program. If not, see .
22 | """
23 | __version__ = 1.0
24 |
25 | import uuid
26 | import time
27 | import traceback
28 | from threading import Lock
29 | from threading import Thread
30 | from java.util.concurrent import TimeUnit
31 | from java.util.concurrent import LinkedBlockingDeque
32 | from turbodataminer.ui.core.scripting import ErrorDialog
33 |
34 |
35 | class MessagingQueueItem:
36 | def __init__(self, request):
37 | self.request = request
38 | self.uuid = str(uuid.uuid4())
39 |
40 |
41 | class CommunicationManager:
42 | """
43 | This class implements multi-threaded async sending and receiving of HTTP requests and responses.
44 | """
45 |
46 | def __init__(self, extender, ide_pane):
47 | """
48 | :param extender:
49 | :param ide_pane:
50 | """
51 | self._extender = extender
52 | self._callbacks = extender.callbacks
53 | self._http_service_lock = Lock()
54 | self._http_service = None
55 | self._ide_pane = ide_pane
56 | self._queue = LinkedBlockingDeque()
57 | self._stop_lock = Lock()
58 | self._callback_method_lock = Lock()
59 | self._callback_method = None
60 | self._kwargs = None
61 | self._cache_lock = Lock()
62 | self._cache = {}
63 | self.rows_lock = Lock()
64 | self.message_infos_lock = Lock()
65 | self.__stop = False
66 | self._threads = []
67 |
68 | @property
69 | def http_service(self):
70 | with self._http_service_lock:
71 | result = self._http_service
72 | return result
73 |
74 | def set_http_service(self, value):
75 | with self._http_service_lock:
76 | self._http_service = value
77 |
78 | @property
79 | def callback_method(self):
80 | with self._callback_method_lock:
81 | result = self._callback_method
82 | return result
83 |
84 | def register_callback(self, method):
85 | with self._callback_method_lock:
86 | self._callback_method = method
87 |
88 | def register_arguments(self, **kwargs):
89 | self._kwargs = kwargs
90 |
91 | @property
92 | def _stop(self):
93 | """
94 | This thread-safe property allows worker threads to determine if the producer thread already signaled that no
95 | further HTTP requests will be put into the sending queue.
96 | :return:
97 | """
98 | with self._stop_lock:
99 | result = self.__stop
100 | return result
101 |
102 | def stop(self):
103 | """
104 | This thead-safe method is used by the producer thread to signal worker threads that no further HTTP requests
105 | are put into the seinding queue.
106 | :return: The method does not req
107 | """
108 | with self._stop_lock:
109 | self.__stop = True
110 |
111 | def add_http_request(self, request):
112 | """
113 | This method is used by the producer thread to add new requests to the sending queue.
114 | :param request: The request that shall be added to the sending queue.
115 | :return: GUID
116 | """
117 | item = MessagingQueueItem(request)
118 | result = None
119 | if self._queue.add(item):
120 | uuid = item.uuid
121 | with self._cache_lock:
122 | self._cache[uuid] = {"uuid": uuid}
123 | result = self._cache[uuid]
124 | return result
125 |
126 | def _make_http_request(self):
127 | """
128 | This method is used by the worker threads to send HTTP requests.
129 | :return:
130 | """
131 | while not self._stop and self._ide_pane.activated:
132 | item = self._queue.poll(500, TimeUnit.MILLISECONDS)
133 | if item:
134 | try:
135 | request_response = self._callbacks.makeHttpRequest(self._http_service, item.request, False)
136 | if self.callback_method:
137 | with self._cache_lock:
138 | cache = self._cache[item.uuid]
139 | self.callback_method(new_message_info=request_response, cache=cache, **self._kwargs)
140 | except:
141 | self._ide_pane.activated = False
142 | traceback.print_exc(file=self._callbacks.getStderr())
143 | ErrorDialog.Show(self._extender.parent, traceback.format_exc())
144 |
145 | def start(self, thread_count=5):
146 | """
147 | This method starts all worker threads
148 | :param thread_count:
149 | :return:
150 | """
151 | for i in range(0, thread_count):
152 | thread = Thread(target=self._make_http_request)
153 | thread.daemon = True
154 | thread.start()
155 | self._threads.append(thread)
156 |
157 | def join(self):
158 | """
159 | This method waits until the queue is empty and afterwards, notifies all worker threads that work is complete.
160 | :return:
161 | """
162 | # Wait until the queue is empty
163 | while self._queue.size() != 0 and self._ide_pane.activated:
164 | time.sleep(.5)
165 | # Signal threads that no further HTTP requests will be queued.
166 | self.stop()
167 | # Wait until all threads are completed.
168 | for thread in self._threads:
169 | thread.join()
170 |
--------------------------------------------------------------------------------
/turbodataminer/turbodataminer/model/scripting.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | This module implements the core functionality for scripts
4 | """
5 |
6 | __author__ = "Lukas Reiter"
7 | __license__ = "GPL v3.0"
8 | __copyright__ = """Copyright 2020 Lukas Reiter
9 |
10 | This program is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | This program is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with this program. If not, see .
22 | """
23 | __version__ = 1.0
24 |
25 | import os
26 | import uuid
27 | import json
28 |
29 |
30 | class PluginType:
31 | # TODO: Update in case of new intel component
32 | proxy_history_analyzer = 0
33 | http_listener_analyzer = 1
34 | proxy_listener_analyzer = 2
35 | http_listener_modifier = 3
36 | proxy_listener_modifier = 4
37 | custom_message_editor = 5
38 | site_map_analyzer = 6
39 | context_menu_analyzer = 7
40 | scanner_check = 8
41 |
42 |
43 | class PluginCategory:
44 | # TODO: Update in case of new intel component
45 | analyzer = 0
46 | modifier = 1
47 | custom_message_editor = 2
48 | scan = 3
49 |
50 |
51 | class PluginInformation:
52 | """
53 | This plugin holds information about a specific plugin (e.g., Proxy History Parser)
54 | """
55 |
56 | def __init__(self, plugin_id, name, category, selected=False):
57 | self._plugin_id = plugin_id
58 | self._name = name
59 | self._selected = selected
60 | self._category = category
61 |
62 | @property
63 | def plugin_id(self):
64 | return self._plugin_id
65 |
66 | @property
67 | def name(self):
68 | return self._name
69 |
70 | @property
71 | def selected(self):
72 | return self._selected
73 |
74 | @property
75 | def category(self):
76 | return self._category
77 |
78 | @staticmethod
79 | def get_plugin_by_id(plugin_id):
80 | for plugin in LIST:
81 | if plugin.plugin_id == plugin_id:
82 | return plugin
83 | return None
84 |
85 | @staticmethod
86 | def get_plugins_by_category(categories=None):
87 | rvalues = []
88 | if not isinstance(categories, list):
89 | categories = [categories]
90 | if not categories:
91 | return LIST
92 | for plugin in LIST:
93 | if plugin.category in categories:
94 | rvalues.append(plugin)
95 | return rvalues
96 |
97 | def __repr__(self):
98 | return self._name
99 |
100 |
101 | # TODO: Update in case of new intel component
102 | LIST = [PluginInformation(PluginType.proxy_history_analyzer, "Proxy History Analyzer", PluginCategory.analyzer),
103 | PluginInformation(PluginType.site_map_analyzer, "Site Map Analyzer", PluginCategory.analyzer),
104 | PluginInformation(PluginType.http_listener_analyzer, "HTTP Listener Analyzer", PluginCategory.analyzer),
105 | PluginInformation(PluginType.context_menu_analyzer, "Context Menu Analyzer", PluginCategory.analyzer),
106 | PluginInformation(PluginType.http_listener_modifier, "HTTP Listener Modifier", PluginCategory.modifier),
107 | PluginInformation(PluginType.proxy_listener_modifier, "Proxy Listener Modifier", PluginCategory.modifier),
108 | PluginInformation(PluginType.custom_message_editor, "Custom Message Editor", PluginCategory.custom_message_editor),
109 | PluginInformation(PluginType.scanner_check, "Custom Scanner Check", PluginCategory.scan)]
110 |
111 |
112 | class ScriptInformation:
113 | """
114 | This class holds all information and methods about a script
115 | """
116 |
117 | def __init__(self,
118 | guid=str(uuid.uuid4()),
119 | name=None,
120 | author=None,
121 | version=None,
122 | plugins=[],
123 | script=None,
124 | burp_professional_only=False):
125 | self.uuid = guid
126 | self.name = name
127 | self.author = author
128 | self.version = version
129 | self.plugins = plugins
130 | self.burp_professional_only = burp_professional_only
131 | self._script = script
132 |
133 | @property
134 | def script(self):
135 | return self._script if self._script else ""
136 |
137 | @script.setter
138 | def script(self, value):
139 | self._script = value if value else ""
140 |
141 | @property
142 | def file_name(self):
143 | return "{}.json".format(self.uuid)
144 |
145 | @staticmethod
146 | def load_json(object):
147 | """This method parses the given json object and returns a class of type ScriptInformation"""
148 | json_object = json.JSONDecoder().decode(object) if isinstance(object, str) else object
149 | plugins = []
150 | if "plugins" in json_object:
151 | plugins = [PluginInformation.get_plugin_by_id(plugin_id) for plugin_id in json_object["plugins"]]
152 | return ScriptInformation(guid=json_object["uuid"] if "uuid" in json_object else None,
153 | name=json_object["name"] if "name" in json_object else None,
154 | author=json_object["author"] if "author" in json_object else None,
155 | version=json_object["version"] if "version" in json_object else None,
156 | plugins=plugins,
157 | burp_professional_only=json_object["burp_professional_only"] if "burp_professional_only" in json_object else False,
158 | script=json_object["script"] if "script" in json_object else None)
159 |
160 | def is_new(self, script_path):
161 | """This method returns true if the given script is new."""
162 | full_path = os.path.join(script_path, self.file_name)
163 | return not os.path.isfile(full_path)
164 |
165 | def get_json(self):
166 | """This method returns a json object representing the object"""
167 | return {"uuid": self.uuid,
168 | "name": self.name,
169 | "author": self.author,
170 | "version": self.version,
171 | "plugins": [item.plugin_id for item in self.plugins],
172 | "burp_professional_only": self.burp_professional_only,
173 | "script": self.script}
174 |
175 | def __repr__(self):
176 | if self.name:
177 | name = self.name + ("*" if self.burp_professional_only else "")
178 | return "{} ({}) - {} - {} - {}".format(name, self.uuid, self.version, self.author, self.plugins)
179 | return ""
180 |
--------------------------------------------------------------------------------
/turbodataminer/scripts/711a885e-d2dc-46b7-b379-b8ad3f04424b.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Lukas Reiter",
3 | "plugins": [
4 | 7
5 | ],
6 | "burp_professional_only": false,
7 | "uuid": "711a885e-d2dc-46b7-b379-b8ad3f04424b",
8 | "version": "v1.0",
9 | "script": "\"\"\"\r\nThis script appends XSS polygots to all user-selected parameters.\r\n\r\nUpdate the variable SNIPER if you want to inject the polygots in battery ram style rather than sniper style.\r\n\"\"\"\r\nimport re\r\nfrom datetime import datetime\r\n\r\n\r\nclass ScanItem:\r\n\tdef __init__(self, payload, expected_value=\"\", regex=False, prepand_param_value=False):\r\n\t\tself.payload = unicode(payload)\r\n\t\tself._regex = regex\r\n\t\tself._prepand_param_value = prepand_param_value\r\n\t\tself._expected_value = unicode(expected_value)\r\n\r\n\tdef get_payload(self, parameter):\r\n\t\t# global helpers\r\n\t\treturn unicode(parameter.getValue()) + helpers.urlEncode(self.payload)\r\n\r\n\tdef check_response(self, message_info, parameter):\r\n\t\t# global helpers\r\n\t\tresult = False\r\n\t\tif self._expected_value or self._prepand_param_value:\r\n\t\t\tresponse = helpers.bytesToString(message_info.getResponse())\r\n\t\t\tpattern = parameter.getValue() + self._expected_value if self._prepand_param_value else self._expected_value\r\n\t\t\tif self._regex:\r\n\t\t\t\tresult = re.search(pattern, response) is not None\r\n\t\t\telse:\r\n\t\t\t\tresult = pattern in response\r\n\t\treturn result\r\n\r\n\r\n# If the following variable is set to True, then the script injects the polygot in sniper style (each parameter individually) and if set to False in battery ram style (all parameters at once).\r\nSNIPER = True\r\nheader = [\"Ref.\", \"Host\", \"URL\", \"Parameter\", \"Value\", \"Payload\", \"Hit\", \"Response Timer\", \"Status Code\", \"Content Length\", \"Status Code (New)\", \"Content Length (New)\"]\r\n\r\n# List of XSS polygots to be tested\r\n# Source: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet#polyglots\r\nxss_polygots = [ScanItem(\"\"), ScanItem(\"\"\"javascript:/*-->