├── turbodataminer ├── turbodataminer │ ├── __init__.py │ ├── model │ │ ├── __init__.py │ │ ├── scope.py │ │ ├── messaging.py │ │ ├── scripting.py │ │ ├── heatmap.py │ │ └── intelligence.py │ └── ui │ │ ├── __init__.py │ │ ├── core │ │ ├── __init__.py │ │ ├── analyzers.py │ │ ├── messageviewpane.py │ │ ├── intelbase.py │ │ └── jtabbedpaneclosable.py │ │ ├── scoping │ │ ├── __init__.py │ │ ├── scopetable.py │ │ └── scopedialog.py │ │ ├── modifiers.py │ │ ├── scannercheck.py │ │ └── custommessage.py ├── data │ ├── xercesImpl.jar │ ├── error-signatures.json │ └── file-signatures.json └── scripts │ ├── 89f968ba-6eb0-404a-be2c-aa2aa745b6ef.json │ ├── e0f0e59d-1cd7-46c2-a314-62ee6237a5b6.json │ ├── f0cfa6f0-70d7-4d82-91c2-24bbf7efd61d.json │ ├── e0c874c5-9736-4213-b8dc-8bb21f1e0d48.json │ ├── 1a2e4592-73d4-4c7e-81fd-23b379a54570.json │ ├── 21c8632e-2213-4450-8198-9f3bd657771f.json │ ├── 1d52128b-9770-4179-a3f1-6af16c032d3e.json │ ├── 26d5dd1a-401e-4e37-a6c1-ae4fde640c3a.json │ ├── 24768fe1-0bb5-4f99-ac79-f11b54dff146.json │ ├── b61fc205-cf03-48d7-820a-75f2cbf12530.json │ ├── aa0b7080-f27c-4ed4-a6a2-7802c6656a64.json │ ├── cd9f9437-da1d-41b9-9660-4e3575268f62.json │ ├── 3fe1e364-1c65-48e9-8696-cc89e2d09d56.json │ ├── d8dbb2e9-1765-4319-baef-81ba36d3654b.json │ ├── 15302930-aa43-4d7a-88c5-434bc4b9f763.json │ ├── f6aa595e-b675-427f-b466-cf36149521d7.json │ ├── fcfbfcaa-e902-4469-bc75-0c5634d4571a.json │ ├── 12881ce6-2477-4b0c-a633-9d170f8b07ab.json │ ├── 45516c08-5289-4c66-ae30-8e7c69319e0a.json │ ├── ad285305-2207-42e1-b56c-77a7dbd862a2.json │ ├── 4a70691d-14fd-4fea-815c-9eef43c560a9.json │ ├── 7d0eb667-cca4-427a-8ca0-57d98c4177fd.json │ ├── c5b76994-90ff-4e0d-8419-434e8365cdeb.json │ ├── 83e9f980-83e2-42da-b602-6b7741d13bcf.json │ ├── 1457a809-ec9e-45d6-a087-301e101f6e55.json │ ├── d9f5c0b0-1da9-4a82-b572-ad47e70f92b0.json │ ├── c72570de-2a0d-4288-bc1c-730c2e1149db.json │ ├── cadf16a4-0743-4359-a6a8-f011659571b3.json │ └── 711a885e-d2dc-46b7-b379-b8ad3f04424b.json ├── media ├── example.png ├── about-tab.png ├── context-menu-analyzer-context-menu.png └── context-menu-analyzer-example-script.png ├── BappManifest.bmf ├── BappDescription.html ├── .gitignore └── README.md /turbodataminer/turbodataminer/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /turbodataminer/turbodataminer/model/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /turbodataminer/turbodataminer/ui/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /turbodataminer/turbodataminer/ui/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /turbodataminer/turbodataminer/ui/scoping/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /media/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chopicalqui/TurboDataMiner/HEAD/media/example.png -------------------------------------------------------------------------------- /media/about-tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chopicalqui/TurboDataMiner/HEAD/media/about-tab.png -------------------------------------------------------------------------------- /turbodataminer/data/xercesImpl.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chopicalqui/TurboDataMiner/HEAD/turbodataminer/data/xercesImpl.jar -------------------------------------------------------------------------------- /media/context-menu-analyzer-context-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chopicalqui/TurboDataMiner/HEAD/media/context-menu-analyzer-context-menu.png -------------------------------------------------------------------------------- /media/context-menu-analyzer-example-script.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chopicalqui/TurboDataMiner/HEAD/media/context-menu-analyzer-example-script.png -------------------------------------------------------------------------------- /BappManifest.bmf: -------------------------------------------------------------------------------- 1 | Uuid: 84350b8f090a4388a9d12cca141f201b 2 | ExtensionType: 2 3 | Name: Turbo Data Miner 4 | RepoName: turbo-data-miner 5 | ScreenVersion: 1.0a 6 | SerialVersion: 4 7 | MinPlatformVersion: 2 8 | ProOnly: False 9 | Author: Chopicalqui 10 | ShortDescription: Flexible and dynamic extraction, correlation, and structured presentation of information as well as on-the-fly modification of outgoing or incoming HTTP requests using Python scripts. 11 | EntryPoint: turbodataminer/turbodataminer.py 12 | BuildCommand: 13 | SupportedProducts: Pro, Community 14 | -------------------------------------------------------------------------------- /BappDescription.html: -------------------------------------------------------------------------------- 1 |

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 | ![Turbo Data Miner's Proxy History Analyzer](media/example.png) 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:/*-->\"\"\", expected_value=\"ja\", prepand_param_value=True),\r\n\tScanItem(\"\"\"javascript:\"/*'/*`/*-->\"\"\", expected_value=\"ja\", prepand_param_value=True),\r\n\tScanItem(\"\"\"javascript:/*-->
\"\"\", expected_value=\"ja\", prepand_param_value=True)]\r\n\r\n# List of basic SQL injection tests\r\nsqli_payloads = [ScanItem(\"\"), ScanItem(\"'\"), ScanItem(\"''\"), ScanItem(\"'||'\"), ScanItem(\"'+'\"), ScanItem(\"+1\"), ScanItem(\"1+1\"), ScanItem(\"' concat '\"),\r\n\tScanItem(\"' --\"), ScanItem(\"'; --\"), ScanItem(\"'--\"), ScanItem(\"';--\"), ScanItem(\"' #\"), ScanItem(\"'; #\"), ScanItem(\"'#\"), ScanItem(\"';#\"),\r\n\tScanItem(\"' OR 1/0 --\"), ScanItem(\"' OR 1/0; --\"), ScanItem(\"' OR 1/0--\"), ScanItem(\"' OR 1/0;--\"), ScanItem(\"' OR 1/0 #\"),\r\n\tScanItem(\"' OR 1/0; #\"), ScanItem(\"' OR 1/0#\"), ScanItem(\"' OR 1/0;#\"), ScanItem(\"'; WAITFOR DELAY ('0:0:5') --\"), ScanItem(\"'; SELECT sleep(10);\"),\r\n\tScanItem(\"'; SELECT pg_sleep(10);\")]\r\n\r\n# SSTI payloads\r\nssti_payloads = [ScanItem(\"\"), ScanItem('{{9999998+1}}', expected_value=9999999), ScanItem('{{1/0}}'), ScanItem(\"\"\"{{<%[%'\"}}%\\\\\"\"\", prepand_param_value=True),\r\n\tScanItem('}}ASDF', expected_value=\"[^}]ASDF\", regex=True), ScanItem(\"\"\"<%=9999998+1%>\"\"\", expected_value=9999999),\r\n\tScanItem(\"\"\"<%=1/0%>\"\"\"), ScanItem(\"\"\"%>ASDF\"\"\", expected_value=\"[^>]ASDF\", regex=True), ScanItem(\"\"\"<%sleep(5)%>\"\"\")]\r\n\r\ndef obtain_stats(message_info):\r\n\t\"\"\"\r\n\tThis method obtains statistics from HTTP responses for the UI table.\r\n\t\"\"\"\r\n\tresult = []\r\n\tresponse = message_info.getResponse()\r\n\tif response:\r\n\t\tresponse_info = helpers.analyzeResponse(response)\r\n\t\tbody_offset = response_info.getBodyOffset()\r\n\t\tcontent_length = len(response) - body_offset\r\n\t\tresult.extend([response_info.getStatusCode(), content_length])\r\n\telse:\r\n\t\tresult.extend([-1, -1])\r\n\treturn result\r\n\r\n\r\ndef perform_test(scope_object, payloads, tab_prefix, sniper):\r\n\t\"\"\"\r\n\tThis method creates the test requests based on the given parameters.\r\n\t\"\"\"\r\n\r\n\tif not scope_object.canceled:\r\n\t\tservice_info = message_info.getHttpService()\r\n\t\tif sniper:\r\n\t\t\t# Append current payload to each selected parameter individually.\r\n\t\t\tfor parameter in request_info.getParameters():\r\n\t\t\t\tfor i in range(0, len(payloads)):\r\n\t\t\t\t\tscan_item = payloads[i]\r\n\t\t\t\t\tif scope_object.match(parameter):\r\n\t\t\t\t\t\t# Build updated request\r\n\t\t\t\t\t\tparameter_name = parameter.getName()\r\n\t\t\t\t\t\tnew_parameter = helpers.buildParameter(parameter_name, scan_item.get_payload(parameter), parameter.getType())\r\n\t\t\t\t\t\tnew_request = helpers.updateParameter(message_info.getRequest(), new_parameter)\r\n\t\t\t\t\t\t# Send the updated HTTP request\r\n\t\t\t\t\t\tstart = datetime.now()\r\n\t\t\t\t\t\tnew_message_info = callbacks.makeHttpRequest(service_info, new_request, False)\r\n\t\t\t\t\t\tend = datetime.now()\r\n\t\t\t\t\t\thit = scan_item.check_response(new_message_info, parameter)\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t# Obtain information for UI table\r\n\t\t\t\t\t\trow = [ref, get_hostname(url), url.getPath(), parameter_name, parameter.getValue(), scan_item.payload, hit, (end - start).microseconds]\r\n\t\t\t\t\t\trow += obtain_stats(message_info)\r\n\t\t\t\t\t\trow += obtain_stats(new_message_info)\r\n\t\t\t\t\t\tadd_table_row(row, {tab_prefix: new_message_info})\r\n\t\telse:\r\n\t\t\t# Append current polygot to all selected parameters at once.\r\n\t\t\tfor i in range(0, len(payloads)):\r\n\t\t\t\tscan_item = payloads[i]\r\n\t\t\t\tnew_request = None\r\n\t\t\t\tfor parameter in request_info.getParameters():\r\n\t\t\t\t\tif scope_object.match(parameter):\r\n\t\t\t\t\t\t# Build updated request\r\n\t\t\t\t\t\tparameter_name = parameter.getName()\r\n\t\t\t\t\t\tnew_parameter = helpers.buildParameter(parameter_name, scan_item.get_payload(parameter), parameter.getType())\r\n\t\t\t\t\t\tnew_request = helpers.updateParameter(new_request, new_parameter) if new_request else helpers.updateParameter(message_info.getRequest(), new_parameter)\r\n\t\t\t\tif new_request:\r\n\t\t\t\t\t# Send the updated HTTP request\r\n\t\t\t\t\tstart = datetime.now()\r\n\t\t\t\t\tnew_message_info = callbacks.makeHttpRequest(service_info, new_request, False)\r\n\t\t\t\t\tend = datetime.now()\r\n\t\t\t\t\t# Obtain information for UI table\r\n\t\t\t\t\trow = [ref, get_hostname(url), url.getPath(), None, None, scan_item.payload, None, (end - start).microseconds]\r\n\t\t\t\t\trow += obtain_stats(message_info)\r\n\t\t\t\t\trow += obtain_stats(new_message_info)\r\n\t\t\t\t\tadd_table_row(row, {tab_prefix: new_message_info})\r\n\r\n# Display scope dialog and define scope\r\nscope_object = show_scope_parameter_dialog(request_info)\r\n\r\n# Perform SQLi test\r\nperform_test(scope_object, sqli_payloads, \"SQLi\", SNIPER)\r\n\r\n# Perform XSS test\r\n# xss_polygots += ssti_payloads\r\n# perform_test(scope_object, xss_polygots, \"XSS\", SNIPER)\r\n", 10 | "name": "Scan - Template Script to Append Injection Payloads (XSS/SQLi/SSTI) to User-Selected Parameters" 11 | } -------------------------------------------------------------------------------- /turbodataminer/turbodataminer/ui/core/intelbase.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This module implements the base class functionalities for all analyzers, modifiers, and custom message GUIs. 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 json 27 | import base64 28 | import traceback 29 | from threading import Lock 30 | from javax.swing import JPanel 31 | from java.awt import BorderLayout 32 | from turbodataminer.exports import ExportedMethods 33 | from turbodataminer.ui.core.scripting import IdePane 34 | from turbodataminer.ui.core.scripting import ErrorDialog 35 | from turbodataminer.model.scripting import ScriptInformation 36 | from turbodataminer.model.scripting import PluginInformation 37 | 38 | 39 | class IntelBaseConfiguration: 40 | """ 41 | This class parses an API for reading an intel plugin's JSON configuration. 42 | """ 43 | 44 | def __init__(self, configuration=None): 45 | """The JSON object that shall be loaded""" 46 | self.script_info = None 47 | self.code_changed = False 48 | self.activated = False 49 | if configuration: 50 | self.script_info = ScriptInformation.load_json( 51 | configuration["script_info"] if "script_info" in configuration else {}) 52 | self.code_changed = configuration["code_changed"] if "code_changed" in configuration else False 53 | self.activated = configuration["activated"] if "activated" in configuration else False 54 | 55 | 56 | class IntelBase(JPanel): 57 | """ 58 | This abstract class is the base class for all analyzers, modifiers, and custom message panes GUIs. 59 | """ 60 | 61 | SCRIPTS_DIR = "scripts" 62 | 63 | def __init__(self, extender, plugin_id, plugin_category_id, pre_code=None, post_code=None, configuration=None, 64 | executable_on_startup=False, closable_tabbed_pane=None, disable_start_stop_button=False, 65 | disable_clear_session_button=False): 66 | """ 67 | :param extender: 68 | :param id: Usually the class name. This information is used for storing the current state in Burp Suite in case 69 | the extension is unloaded. 70 | :param plugin_id: Is of type tubodataminer.model.scripting.PluginType and specifies the plugin type. 71 | :param plugin_category_id: Is of type tubodataminer.model.scripting.PluginCategory and specifies the plugin 72 | category. 73 | :param pre_code: If specified, then this code is inserted before the user-provided script code. 74 | :param pre_code: If specified, then this code is appended to the user-provided script code. 75 | :param configuration: JSON object containing configuration information for the given plugin type. Usually 76 | this configuration is obtained from Burp Suite's configuration storage at startup. 77 | :param executable_on_startup: Boolean that specifies whether the given plugin type is allowed (True) to 78 | automatically execute after startup. 79 | :closable_tabbed_pane: Is of type JTabbedPaneClosable and allows the intelligence plugin to access content of 80 | the tabbed pane like the current tab's title. 81 | """ 82 | JPanel.__init__(self) 83 | self.setLayout(BorderLayout()) 84 | self._extender = extender 85 | self._callbacks = extender.callbacks 86 | self._plugin_category_id = plugin_category_id 87 | self._helpers = self._callbacks.getHelpers() 88 | self._plugin_id = plugin_id 89 | self._scripts_dir = os.path.join(extender.home_dir, IntelBase.SCRIPTS_DIR) 90 | self._ide_pane = IdePane(self, 91 | pre_script_code=pre_code, 92 | post_script_code=post_code, 93 | disable_start_stop_button=disable_start_stop_button, 94 | disable_clear_session_button=disable_clear_session_button) 95 | self._exported_methods = ExportedMethods(extender, self._ide_pane) 96 | self._session = {} 97 | self._ref = 1 98 | self._executable_on_startup = executable_on_startup 99 | self._ide_pane.code_changed = False 100 | self.closable_tabbed_pane = closable_tabbed_pane 101 | # Load configuration 102 | if not configuration: 103 | configuration = IntelBaseConfiguration() 104 | configuration.script_info = ScriptInformation(plugins=[PluginInformation.get_plugin_by_id(self._plugin_id)]) 105 | configuration.activated = configuration.activated and executable_on_startup 106 | self._ide_pane.script_info = configuration.script_info 107 | self._ide_pane.code_changed = configuration.code_changed 108 | # Register start, stop and cleaning methods 109 | self._ide_pane.register_start_analysis_function(self.start_analysis) 110 | self._ide_pane.register_stop_analysis_function(self.stop_analysis) 111 | self._ide_pane.register_clear_session_function(self.clear_session) 112 | 113 | @property 114 | def ide_pane(self): 115 | return self._ide_pane 116 | 117 | @property 118 | def extender(self): 119 | return self._extender 120 | 121 | @property 122 | def callbacks(self): 123 | return self._callbacks 124 | 125 | @property 126 | def plugin_category_id(self): 127 | return self._plugin_category_id 128 | 129 | @property 130 | def plugin_id(self): 131 | return self._plugin_id 132 | 133 | @property 134 | def scripts_dir(self): 135 | return self._scripts_dir 136 | 137 | @property 138 | def session(self): 139 | return self._session 140 | 141 | def get_json(self): 142 | result = {} 143 | script_info = self._ide_pane.script_info 144 | result["script_info"] = script_info.get_json() 145 | result["code_changed"] = self._ide_pane.code_changed 146 | result["activated"] = self._ide_pane.activated 147 | return result 148 | 149 | def clear_session(self): 150 | with self._table_model_lock: 151 | self._session = {} 152 | 153 | def start_analysis(self): 154 | """This method is invoked when the analysis is started""" 155 | raise NotImplementedError("This function is not implemented!") 156 | 157 | def stop_analysis(self): 158 | """This method is invoked when the analysis is stopped""" 159 | pass 160 | 161 | def process_proxy_history_entry(self, message_info, is_request=False, tool_flag=None, send_date=None, received_date=None, 162 | listener_interface=None, client_ip_address=None, timedout=None, 163 | message_reference=None, proxy_message_info=None, time_delta=None, in_scope=None, 164 | communication_manager=None, invocation=None): 165 | raise NotImplementedError("Method not implemented yet") 166 | -------------------------------------------------------------------------------- /turbodataminer/turbodataminer/ui/modifiers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This module implements all functionalities for all modifier GUIs. 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 traceback 26 | from burp import IHttpListener 27 | from burp import IProxyListener 28 | from turbodataminer.ui.core.intelbase import IntelBase 29 | from turbodataminer.ui.core.scripting import ErrorDialog 30 | from turbodataminer.model.scripting import PluginType 31 | from turbodataminer.model.scripting import PluginCategory 32 | from java.lang import NullPointerException 33 | 34 | 35 | class ModifierTab(IntelBase): 36 | """ 37 | This class implements the GUI and base class for on the fly modifications. 38 | """ 39 | 40 | def __init__(self, **kwargs): 41 | IntelBase.__init__(self, plugin_category_id=PluginCategory.modifier, executable_on_startup=True, **kwargs) 42 | 43 | def start_analysis(self): 44 | """This method is invoked when the analysis is started""" 45 | self._ref = 1 46 | 47 | def process_proxy_history_entry(self, message_info, is_request=False, tool_flag=None, send_date=None, received_date=None, 48 | listener_interface=None, client_ip_address=None, timedout=None, 49 | message_reference=None, proxy_message_info=None, time_delta=None, in_scope=None, 50 | communication_manager=None, invocation=None): 51 | """ 52 | This method executes the Python script for each HTTP request response item in the HTTP proxy history. 53 | :return: Returns True if execution was successful else False 54 | """ 55 | if not message_info: 56 | return 57 | # Setup API 58 | try: 59 | request_info = self._helpers.analyzeRequest(message_info) 60 | url = request_info.getUrl() 61 | in_scope = self._callbacks.isInScope(url) if in_scope is None else in_scope 62 | except NullPointerException: 63 | # This should never happen except the project file is corrupt. 64 | traceback.print_exc(file=self._callbacks.getStderr()) 65 | return 66 | globals = { 67 | 'callbacks': self._callbacks, 68 | 'xerceslib': self._extender.xerces_classloader, 69 | 'plugin_id': self._plugin_id, 70 | 'is_request': is_request, 71 | 'get_json_attributes': self._exported_methods.get_json_attributes, 72 | 'get_json_attribute_by_path': self._exported_methods.get_json_attribute_by_path, 73 | 'get_headers': self._exported_methods.get_headers, 74 | 'get_header': self._exported_methods.get_header, 75 | 'get_cookies': self._exported_methods.get_cookies, 76 | 'get_cookie_attributes': self._exported_methods.get_cookie_attributes, 77 | 'get_parameters': self._exported_methods.get_parameters, 78 | 'get_parameter_name': self._exported_methods.get_parameter_name, 79 | 'compress_gzip': self._exported_methods.compress_gzip, 80 | 'decompress_gzip': self._exported_methods.decompress_gzip, 81 | 'get_content_length': self._exported_methods.get_content_length, 82 | 'get_content_type': self._exported_methods.get_content_type, 83 | 'get_hostname': self._exported_methods.get_hostname, 84 | 'analyze_signatures': self._exported_methods.analyze_signatures, 85 | 'find_error_messages': self._exported_methods.find_error_messages, 86 | 'get_extension_info': self._exported_methods.get_extension_info, 87 | 'find_versions': self._exported_methods.find_versions, 88 | 'find_domains': self._exported_methods.find_domains, 89 | 'decode_html': self._exported_methods.decode_html, 90 | 'url_decode': self._exported_methods.url_decode, 91 | 'analyze_request': self._exported_methods.analyze_request, 92 | 'analyze_response': self._exported_methods.analyze_response, 93 | 'get_jwt': self._exported_methods.get_jwt, 94 | 'decode_jwt': self._exported_methods.decode_jwt, 95 | 'encode_jwt': self._exported_methods.encode_jwt, 96 | 'send_http_message': self._exported_methods.send_http_message, 97 | 'split_http_header': self._exported_methods.split_http_header, 98 | 'has_header': self._exported_methods.has_header, 99 | 'helpers': self._helpers, 100 | 'url': url, 101 | 'message_info': message_info, 102 | 'request_info': request_info, 103 | 'session': self._session, 104 | 'has_stopped': self._exported_methods.has_stopped, 105 | 'in_scope': in_scope, 106 | 'ref': self._ref 107 | } 108 | if tool_flag: 109 | globals["tool_flag"] = tool_flag 110 | if proxy_message_info: 111 | globals["proxy_message_info"] = proxy_message_info 112 | # Execute script 113 | exec(self.ide_pane.compiled_code, globals) 114 | # Reimport API variables 115 | self._session = globals['session'] 116 | self._ref += 1 117 | 118 | 119 | class HttpListenerModifier(ModifierTab, IHttpListener): 120 | """ 121 | Modifies requests and responses on the fly through the IHttpListener interface 122 | """ 123 | 124 | def __init__(self, **kwargs): 125 | ModifierTab.__init__(self, plugin_id=PluginType.http_listener_modifier, **kwargs) 126 | self.add(self._ide_pane) 127 | 128 | def processHttpMessage(self, tool_flag, is_request, message_info): 129 | """ 130 | This method is invoked when an HTTP request is about to be issued, and when an HTTP response has been received. 131 | :param tool_flag: Burp Suite tool that issued the request 132 | :param is_request: True or false depending on whether the provided message is a request or response. 133 | :param message_info: Contains the actual information in form of an IHttpRequestResponse instance. 134 | :return: 135 | """ 136 | try: 137 | if self._ide_pane.activated: 138 | self.process_proxy_history_entry(message_info, is_request, tool_flag) 139 | except: 140 | self._ide_pane.activated = False 141 | traceback.print_exc(file=self._callbacks.getStderr()) 142 | ErrorDialog.Show(self._extender.parent, traceback.format_exc()) 143 | 144 | 145 | class ProxyListenerModifier(ModifierTab, IProxyListener): 146 | """ 147 | Modifies requests and responses on the fly through the IProxyListener interface 148 | """ 149 | 150 | def __init__(self, **kwargs): 151 | ModifierTab.__init__(self, plugin_id=PluginType.proxy_listener_modifier, **kwargs) 152 | self.add(self._ide_pane) 153 | 154 | def processProxyMessage(self, is_request, message): 155 | """ 156 | This method is invoked when an HTTP request is about to be issued, and when an HTTP response has been received. 157 | :param is_request: True or false depending on whether the provided message is a request or response. 158 | :param message: Contains the actual information in form of an IHttpRequestResponse instance. 159 | :return: 160 | """ 161 | try: 162 | if self._ide_pane.activated: 163 | self.process_proxy_history_entry(message.getMessageInfo(), 164 | is_request, 165 | proxy_message_info=message) 166 | except: 167 | self._ide_pane.activated = False 168 | traceback.print_exc(file=self._callbacks.getStderr()) 169 | ErrorDialog.Show(self._extender.parent, traceback.format_exc()) 170 | -------------------------------------------------------------------------------- /turbodataminer/turbodataminer/ui/scoping/scopedialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This module implements the UI component to display scope dialogs. 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 | from burp import IParameter 26 | from javax.swing import JPanel 27 | from javax.swing import JButton 28 | from javax.swing import JDialog 29 | from javax.swing import JComboBox 30 | from javax.swing import JOptionPane 31 | from javax.swing import JScrollPane 32 | from java.awt import GridLayout 33 | from java.awt import BorderLayout 34 | from java.awt import Dimension 35 | from turbodataminer.ui.scoping.scopetable import ScopeTable 36 | 37 | 38 | class BaseScopeDialog(JDialog): 39 | """ 40 | This dialog implements all functionality to display a scope dialog. 41 | """ 42 | 43 | def __init__(self, owner, title="Check items to be processed", enable_filter=True): 44 | JDialog.__init__(self, owner, title) 45 | # Set the dialogs layout 46 | self._owner = owner 47 | self.setModal(True) 48 | self.setSize(800, 500) 49 | self.setMaximumSize(Dimension(800, 500)) 50 | self.setMinimumSize(Dimension(800, 500)) 51 | self.setLayout(BorderLayout()) 52 | self.windowClosing = self._cancel_action 53 | # Initializing the UI table 54 | self._scope_table = ScopeTable() 55 | self._scope_table.setPreferredSize(Dimension(600, 300)) 56 | # table of extracted entries 57 | scroll_pane = JScrollPane() 58 | scroll_pane.setViewportView(self._scope_table) 59 | self.add(scroll_pane, BorderLayout.PAGE_START) 60 | # add selection 61 | self._filter_option = JComboBox(["Test only selected items (whitelisting)", 62 | "Exclude all selected items from testing/processing (blacklisting)"]) 63 | self._filter_option.setEditable(False) 64 | self._filter_option.setEnabled(enable_filter) 65 | self._filter_option.setSelectedIndex(0) 66 | self._filter_option.setToolTipText("Specifies whether a whitelisting or blacklisting approach is " 67 | "be applied on the checked items in the table above.") 68 | self.add(self._filter_option, BorderLayout.CENTER) 69 | # Add Buttons 70 | button_panel = JPanel() 71 | button_panel.setLayout(GridLayout(1, 2)) 72 | b_save = JButton("Ok", actionPerformed=self._save_action) 73 | button_panel.add(b_save) 74 | b_cancel = JButton("Cancel", actionPerformed=self._cancel_action) 75 | button_panel.add(b_cancel) 76 | self.add(button_panel, BorderLayout.PAGE_END) 77 | # Initialize variables 78 | self.canceled = None 79 | self.filter_results = [] 80 | self.setLocationRelativeTo(owner) 81 | 82 | @property 83 | def data_model(self): 84 | return self._scope_table.data_model 85 | 86 | def display(self, header, content): 87 | """This method displays the scoping dialog.""" 88 | self._scope_table.set_model(header=header, rows=content) 89 | self.setVisible(True) 90 | self.pack() 91 | 92 | def _save_action(self, event): 93 | """ 94 | This method is invoked when the save button is clicked. 95 | """ 96 | if self._set_filters(): 97 | self.canceled = False 98 | self.setVisible(False) 99 | 100 | def _cancel_action(self, event): 101 | """ 102 | This method is invoked when the cancel button is clicked. 103 | """ 104 | self.canceled = True 105 | self.setVisible(False) 106 | 107 | @property 108 | def whitelisting(self): 109 | return self._filter_option.getSelectedIndex() == 0 110 | 111 | def _set_filters(self): 112 | """ 113 | This method is called by save_action to set the filter arrays. 114 | :return: 115 | """ 116 | self.filter_results = [] 117 | row_count = self.data_model.getRowCount() 118 | column_count = self.data_model.getColumnCount() 119 | for row_index in range(0, row_count): 120 | selected = self.data_model.getValueAt(row_index, 0) 121 | # Obtain current row values 122 | row = [] 123 | for column_index in range(1, column_count): 124 | value = self.data_model.getValueAt(row_index, column_index) 125 | row.append(value) 126 | # Add to corresponding filter 127 | if selected: 128 | self.filter_results.append(row) 129 | # Perform checks 130 | result = len(self.filter_results) != 0 131 | if not result: 132 | JOptionPane.showConfirmDialog(self._owner, 133 | "At least one item must be selected.", 134 | "Invalid input ...", 135 | JOptionPane.DEFAULT_OPTION) 136 | return result 137 | 138 | 139 | class ParameterScopeDialog(BaseScopeDialog): 140 | """ 141 | This dialog implements all functionality to display a scope dialog for the given IRequestInfo. 142 | """ 143 | 144 | def __init__(self, owner): 145 | BaseScopeDialog.__init__(self, owner, title="Check parameters to be processed...", enable_filter=False) 146 | 147 | @staticmethod 148 | def get_parameter_name(type): 149 | rvalue = None 150 | if type == IParameter.PARAM_URL: 151 | rvalue = "GET" 152 | elif type == IParameter.PARAM_BODY: 153 | rvalue = "POST" 154 | elif type == IParameter.PARAM_COOKIE: 155 | rvalue = "Cookie" 156 | elif type == IParameter.PARAM_XML: 157 | rvalue = "XML" 158 | elif type == IParameter.PARAM_XML_ATTR: 159 | rvalue = "XML Attr" 160 | elif type == IParameter.PARAM_MULTIPART_ATTR: 161 | rvalue = "Multipart Attr" 162 | elif type == IParameter.PARAM_JSON: 163 | rvalue = "JSON" 164 | return rvalue 165 | 166 | @staticmethod 167 | def get_parameter_type(type_name): 168 | """ 169 | This method returns the parameter type value based on the given descriptive parameter name of the given. 170 | This method is usually used to convert the value returned by get_parameter_name back to the IParameter type 171 | value (e.g., "GET" is IParameter.PARAM_URL, "POST" is IParameter.PARAM_BODY, etc.). 172 | 173 | :param type_name (str): The descriptive parameter name that shall be returned as integer. 174 | :return (str): The integer value that matches the given descriptive parameter name or None. 175 | """ 176 | if type_name == "GET": 177 | result = IParameter.PARAM_URL 178 | elif type_name == "POST": 179 | result = IParameter.PARAM_BODY 180 | elif type_name == "Cookie": 181 | result = IParameter.PARAM_COOKIE 182 | elif type_name == "XML": 183 | result = IParameter.PARAM_XML 184 | elif type_name == "XML Attr": 185 | result = IParameter.PARAM_XML_ATTR 186 | elif type_name == "Multipart Attr": 187 | result = IParameter.PARAM_MULTIPART_ATTR 188 | elif type_name == "JSON": 189 | result = IParameter.PARAM_JSON 190 | else: 191 | result = None 192 | return result 193 | 194 | def display(self, request_info): 195 | """This method displays the scoping dialog.""" 196 | parameters = [] 197 | for parameter in request_info.getParameters(): 198 | type_name = self.get_parameter_name(parameter.getType()) 199 | parameters.append([type_name, parameter.getName(), parameter.getValue()]) 200 | self._scope_table.set_model(header=["Type", "Name", "Example Value"], rows=parameters) 201 | self.setVisible(True) 202 | self.pack() 203 | 204 | def match(self, parameter): 205 | """ 206 | Returns True if the given IParameter is 207 | :param parameter: 208 | :return: 209 | """ 210 | result = False 211 | for include in self.filter_results: 212 | result = self.get_parameter_type(include[0]) == parameter.getType() and include[1] == parameter.getName() 213 | if result: 214 | break 215 | return result 216 | -------------------------------------------------------------------------------- /turbodataminer/turbodataminer/model/heatmap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This module implements heatmap functionality. 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 threading 26 | from java.awt import Color 27 | from java.lang import Float 28 | from java.lang import Double 29 | from java.lang import Integer 30 | from javax.swing.table import DefaultTableCellRenderer 31 | 32 | 33 | class PalletIndex: 34 | """ 35 | This class manages the minimal and maximal values of a heat map group. In addition, it allows the computation of 36 | the index within the pallet list. 37 | """ 38 | 39 | def __init__(self, min_value, max_value, column_names): 40 | self._value_lock = threading.Lock() 41 | self.column_names = column_names 42 | if min_value == max_value: 43 | self._min_value = 0 44 | self._max_value = 0 45 | self._normalized_max_value = None 46 | else: 47 | self._min_value = min_value 48 | self._max_value = max_value 49 | self._normalized_max_value = max_value - min_value 50 | 51 | def update_values(self, min_value, max_value): 52 | """ 53 | Implementing a setter for min_value and max_value did not work. Therefore, we had to implement this workaround. 54 | :param min_value: 55 | :param max_value: 56 | :return: 57 | """ 58 | if min_value == max_value: 59 | self._min_value = 0 60 | self._max_value = 0 61 | self._normalized_max_value = None 62 | else: 63 | self._min_value = min_value 64 | self._max_value = max_value 65 | self._normalized_max_value = max_value - min_value 66 | 67 | def reset(self): 68 | """ 69 | This method re-initializes the PalletIndex object. 70 | :return: 71 | """ 72 | self._min_value = None 73 | self._max_value = None 74 | self._normalized_max_value = None 75 | 76 | def update_value(self, current_value): 77 | """ 78 | This method compares the given value against the current min and max values and if necessary updates them 79 | accordingly. 80 | :param current_value: The value against which min/max values are compared. 81 | :return: True if the min/max value was updated. 82 | """ 83 | result = False 84 | with self._value_lock: 85 | if self._min_value is None and self._max_value is None: 86 | self._min_value = current_value 87 | self._max_value = current_value 88 | self._normalized_max_value = None 89 | elif current_value < self._min_value: 90 | self._min_value = current_value 91 | self._normalized_max_value = self._max_value - self._min_value \ 92 | if self._max_value > self._min_value else None 93 | result = True 94 | elif self._max_value < current_value: 95 | self._max_value = current_value 96 | self._normalized_max_value = self._max_value - self._min_value \ 97 | if self._max_value > self._min_value else None 98 | result = True 99 | return result 100 | 101 | def get_pallet_index(self, pallet_length, value): 102 | """ 103 | This method returns the index in the pallet for the given cell value. 104 | :param pallet_length: 105 | :param value: 106 | :return: 107 | """ 108 | result = None 109 | with self._value_lock: 110 | if self._normalized_max_value is not None: 111 | i = pallet_length * (value - self._min_value) / self._normalized_max_value 112 | result = int(min(max(i, 0), pallet_length - 1)) 113 | return result 114 | 115 | def __repr__(self): 116 | return "({}/{}/{})".format(self._min_value, self._max_value, self._normalized_max_value) 117 | 118 | 119 | class HeatMapMenuEntry: 120 | """ 121 | This class implements a single heat map menu entry. 122 | """ 123 | 124 | def __init__(self, column_name, class_type): 125 | self.column_name = column_name 126 | self.heat_map_groups = [] 127 | if class_type == Float or class_type == float: 128 | self.class_type = Float 129 | elif class_type == Double: 130 | self.class_type = Double 131 | elif class_type == Integer or class_type == int: 132 | self.class_type = Integer 133 | else: 134 | raise NotImplementedError("Case not implemented") 135 | 136 | 137 | class IntelTableCellRenderer(DefaultTableCellRenderer): 138 | """ 139 | This class implements a heat map for the UI table. 140 | """ 141 | 142 | def __init__(self, intel_table, pallet, pallet_indices): 143 | DefaultTableCellRenderer.__init__(self) 144 | self._intel_table = intel_table 145 | self._pallet = pallet 146 | self._pallet_length = len(self._pallet) 147 | self.pallet_indices_lock = threading.Lock() 148 | self.pallet_indices = pallet_indices 149 | self.pallet_indices_count = len(pallet_indices) 150 | 151 | def getTableCellRendererComponent(self, table, value, is_selected, has_focus, row, column): 152 | """This method is called by the UI table to calculate a row's background color.""" 153 | result = DefaultTableCellRenderer.getTableCellRendererComponent(self, table, value, is_selected, has_focus, row, column) 154 | with self.pallet_indices_lock: 155 | pallet_indices_count = self.pallet_indices_count 156 | if column < pallet_indices_count: 157 | with self.pallet_indices_lock: 158 | pallet_index = self.pallet_indices[column] 159 | if pallet_index: 160 | index = pallet_index.get_pallet_index(self._pallet_length, value) 161 | if index is not None: 162 | result.setBackground(self._pallet[index]) 163 | else: 164 | result.setBackground(None) 165 | elif not is_selected: 166 | result.setBackground(None) 167 | else: 168 | result.setBackground(None) 169 | return result 170 | 171 | def update_pallet_indices(self, value, column_index): 172 | """ 173 | This method updates the min/max values of the PalletIndex object located at column_index. 174 | :param value: The value based on which the min/max values of the PalletIndex object shall be updated. Note that 175 | this method assumes that but does not check whether the given value is numeric. 176 | :param column_index: The value's column index, which determines the PalletIndex object. 177 | :return: True if the min/max values of PalletIndex object were updated. 178 | """ 179 | result = False 180 | with self.pallet_indices_lock: 181 | if column_index < self.pallet_indices_count: 182 | pallet = self.pallet_indices[column_index] 183 | # Only if there is a PalletIndex object, then check whether the heat map's min/max values shall be 184 | # updated 185 | if pallet: 186 | result = pallet.update_value(value) 187 | else: 188 | self.pallet_indices.append(None) 189 | self.pallet_indices_count += 1 190 | return result 191 | 192 | def reset_pallet_indices(self): 193 | """ 194 | This method resets the min/max values of the PalletIndex objects. 195 | :return: 196 | """ 197 | with self.pallet_indices_lock: 198 | for pallet in self.pallet_indices: 199 | if pallet: 200 | pallet.reset() 201 | 202 | 203 | class IntelDefaultTableCellRenderer(DefaultTableCellRenderer): 204 | """ 205 | This class implements the default JTable background. This is necessary to ensure that the background stays the same 206 | independent from whether the heat map is active or not. 207 | """ 208 | 209 | def __init__(self): 210 | DefaultTableCellRenderer.__init__(self) 211 | 212 | def getTableCellRendererComponent(self, table, value, is_selected, has_focus, row, column): 213 | """This method is called by the UI table to calculate a row's background color.""" 214 | result = DefaultTableCellRenderer.getTableCellRendererComponent(self, table, value, is_selected, has_focus, row, column) 215 | if not is_selected: 216 | result.setBackground(None) 217 | return result 218 | 219 | def reset_pallet_indices(self): 220 | """ 221 | This method resets the min/max values of the PalletIndex objects. 222 | :return: 223 | """ 224 | pass 225 | -------------------------------------------------------------------------------- /turbodataminer/turbodataminer/model/intelligence.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This module implements the core functionality for JTable's data model. 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 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 TableRowEntry: 35 | 36 | def __init__(self, message_info): 37 | self._rows = [] 38 | self.message_info = message_info 39 | self.used = False 40 | 41 | @property 42 | def rows(self): 43 | return self._rows 44 | 45 | def add_table_row(self, row, message_infos=None): 46 | if isinstance(row, list): 47 | self._rows.append(IntelDataModelEntry(row, self.message_info, message_infos)) 48 | else: 49 | self._rows.append(IntelDataModelEntry([row], self.message_info, message_infos)) 50 | 51 | 52 | class IntelDataModelEntry: 53 | """ 54 | Represents a single row in the IntelDataModel class 55 | 56 | This class contains all information about a single table row. 57 | """ 58 | 59 | def __init__(self, elements, message_info=None, message_infos=None): 60 | """ 61 | :param elements: A list of items that were extracted from the request and/or response 62 | :param message_info: The IHttpRequestResponse from where the data elements were extracted 63 | :param message_infos: List of additional IHttpRequestResponse objects associated with the current row. 64 | """ 65 | self._elements = [] 66 | for item in elements: 67 | if isinstance(item, Integer) or \ 68 | isinstance(item, Float) or \ 69 | isinstance(item, Boolean) or \ 70 | isinstance(item, int) or \ 71 | isinstance(item, float) or \ 72 | isinstance(item, bool): 73 | self._elements.append(item) 74 | else: 75 | self._elements.append(unicode(item, encoding="utf-8", errors="ignore")) 76 | self._length = len(elements) 77 | self._message_info = message_info 78 | self._message_infos = message_infos if message_infos else {} 79 | 80 | @property 81 | def len(self): 82 | """Returns the number elements of the row""" 83 | return self._length 84 | 85 | @property 86 | def message_info(self): 87 | """Returns the IHttpRequestResponse object from which the information was extracted""" 88 | return self._message_info 89 | 90 | @property 91 | def message_infos(self): 92 | """Returns the list of additional IHttpRequestResponse objects that are associated with the row""" 93 | return self._message_infos 94 | 95 | @property 96 | def elements(self): 97 | """Returns the content of the row in form of a list""" 98 | return self._elements 99 | 100 | def get_value_at(self, i): 101 | """Returns the element at position i""" 102 | if i >= self._length: 103 | return None 104 | return self._elements[i] 105 | 106 | def is_numeric(self, i): 107 | """Returns true if the column type at column_index is numeric""" 108 | data_type = self.get_type_at(i) 109 | return data_type == Integer or data_type == Float or data_type == int or data_type == float 110 | 111 | def get_type_at(self, i): 112 | """Returns the element type at position i""" 113 | return_value = String 114 | value = self.get_value_at(i) 115 | if isinstance(value, bool): 116 | return_value = Boolean 117 | elif isinstance(value, float): 118 | return_value = Float 119 | elif isinstance(value, int): 120 | return_value = Integer 121 | elif isinstance(value, time): 122 | return_value = Date 123 | return return_value 124 | 125 | 126 | class IntelDataModel(AbstractTableModel): 127 | """ 128 | The data model used by class IntelTable 129 | 130 | This class implements the data model to display information in the IntelTable. This class maintains a list of rows. 131 | Each row is represented by the class IntelDataModelEntry. 132 | """ 133 | 134 | def __init__(self): 135 | self._header = [] 136 | self._content = [] 137 | self._column_count = 0 138 | self._row_count = 0 139 | 140 | def set_header(self, header, reset_column_count=False): 141 | """ 142 | Method used to set the header of the data model 143 | :param header: List of header elements 144 | :return: Returns true 145 | """ 146 | if not isinstance(header, list): 147 | raise ValueError("Variable 'header' must be a list!") 148 | 149 | self._header = header 150 | count = len(header) 151 | if self._column_count <= count: 152 | self._column_count = count 153 | elif reset_column_count: 154 | self._column_count = count 155 | self.fireTableStructureChanged() 156 | return True 157 | 158 | def get_header(self): 159 | return self._header 160 | 161 | def clear_data(self): 162 | if self._row_count != 0: 163 | self._content = [] 164 | old_row_count = self._row_count 165 | self._row_count = 0 166 | self.fireTableRowsDeleted(0, old_row_count - 1) 167 | 168 | def add_rows(self, entries): 169 | """ 170 | Method used to add a new row to the data model 171 | 172 | :param entries: A list of IntelDataModelEntries 173 | """ 174 | if isinstance(entries, TableRowEntry): 175 | table_rows = entries.rows 176 | elif isinstance(entries, list): 177 | table_rows = entries 178 | else: 179 | raise ValueError("Variable 'entries' must be a list!") 180 | 181 | rows = 0 182 | if table_rows: 183 | for entry in table_rows: 184 | if self._column_count < entry.len: 185 | self._column_count = entry.len 186 | self.fireTableStructureChanged() 187 | self._content.append(entry) 188 | rows = rows + 1 189 | 190 | old_row_count = self._row_count 191 | self._row_count = self._row_count + rows 192 | if rows > 0: 193 | self.fireTableRowsInserted(old_row_count, self._row_count - 1) 194 | 195 | def delete_row(self, row_index): 196 | if row_index < self._row_count: 197 | del self._content[row_index] 198 | self._row_count = self._row_count - 1 199 | self.fireTableRowsDeleted(row_index, row_index) 200 | 201 | def get_message_info_at(self, row_index): 202 | """Returns the message info at row_index""" 203 | return self._content[row_index].message_info 204 | 205 | def get_message_infos_at(self, row_index): 206 | """Returns the message infos at row_index""" 207 | return self._content[row_index].message_infos 208 | 209 | def getRowCount(self): 210 | """Returns the total number of rows managed by the data model""" 211 | try: 212 | return self._row_count 213 | except: 214 | print(traceback.format_exc()) 215 | return 0 216 | 217 | def getColumnCount(self): 218 | """Returns the total number of columns managemed by the data model""" 219 | try: 220 | return self._column_count 221 | except: 222 | print(traceback.format_exc()) 223 | return 0 224 | 225 | def getColumnName(self, column_index): 226 | """Returns the column name at position column_index""" 227 | try: 228 | if column_index < len(self._header): 229 | return self._header[column_index] 230 | return None 231 | except: 232 | print(traceback.format_exc()) 233 | return None 234 | 235 | def getValueAt(self, row_index, column_index): 236 | """Returns the element at row row_index and column column_index""" 237 | try: 238 | row = self._content[row_index] 239 | if column_index < row.len: 240 | return row.get_value_at(column_index) 241 | except: 242 | print(traceback.format_exc()) 243 | return None 244 | 245 | def is_numeric(self, column_index): 246 | """Returns true if the column type at column_index is numeric""" 247 | data_type = self.getColumnClass(column_index) 248 | return data_type == Integer or data_type == Float or data_type == int or data_type == float 249 | 250 | def get_min_max_values(self, column_names): 251 | """ 252 | Returns the smallest and largest value over the given volumn_names 253 | :param column_names: List of columns over which the smallest and largest value should be found 254 | :return: List with two elements. The first element contains the smallest and the second element the largest 255 | value 256 | """ 257 | min_value = 0 258 | max_value = 0 259 | first = True 260 | for column_name in column_names: 261 | index = self._header.index(column_name) 262 | if index >= 0: 263 | for item in self._content: 264 | column_value = item.get_value_at(index) 265 | if item.is_numeric(index): 266 | if first: 267 | first = False 268 | min_value = column_value 269 | max_value = column_value 270 | if min_value > column_value: 271 | min_value = column_value 272 | if max_value < column_value: 273 | max_value = column_value 274 | return [min_value, max_value] 275 | 276 | def getColumnClass(self, column_index): 277 | """Returns the column type at column_index""" 278 | try: 279 | if self._row_count >= 1: 280 | row = self._content[0] 281 | return row.get_type_at(column_index) 282 | except: 283 | print(traceback.format_exc()) 284 | return String 285 | -------------------------------------------------------------------------------- /turbodataminer/data/file-signatures.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "https://en.wikipedia.org/wiki/List_of_file_signatures", 3 | "signatures": [ 4 | { 5 | "extensions": [ 6 | "ico" 7 | ], 8 | "category": "Image", 9 | "description": "Computer icon encoded in ICO file format", 10 | "offset": 0, 11 | "hex_signatures": [ 12 | "\\x00\\x00\\x01\\x00" 13 | ], 14 | "str_signatures": [], 15 | "b64_signatures": [] 16 | }, 17 | { 18 | "extensions": [ 19 | "z", 20 | "tar.z" 21 | ], 22 | "category": "Archive", 23 | "description": "compressed file (often tar zip) using Lempel-Ziv-Welch algorithm", 24 | "offset": 0, 25 | "hex_signatures": [ 26 | "\\x1f\\x9d" 27 | ], 28 | "str_signatures": [], 29 | "b64_signatures": [] 30 | }, 31 | { 32 | "extensions": [ 33 | "z", 34 | "tar.z" 35 | ], 36 | "category": "Archive", 37 | "description": "compressed file (often tar zip) using LZH algorithm", 38 | "offset": 0, 39 | "hex_signatures": [ 40 | "\\x1f\\xa0" 41 | ], 42 | "str_signatures": [], 43 | "b64_signatures": [] 44 | }, 45 | { 46 | "extensions": [ 47 | "bz2" 48 | ], 49 | "category": "Archive", 50 | "description": "Compressed file using Bzip2 algorithm", 51 | "offset": 0, 52 | "hex_signatures": [ 53 | "\\x42\\x5a\\x68" 54 | ], 55 | "str_signatures": [ 56 | "BZh" 57 | ], 58 | "b64_signatures": [] 59 | }, 60 | { 61 | "extensions": [ 62 | "gif" 63 | ], 64 | "category": "Image", 65 | "description": "Image file encoded in the Graphics Interchange Format (GIF)", 66 | "offset": 0, 67 | "hex_signatures": [ 68 | "\\x47\\x49\\x46\\x38\\x37\\x61", 69 | "\\x47\\x49\\x46\\x38\\x39\\x61" 70 | ], 71 | "str_signatures": [ 72 | "GIF87a", 73 | "GIF89a" 74 | ], 75 | "b64_signatures": [] 76 | }, 77 | { 78 | "extensions": [ 79 | "tif", 80 | "tiff" 81 | ], 82 | "category": "Image", 83 | "description": "Tagged Image File Format", 84 | "offset": 0, 85 | "hex_signatures": [ 86 | "\\x49\\x49\\x2A\\x00", 87 | "\\x4D4D\\x00\\x2A" 88 | ], 89 | "str_signatures": [ 90 | "II\\*.", 91 | "MM.\\*" 92 | ], 93 | "b64_signatures": [] 94 | }, 95 | { 96 | "extensions": [ 97 | "jpg", 98 | "jpeg" 99 | ], 100 | "category": "Image", 101 | "description": " JPEG raw or in the JFIF or Exif file format", 102 | "offset": 0, 103 | "hex_signatures": [ 104 | "\\xFF\\xD8\\xFF\\xDB", 105 | "\\xFF\\xD8\\xFF\\xE0..4A\\x46\\x49\\x46\\x00\\x01", 106 | "\\xFF\\xD8\\xFF\\xE1..45\\x78\\x69\\x66\\x00\\x00" 107 | ], 108 | "str_signatures": [], 109 | "b64_signatures": [] 110 | }, 111 | { 112 | "extensions": [ 113 | "exe" 114 | ], 115 | "category": "Executable", 116 | "description": "DOS MZ executable file format and its descendants (including NE and PE)", 117 | "offset": 0, 118 | "hex_signatures": [ 119 | "\\x4D\\x5A" 120 | ], 121 | "str_signatures": [ 122 | "MZ" 123 | ], 124 | "b64_signatures": [] 125 | }, 126 | { 127 | "extensions": [ 128 | "lz" 129 | ], 130 | "category": "Archive", 131 | "description": "lzip compressed file", 132 | "offset": 0, 133 | "hex_signatures": [ 134 | "\\x4C\\x5A\\x49\\x50" 135 | ], 136 | "str_signatures": [ 137 | "LZIP" 138 | ], 139 | "b64_signatures": [] 140 | }, 141 | { 142 | "extensions": [ 143 | "zip", 144 | "jar", 145 | "odt", 146 | "ods", 147 | "odp", 148 | "docx", 149 | "xlsx", 150 | "pptx", 151 | "vsdx", 152 | "apk", 153 | "aar" 154 | ], 155 | "category": "Archive", 156 | "description": "zip file format and formats based on it, such as JAR, ODF, OOXML", 157 | "offset": 0, 158 | "hex_signatures": [ 159 | "\\x50\\x4B\\x03\\x04", 160 | "\\x50\\x4B\\x05\\x06", 161 | "\\x50\\x4B\\x07\\x08" 162 | ], 163 | "str_signatures": [ 164 | "PK" 165 | ], 166 | "b64_signatures": [] 167 | }, 168 | { 169 | "extensions": [ 170 | "rar" 171 | ], 172 | "category": "Archive", 173 | "description": "RAR archive", 174 | "offset": 0, 175 | "hex_signatures": [ 176 | "\\x52\\x61\\x72\\x21\\x1A\\x07\\x00", 177 | "\\x52\\x61\\x72\\x21\\x1A\\x07\\x01\\x00" 178 | ], 179 | "str_signatures": [ 180 | "Rar!" 181 | ], 182 | "b64_signatures": [] 183 | }, 184 | { 185 | "extensions": [ 186 | "elf" 187 | ], 188 | "category": "Executable", 189 | "description": "Executable and Linkable Format", 190 | "offset": 0, 191 | "hex_signatures": [ 192 | "\\x7F\\x45\\x4C\\x46" 193 | ], 194 | "str_signatures": [ 195 | ".ELF" 196 | ], 197 | "b64_signatures": [] 198 | }, 199 | { 200 | "extensions": [ 201 | "png" 202 | ], 203 | "category": "Image", 204 | "description": "Image encoded in the Portable Network Graphics format", 205 | "offset": 0, 206 | "hex_signatures": [ 207 | "\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A" 208 | ], 209 | "str_signatures": [ 210 | ".PNG...." 211 | ], 212 | "b64_signatures": [] 213 | }, 214 | { 215 | "extensions": [ 216 | "class" 217 | ], 218 | "category": "Executable", 219 | "description": "Java class file, Mach-O Fat Binary", 220 | "offset": 0, 221 | "hex_signatures": [ 222 | "\\xCA\\xFE\\xBA\\xBE" 223 | ], 224 | "str_signatures": [], 225 | "b64_signatures": [] 226 | }, 227 | { 228 | "extensions": [ 229 | "ps" 230 | ], 231 | "category": "Document", 232 | "description": "PostScript document", 233 | "offset": 0, 234 | "hex_signatures": [ 235 | "\\x25\\x21\\x50\\x53" 236 | ], 237 | "str_signatures": [ 238 | "%!PS" 239 | ], 240 | "b64_signatures": [] 241 | }, 242 | { 243 | "extensions": [ 244 | "pdf" 245 | ], 246 | "category": "Document", 247 | "description": "PDF document", 248 | "offset": 0, 249 | "hex_signatures": [ 250 | "\\x25\\x50\\x44\\x46" 251 | ], 252 | "str_signatures": [ 253 | "%PDF" 254 | ], 255 | "b64_signatures": [] 256 | }, 257 | { 258 | "extensions": [ 259 | "bmp" 260 | ], 261 | "category": "Image", 262 | "description": "BMP file, a bitmap format used mostly in the Windows world", 263 | "offset": 0, 264 | "hex_signatures": [ 265 | "\\x42\\x4D" 266 | ], 267 | "str_signatures": [ 268 | "BM" 269 | ], 270 | "b64_signatures": [] 271 | }, 272 | { 273 | "extensions": [ 274 | "doc", 275 | "xls", 276 | "ppt", 277 | "msg" 278 | ], 279 | "category": "Document", 280 | "description": "Compound File Binary Format, a container format used for document by older versions of Microsoft Office. It is however an open format used by other programs as well.", 281 | "offset": 0, 282 | "hex_signatures": [ 283 | "\\xD0\\xCF\\x11\\xE0\\xA1\\xB1\\x1A\\xE1" 284 | ], 285 | "str_signatures": [], 286 | "b64_signatures": [] 287 | }, 288 | { 289 | "extensions": [ 290 | "xar" 291 | ], 292 | "category": "Archive", 293 | "description": "eXtensible ARchive format", 294 | "offset": 0, 295 | "hex_signatures": [ 296 | "\\x78\\x61\\x72\\x21" 297 | ], 298 | "str_signatures": [ 299 | "xrar!" 300 | ], 301 | "b64_signatures": [] 302 | }, 303 | { 304 | "extensions": [ 305 | "tar" 306 | ], 307 | "category": "Archive", 308 | "description": "", 309 | "offset": 257, 310 | "hex_signatures": [ 311 | "\\x75\\x73\\x74\\x61\\x72\\x00\\x30\\x30", 312 | "\\x75\\x73\\x74\\x61\\x72\\x20\\x20\\x00" 313 | ], 314 | "str_signatures": [ 315 | "ustar.00", 316 | "ustar ." 317 | ], 318 | "b64_signatures": [] 319 | }, 320 | { 321 | "extensions": [ 322 | "7z" 323 | ], 324 | "category": "Archive", 325 | "description": "7-Zip File Format", 326 | "offset": 0, 327 | "hex_signatures": [ 328 | "\\x37\\x7A\\xBC\\xAF\\x27\\x1C" 329 | ], 330 | "str_signatures": [], 331 | "b64_signatures": [] 332 | }, 333 | { 334 | "extensions": [ 335 | "gz", 336 | "tar.gz" 337 | ], 338 | "category": "Archive", 339 | "description": "GZIP", 340 | "offset": 0, 341 | "hex_signatures": [ 342 | "\\x1F\\x8B" 343 | ], 344 | "str_signatures": [], 345 | "b64_signatures": [] 346 | }, 347 | { 348 | "extensions": [ 349 | "xz", 350 | "tar.xz" 351 | ], 352 | "category": "Archive", 353 | "description": "XZ compression utility using LZMA/LZMA2 compression", 354 | "offset": 0, 355 | "hex_signatures": [ 356 | "\\xFD\\x37\\x7A\\x58\\x5A\\x00\\x00" 357 | ], 358 | "str_signatures": [], 359 | "b64_signatures": [] 360 | }, 361 | { 362 | "extensions": [ 363 | "der" 364 | ], 365 | "category": "Certificate", 366 | "description": "DER encoded X.509 certificate", 367 | "offset": 0, 368 | "hex_signatures": [ 369 | "\\x30\\x82" 370 | ], 371 | "str_signatures": [], 372 | "b64_signatures": [] 373 | }, 374 | { 375 | "extensions": [ 376 | "xml" 377 | ], 378 | "category": "Document", 379 | "description": "eXtensible Markup Language when using the ASCII character encoding", 380 | "offset": 0, 381 | "hex_signatures": [ 382 | "\\x3c\\x3f\\x78\\x6d\\x6c\\x20" 383 | ], 384 | "str_signatures": [ 385 | ". 22 | """ 23 | __version__ = 1.0 24 | 25 | import traceback 26 | from threading import Lock 27 | from burp import IScanIssue 28 | from burp import IScannerCheck 29 | from turbodataminer.model.scripting import PluginType 30 | from turbodataminer.model.scripting import PluginCategory 31 | from turbodataminer.ui.core.intelbase import IntelBase 32 | from turbodataminer.ui.core.scripting import ErrorDialog 33 | 34 | 35 | class ScanIssue(IScanIssue): 36 | """ 37 | This class is derived from IScanIssue and can be used by Custom Scanner Check plugins to store and register IScanIssues. 38 | """ 39 | def __init__(self, 40 | http_service, 41 | url, 42 | message_infos, 43 | name, 44 | detail, 45 | severity, 46 | type=0, 47 | confidence="Certain", 48 | background=None, 49 | remediation=None): 50 | self._http_service = http_service 51 | self._url = url 52 | self._message_infos = message_infos 53 | self._name = name 54 | self._detail = detail 55 | self._severity = severity 56 | self._type = type 57 | self._confidence = confidence 58 | self._background = background 59 | self._remediation = remediation 60 | 61 | def getUrl(self): 62 | return self._url 63 | 64 | def getIssueName(self): 65 | return self._name 66 | 67 | def getIssueType(self): 68 | return self._type 69 | 70 | def getSeverity(self): 71 | return self._severity 72 | 73 | def getConfidence(self): 74 | return self._confidence 75 | 76 | def getIssueBackground(self): 77 | return self._background 78 | 79 | def getRemediationBackground(self): 80 | return self._remediation 81 | 82 | def getIssueDetail(self): 83 | return self._detail 84 | 85 | def getRemediationDetail(self): 86 | return self._remediation 87 | 88 | def getHttpMessages(self): 89 | return self._message_infos 90 | 91 | def getHttpService(self): 92 | return self._http_service 93 | 94 | 95 | class CustomScannerCheckTab(IntelBase, IScannerCheck): 96 | """ 97 | This class is used by the CustomTextEditorImplementation class. It implements the logic to write, compile, and 98 | integrate the code into Burp Suite's message editor tab. 99 | """ 100 | 101 | POST_CODE = """_do_passive_scan = do_passive_scan 102 | _do_active_scan = do_active_scan 103 | _consolidate_duplicate_issues = consolidate_duplicate_issues""" 104 | 105 | def __init__(self, **kwargs): 106 | IntelBase.__init__(self, 107 | plugin_category_id=PluginCategory.scan, 108 | executable_on_startup=True, 109 | plugin_id=PluginType.scanner_check, 110 | post_code=CustomScannerCheckTab.POST_CODE, 111 | **kwargs) 112 | self._do_passive_scan_lock = Lock() 113 | self._do_passive_scan = None 114 | self._do_active_scan_lock = Lock() 115 | self._do_active_scan = None 116 | self._consolidate_duplicate_issues_lock = Lock() 117 | self._consolidate_duplicate_issues = None 118 | self._session_lock = Lock() 119 | self._session = {} 120 | self.add(self._ide_pane) 121 | 122 | def start_analysis(self): 123 | try: 124 | # Setup API 125 | self.session = {} 126 | globals = { 127 | 'callbacks': self._extender.callbacks, 128 | 'xerceslib': self._extender.xerces_classloader, 129 | 'plugin_id': self._plugin_id, 130 | 'get_json_attributes': self._exported_methods.get_json_attributes, 131 | 'get_json_attribute_by_path': self._exported_methods.get_json_attribute_by_path, 132 | 'get_headers': self._exported_methods.get_headers, 133 | 'get_parameters': self._exported_methods.get_parameters, 134 | 'get_parameter_name': self._exported_methods.get_parameter_name, 135 | 'get_header': self._exported_methods.get_header, 136 | 'get_cookies': self._exported_methods.get_cookies, 137 | 'get_cookie_attributes': self._exported_methods.get_cookie_attributes, 138 | 'get_hostname': self._exported_methods.get_hostname, 139 | 'compress_gzip': self._exported_methods.compress_gzip, 140 | 'decompress_gzip': self._exported_methods.decompress_gzip, 141 | 'get_content_length': self._exported_methods.get_content_length, 142 | 'get_content_type': self._exported_methods.get_content_type, 143 | 'analyze_signatures': self._exported_methods.analyze_signatures, 144 | 'find_error_messages': self._exported_methods.find_error_messages, 145 | 'get_extension_info': self._exported_methods.get_extension_info, 146 | 'decode_html': self._exported_methods.decode_html, 147 | 'url_decode': self._exported_methods.url_decode, 148 | 'analyze_request': self._exported_methods.analyze_request, 149 | 'analyze_response': self._exported_methods.analyze_response, 150 | 'find_versions': self._exported_methods.find_versions, 151 | 'find_domains': self._exported_methods.find_domains, 152 | 'get_jwt': self._exported_methods.get_jwt, 153 | 'decode_jwt': self._exported_methods.decode_jwt, 154 | 'encode_jwt': self._exported_methods.encode_jwt, 155 | 'send_http_message': self._exported_methods.send_http_message, 156 | 'split_http_header': self._exported_methods.split_http_header, 157 | 'has_header': self._exported_methods.has_header, 158 | '_do_passive_scan': self._do_passive_scan, 159 | '_do_active_scan': self._do_active_scan, 160 | '_consolidate_duplicate_issues': self._consolidate_duplicate_issues, 161 | 'helpers': self._helpers, 162 | 'ScanIssue': ScanIssue 163 | } 164 | # Execute script 165 | exec(self.ide_pane.compiled_code, globals) 166 | # Reimport API method implementations 167 | self.do_passive_scan = globals['_do_passive_scan'] 168 | self.do_active_scan = globals['_do_active_scan'] 169 | self.consolidate_duplicate_issues = globals['_consolidate_duplicate_issues'] 170 | # Register IScannerCheck 171 | self._extender.callbacks.registerScannerCheck(self) 172 | except: 173 | self._ide_pane.activated = False 174 | self.stop_analysis() 175 | traceback.print_exc(file=self._extender.callbacks.getStdout()) 176 | ErrorDialog.Show(self._extender.parent, traceback.format_exc()) 177 | 178 | def stop_analysis(self): 179 | # Unregister IScannerCheck 180 | self._extender.callbacks.removeScannerCheck(self) 181 | # Delete current method definition 182 | self.do_passive_scan = None 183 | self.do_active_scan = None 184 | self.consolidate_duplicate_issues = None 185 | self.session = {} 186 | 187 | @property 188 | def do_passive_scan(self): 189 | with self._do_passive_scan_lock: 190 | result = self._do_passive_scan 191 | return result 192 | 193 | @do_passive_scan.setter 194 | def do_passive_scan(self, value): 195 | with self._do_passive_scan_lock: 196 | self._do_passive_scan = value 197 | 198 | @property 199 | def do_active_scan(self): 200 | with self._do_active_scan_lock: 201 | result = self._do_active_scan 202 | return result 203 | 204 | @do_active_scan.setter 205 | def do_active_scan(self, value): 206 | with self._do_active_scan_lock: 207 | self._do_active_scan = value 208 | 209 | @property 210 | def consolidate_duplicate_issues(self): 211 | with self._consolidate_duplicate_issues_lock: 212 | result = self._consolidate_duplicate_issues 213 | return result 214 | 215 | @consolidate_duplicate_issues.setter 216 | def consolidate_duplicate_issues(self, value): 217 | with self._consolidate_duplicate_issues_lock: 218 | self._consolidate_duplicate_issues = value 219 | 220 | @property 221 | def session(self): 222 | with self._session_lock: 223 | result = self._session 224 | return result 225 | 226 | @session.setter 227 | def session(self, value): 228 | with self._session_lock: 229 | self._session = value 230 | 231 | def process_proxy_history_entry(self, message_info, is_request=False, tool_flag=None, send_date=None, 232 | received_date=None, listener_interface=None, client_ip_address=None, 233 | timedout=None, message_reference=None, proxy_message_info=None, time_delta=None, 234 | in_scope=None, communication_manager=None, invocation=None): 235 | pass 236 | 237 | def doPassiveScan(self, message_info): 238 | result = [] 239 | try: 240 | if self.do_passive_scan: 241 | result = self.do_passive_scan(message_info, self.session) 242 | except: 243 | self._ide_pane.activated = False 244 | self.stop_analysis() 245 | traceback.print_exc(file=self._extender.callbacks.getStderr()) 246 | ErrorDialog.Show(self._extender.parent, traceback.format_exc()) 247 | return result 248 | 249 | def doActiveScan(self, message_info, insertion_point): 250 | result = [] 251 | try: 252 | if self.do_active_scan: 253 | result = self.do_active_scan(message_info, insertion_point, self.session) 254 | except: 255 | self._ide_pane.activated = False 256 | self.stop_analysis() 257 | traceback.print_exc(file=self._extender.callbacks.getStderr()) 258 | ErrorDialog.Show(self._extender.parent, traceback.format_exc()) 259 | return result 260 | 261 | def consolidateDuplicateIssues(self, existing_issue, new_issue): 262 | result = -1 263 | try: 264 | if self.consolidate_duplicate_issues: 265 | result = self.consolidate_duplicate_issues(existing_issue, new_issue) 266 | except: 267 | self._ide_pane.activated = False 268 | self.stop_analysis() 269 | traceback.print_exc(file=self._extender.callbacks.getStderr()) 270 | ErrorDialog.Show(self._extender.parent, traceback.format_exc()) 271 | return result 272 | -------------------------------------------------------------------------------- /turbodataminer/turbodataminer/ui/custommessage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This module implements all functionalities for all custom message GUIs. 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 traceback 26 | from threading import Lock 27 | from burp import IMessageEditorTab 28 | from burp import IMessageEditorTabFactory 29 | from turbodataminer.model.scripting import PluginType 30 | from turbodataminer.model.scripting import PluginCategory 31 | from turbodataminer.ui.core.intelbase import IntelBase 32 | from turbodataminer.ui.core.scripting import ErrorDialog 33 | 34 | 35 | class CustomMessageEditorTabBase(IntelBase): 36 | """ 37 | This class implements the GUI and base class for on the fly modifications. 38 | """ 39 | 40 | def __init__(self, pre_code=None, post_code=None, **kwargs): 41 | IntelBase.__init__(self, 42 | plugin_category_id=PluginCategory.custom_message_editor, 43 | pre_code=pre_code, 44 | post_code=post_code, 45 | **kwargs) 46 | 47 | 48 | class CustomMessageEditorBase(IMessageEditorTab): 49 | """ 50 | This class implements the base functionalities for the custom editors 51 | """ 52 | 53 | def __init__(self, custom_editor_tab, controller, editable): 54 | """ 55 | Initializes the IMessageEditorTab instance. This constructor is called by 56 | CustomMessageEditorTab.createNewInstance 57 | :param custom_editor_tab: The CustomMessageEditorTab that is displayed in the Others tab. This 58 | object provides access to all necessary information like extender. 59 | :param controller: Provided by IMessageEditorTabFactory 60 | :param editable: Provided by IMessageEditorTabFactory 61 | """ 62 | self._custom_editor_tab = custom_editor_tab 63 | self._extender = custom_editor_tab.extender 64 | self._controller = controller 65 | self._editable = editable 66 | self._current_message = None 67 | 68 | def isEnabled(self, content, is_request): 69 | rvalue = False 70 | try: 71 | if self._custom_editor_tab.is_enabled: 72 | rvalue = self._custom_editor_tab.is_enabled(content, 73 | is_request, 74 | self._custom_editor_tab.session) 75 | except: 76 | self._custom_editor_tab.ide_pane.activated = False 77 | self._custom_editor_tab.stop_analysis() 78 | traceback.print_exc(file=self._extender.callbacks.getStderr()) 79 | ErrorDialog.Show(self._extender.parent, traceback.format_exc()) 80 | return rvalue 81 | 82 | def setMessage(self, content, is_request): 83 | try: 84 | if self._custom_editor_tab.set_message: 85 | self._current_message = self._custom_editor_tab.set_message(content, 86 | is_request, 87 | self._custom_editor_tab.session) 88 | self._set_message(self._current_message, is_request, self._editable) 89 | else: 90 | self._set_message("", is_request, False) 91 | except: 92 | self._custom_editor_tab.ide_pane.activated = False 93 | self._custom_editor_tab.stop_analysis() 94 | traceback.print_exc(file=self._extender.callbacks.getStderr()) 95 | ErrorDialog.Show(self._extender.parent, traceback.format_exc()) 96 | # clear our display 97 | self._set_message("", is_request, False) 98 | 99 | def getMessage(self): 100 | try: 101 | if self._custom_editor_tab.get_message: 102 | text = self._get_message() 103 | return self._custom_editor_tab.get_message(text, self._custom_editor_tab.session) 104 | else: 105 | return None 106 | except: 107 | self._custom_editor_tab.ide_pane.activated = False 108 | self._custom_editor_tab.stop_analysis() 109 | traceback.print_exc(file=self._extender.callbacks.getStderr()) 110 | ErrorDialog.Show(self._extender.parent, traceback.format_exc()) 111 | return None 112 | 113 | def getTabCaption(self): 114 | raise NotImplementedError("Not implemented yet!") 115 | 116 | def getUiComponent(self): 117 | raise NotImplementedError("Not implemented yet!") 118 | 119 | def _set_message(self, content, is_request, editable): 120 | raise NotImplementedError("Not implemented yet!") 121 | 122 | def _get_message(self): 123 | raise NotImplementedError("Not implemented yet!") 124 | 125 | def isModified(self): 126 | raise NotImplementedError("Not implemented yet!") 127 | 128 | def getSelectedData(self): 129 | raise NotImplementedError("Not implemented yet!") 130 | 131 | 132 | class CustomTextEditorImplementation(CustomMessageEditorBase): 133 | """ 134 | This class implements Burp Suite's interface IMessageEditorTab to add a custom text editor tab in the Burp Suite 135 | GUI. Internally, this class uses class CustomMessageEditorTab to allow the management of custom editors in the 136 | Turbo Data Miner extension. 137 | """ 138 | 139 | def __init__(self, editable, **kwargs): 140 | CustomMessageEditorBase.__init__(self, editable=editable, **kwargs) 141 | # create an instance of Burp Suite's text editor, to display our deserialized data 142 | self._text_editor = self._extender.callbacks.createTextEditor() 143 | self._text_editor.setEditable(editable) 144 | 145 | def getTabCaption(self): 146 | result = "Turbo Miner" 147 | try: 148 | closable_tabbed_pane = self._custom_editor_tab.closable_tabbed_pane 149 | if closable_tabbed_pane: 150 | tab_index = closable_tabbed_pane.indexOfComponent(self._custom_editor_tab) 151 | if tab_index >= 0: 152 | tab_component = closable_tabbed_pane.getTabComponentAt(tab_index) 153 | result = tab_component.get_title() 154 | except: 155 | traceback.print_exc(file=self._extender.callbacks.getStdout()) 156 | return result 157 | 158 | def getUiComponent(self): 159 | return self._text_editor.getComponent() 160 | 161 | def _set_message(self, content, is_request, editable): 162 | self._text_editor.setText(content) 163 | self._text_editor.setEditable(editable) 164 | 165 | def _get_message(self): 166 | return self._text_editor.getText() 167 | 168 | def isModified(self): 169 | return self._text_editor.isTextModified() 170 | 171 | def getSelectedData(self): 172 | return self._text_editor.getSelectedText() 173 | 174 | 175 | class CustomMessageEditorTab(CustomMessageEditorTabBase, IMessageEditorTabFactory): 176 | """ 177 | This class is used by the CustomTextEditorImplementation class. It implements the logic to write, compile, and 178 | integrate the code into Burp Suite's message editor tab. 179 | """ 180 | 181 | POST_CODE = """_set_message = set_message 182 | _get_message = get_message 183 | _is_enabled = is_enabled""" 184 | 185 | def __init__(self, **kwargs): 186 | CustomMessageEditorTabBase.__init__(self, 187 | executable_on_startup=True, 188 | plugin_id=PluginType.custom_message_editor, 189 | post_code=CustomMessageEditorTab.POST_CODE, 190 | **kwargs) 191 | self._is_enabled_lock = Lock() 192 | self._is_enabled = None 193 | self._set_message_lock = Lock() 194 | self._set_message = None 195 | self._get_message_lock = Lock() 196 | self._get_message = None 197 | self._session_lock = Lock() 198 | self._session = {} 199 | self.add(self._ide_pane) 200 | 201 | def start_analysis(self): 202 | try: 203 | # Setup API 204 | self.session = {} 205 | globals = { 206 | 'callbacks': self._extender.callbacks, 207 | 'xerceslib': self._extender.xerces_classloader, 208 | 'plugin_id': self._plugin_id, 209 | 'get_json_attributes': self._exported_methods.get_json_attributes, 210 | 'get_json_attribute_by_path': self._exported_methods.get_json_attribute_by_path, 211 | 'get_headers': self._exported_methods.get_headers, 212 | 'get_parameters': self._exported_methods.get_parameters, 213 | 'get_parameter_name': self._exported_methods.get_parameter_name, 214 | 'get_header': self._exported_methods.get_header, 215 | 'get_cookies': self._exported_methods.get_cookies, 216 | 'get_cookie_attributes': self._exported_methods.get_cookie_attributes, 217 | 'get_hostname': self._exported_methods.get_hostname, 218 | 'compress_gzip': self._exported_methods.compress_gzip, 219 | 'decompress_gzip': self._exported_methods.decompress_gzip, 220 | 'get_content_length': self._exported_methods.get_content_length, 221 | 'get_content_type': self._exported_methods.get_content_type, 222 | 'analyze_signatures': self._exported_methods.analyze_signatures, 223 | 'find_error_messages': self._exported_methods.find_error_messages, 224 | 'get_extension_info': self._exported_methods.get_extension_info, 225 | 'decode_html': self._exported_methods.decode_html, 226 | 'url_decode': self._exported_methods.url_decode, 227 | 'analyze_request': self._exported_methods.analyze_request, 228 | 'analyze_response': self._exported_methods.analyze_response, 229 | 'find_versions': self._exported_methods.find_versions, 230 | 'find_domains': self._exported_methods.find_domains, 231 | 'get_jwt': self._exported_methods.get_jwt, 232 | 'decode_jwt': self._exported_methods.decode_jwt, 233 | 'encode_jwt': self._exported_methods.encode_jwt, 234 | 'send_http_message': self._exported_methods.send_http_message, 235 | 'split_http_header': self._exported_methods.split_http_header, 236 | 'has_header': self._exported_methods.has_header, 237 | 'has_stopped': self._exported_methods.has_stopped, 238 | '_set_message': self._set_message, 239 | '_get_message': self._get_message, 240 | '_is_enabled': self._is_enabled, 241 | 'helpers': self._helpers 242 | } 243 | # Execute script 244 | exec(self.ide_pane.compiled_code, globals) 245 | # Reimport API method implementations 246 | self.set_message = globals['_set_message'] 247 | self.get_message = globals['_get_message'] 248 | self.is_enabled = globals['_is_enabled'] 249 | # Register IMessageEditorTabFactory 250 | self._extender.callbacks.registerMessageEditorTabFactory(self) 251 | except: 252 | self._ide_pane.activated = False 253 | self.stop_analysis() 254 | traceback.print_exc(file=self._extender.callbacks.getStdout()) 255 | ErrorDialog.Show(self._extender.parent, traceback.format_exc()) 256 | 257 | def stop_analysis(self): 258 | # Unregister IMessageEditorTabFactory 259 | self._extender.callbacks.removeMessageEditorTabFactory(self) 260 | # Delete current method definition 261 | self.set_message = None 262 | self.get_message = None 263 | self.is_enabled = None 264 | self.session = {} 265 | 266 | @property 267 | def is_enabled(self): 268 | with self._is_enabled_lock: 269 | result = self._is_enabled 270 | return result 271 | 272 | @is_enabled.setter 273 | def is_enabled(self, value): 274 | with self._is_enabled_lock: 275 | self._is_enabled = value 276 | 277 | @property 278 | def set_message(self): 279 | with self._set_message_lock: 280 | result = self._set_message 281 | return result 282 | 283 | @set_message.setter 284 | def set_message(self, value): 285 | with self._set_message_lock: 286 | self._set_message = value 287 | 288 | @property 289 | def get_message(self): 290 | with self._get_message_lock: 291 | result = self._get_message 292 | return result 293 | 294 | @get_message.setter 295 | def get_message(self, value): 296 | with self._get_message_lock: 297 | self._get_message = value 298 | 299 | @property 300 | def session(self): 301 | with self._session_lock: 302 | result = self._session 303 | return result 304 | 305 | @session.setter 306 | def session(self, value): 307 | with self._session_lock: 308 | self._session = value 309 | 310 | def createNewInstance(self, controller, editable): 311 | """This method implements IMessageEditorTabFactory.createNewInstance""" 312 | return CustomTextEditorImplementation(custom_editor_tab=self, controller=controller, editable=editable) 313 | 314 | def process_proxy_history_entry(self, message_info, is_request=False, tool_flag=None, send_date=None, 315 | received_date=None, listener_interface=None, client_ip_address=None, 316 | timedout=None, message_reference=None, proxy_message_info=None, time_delta=None, 317 | in_scope=None, communication_manager=None, invocation=None): 318 | pass 319 | -------------------------------------------------------------------------------- /turbodataminer/turbodataminer/ui/core/jtabbedpaneclosable.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This module implements all functionality to implement a JTabbedPane that allows users to add and remove 4 | tabs in a tab pane. 5 | 6 | The code was ported from: https://github.com/PortSwigger/hackvertor/blob/master/src/main/java/burp/JTabbedPaneClosable.java 7 | """ 8 | 9 | __author__ = "Lukas Reiter" 10 | __license__ = "GPL v3.0" 11 | __copyright__ = """Copyright 2020 Lukas Reiter 12 | 13 | This program is free software: you can redistribute it and/or modify 14 | it under the terms of the GNU General Public License as published by 15 | the Free Software Foundation, either version 3 of the License, or 16 | (at your option) any later version. 17 | 18 | This program is distributed in the hope that it will be useful, 19 | but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | GNU General Public License for more details. 22 | 23 | You should have received a copy of the GNU General Public License 24 | along with this program. If not, see . 25 | """ 26 | __version__ = 1.0 27 | 28 | import traceback 29 | from javax.swing import JPanel 30 | from javax.swing import JLabel 31 | from javax.swing import JTextField 32 | from javax.swing import JTabbedPane 33 | from javax.swing import JOptionPane 34 | from javax.swing.event import ChangeListener 35 | from java.awt import Font 36 | from java.awt import Color 37 | from java.awt import Dimension 38 | from java.awt import GridBagLayout 39 | from java.awt import GridBagConstraints 40 | from java.awt.event import MouseAdapter 41 | from java.awt.event import FocusAdapter 42 | from java.awt.event import MouseListener 43 | from java.awt.event import ComponentAdapter 44 | from turbodataminer.ui.analyzers import HttpListenerAnalyzer 45 | from turbodataminer.ui.modifiers import HttpListenerModifier 46 | from turbodataminer.ui.modifiers import ProxyListenerModifier 47 | from turbodataminer.ui.custommessage import CustomMessageEditorTab 48 | from turbodataminer.ui.core.intelbase import IntelBaseConfiguration 49 | 50 | 51 | class CloseListenerMouseAdapter(MouseAdapter): 52 | """ 53 | Each tab contains a JTextField before the x. This listener class enables editing the JTextField if the user 54 | double-clicks on it. 55 | """ 56 | 57 | def __init__(self, tab, text_field): 58 | MouseAdapter.__init__(self) 59 | self._tab = tab 60 | self._text_field = text_field 61 | 62 | def mouseClicked(self, event): 63 | """ 64 | This method is an event for the textField component. 65 | :param event: 66 | :return: 67 | """ 68 | tabbed_pane = self._text_field.getParent().getParent().getParent() 69 | tabbed_pane.setSelectedIndex(tabbed_pane.indexOfComponent(self._tab)) 70 | if event.getClickCount() == 2: 71 | self._text_field.setEditable(True) 72 | 73 | 74 | class CloseListenerFocusAdapter(FocusAdapter): 75 | """ 76 | Each tab contains a JTextField before the x. CloseListenerMouseAdapter enables editing the JTextField if the user 77 | double-clicks on it. This class is the counterpart that disables editing as soon as the focus is lost. 78 | """ 79 | 80 | def __init__(self, text_field): 81 | FocusAdapter.__init__(self) 82 | self._text_field = text_field 83 | 84 | def focusLost(self, event): 85 | """ 86 | This method is an event for the textField component. 87 | :param event: 88 | :return: 89 | """ 90 | self._text_field.setEditable(False) 91 | 92 | 93 | class CloseListener(MouseListener): 94 | 95 | def __init__(self, extender, tab): 96 | MouseListener.__init__(self) 97 | self._extender = extender 98 | self._tab = tab 99 | 100 | def mouseClicked(self, event): 101 | """ 102 | This method is called when button x is clicked to close the tab. 103 | :param event: 104 | :return: 105 | """ 106 | if isinstance(event.getSource(), JLabel): 107 | clicked_button = event.getSource() 108 | tabbed_pane = clicked_button.getParent().getParent().getParent() 109 | result = self._pre_closing_activities() 110 | if result != JOptionPane.CANCEL_OPTION: 111 | # Remove tab from UI 112 | tabbed_pane.clicked_delete = True 113 | tabbed_pane.remove(self._tab) 114 | 115 | def _pre_closing_activities(self): 116 | """ 117 | This method performs all necessary activities before the tab can be closed. 118 | :return: 119 | """ 120 | # Check if the script has to be saved and continue operation based on the user's decision 121 | result = self._tab.ide_pane.save_current_script() 122 | if result != JOptionPane.CANCEL_OPTION: 123 | # Closing the tab is like clicking the Stop button and as a result, we re-enable the IDE pane components 124 | # as well as call the cleanup function. 125 | self._tab.ide_pane.activated = False 126 | self._tab.ide_pane.stop_analysis_function() 127 | # Remove Burp Suite Listener 128 | JTabbedPaneClosable.remove_listener(self._extender.callbacks, self._tab) 129 | return result 130 | 131 | def mousePressed(self, event): 132 | pass 133 | 134 | def mouseReleased(self, event): 135 | pass 136 | 137 | def mouseEntered(self, event): 138 | pass 139 | 140 | def mouseExited(self, event): 141 | pass 142 | 143 | 144 | class CloseButtonTab(JPanel): 145 | 146 | def __init__(self, extender, tab, title, icon): 147 | JPanel.__init__(self) 148 | self._extender = extender 149 | self.tab = tab 150 | self._text_field = JTextField(title) 151 | self.setOpaque(False) 152 | self.setLayout(GridBagLayout()) 153 | c = GridBagConstraints() 154 | c.fill = GridBagConstraints.HORIZONTAL 155 | c.gridx = 0 156 | c.gridy = 0 157 | c.weightx = 0.5 158 | c.gridwidth = 1 159 | c.ipadx = 8 160 | self._text_field.setOpaque(False) 161 | self._text_field.setBackground(Color(0, 0, 0, 0)) 162 | self._text_field.setBorder(None) 163 | self._text_field.setEditable(False) 164 | # The following two listeners enable editing in case the user double clicks on the text field and disables 165 | # editing as soon as the focus is lost. 166 | self._text_field.addMouseListener(CloseListenerMouseAdapter(self.tab, self._text_field)) 167 | self._text_field.addFocusListener(CloseListenerFocusAdapter(self._text_field)) 168 | self.add(self._text_field, c) 169 | close = JLabel("x") 170 | close.setFont(Font("Courier New", Font.PLAIN, 10)) 171 | close.setPreferredSize(Dimension(10, 10)) 172 | close.setBorder(None) 173 | close.addMouseListener(CloseListener(self._extender, self.tab)) 174 | c.gridx = 1 175 | self.add(close, c) 176 | 177 | def focusLost(self, event): 178 | """ 179 | This method is an event for the textField component. 180 | :param event: 181 | :return: 182 | """ 183 | self._text_field.setEditable(False) 184 | 185 | def get_title(self): 186 | """ 187 | This method returns the tab's title. 188 | :return: 189 | """ 190 | return self._text_field.getText() 191 | 192 | 193 | class JTabbedPaneClosableComponentAdapter(ComponentAdapter): 194 | 195 | def __init__(self, tabbed_pane): 196 | ComponentAdapter.__init__(self) 197 | self._tabbed_pane = tabbed_pane 198 | 199 | def componentShown(self, event): 200 | if self._tabbed_pane.getSelectedIndex() == -1: 201 | return 202 | 203 | 204 | class JTabbedPaneClosableChangeListener(ChangeListener): 205 | 206 | def __init__(self, tabbed_pane, tab_count): 207 | ComponentAdapter.__init__(self) 208 | self._tabbed_pane = tabbed_pane 209 | self._tab_count = tab_count 210 | 211 | def stateChanged(self, event): 212 | if self._tabbed_pane.getSelectedIndex() >= 0: 213 | if self._tabbed_pane.clicked_delete: 214 | self._tabbed_pane.clicked_delete = False 215 | if self._tabbed_pane.getTabCount() > 1: 216 | if self._tabbed_pane.getSelectedIndex() == self._tabbed_pane.getTabCount() - 1: 217 | self._tabbed_pane.setSelectedIndex(self._tabbed_pane.getTabCount() - 2) 218 | return 219 | # Do not make an elif here 220 | if self._tabbed_pane.getTitleAt(self._tabbed_pane.getSelectedIndex()) == "...": 221 | self._tab_count += 1 222 | panel = self._tabbed_pane.create_component() 223 | self._tabbed_pane.remove(self._tabbed_pane.getSelectedIndex()) 224 | self._tabbed_pane.addTab(str(self._tab_count), None, panel) 225 | self._tabbed_pane.addTab("...", None, JPanel()) 226 | self._tabbed_pane.setSelectedIndex(self._tabbed_pane.getTabCount() - 2) 227 | 228 | 229 | class JTabbedPaneClosable(JTabbedPane): 230 | """ 231 | Implements a JTabbedPane which allows users to add and close tabs. 232 | """ 233 | 234 | def __init__(self, extender, component_class, configuration=None): 235 | JTabbedPane.__init__(self) 236 | tab_count = 0 237 | print("Load: {}".format(component_class.__name__)) 238 | self.clicked_delete = False 239 | self.extender = extender 240 | self._component_class = component_class 241 | self.addComponentListener(JTabbedPaneClosableComponentAdapter(self)) 242 | # Read configuration and load plugins 243 | if configuration and "tabs" in configuration: 244 | for tab_index in configuration["tabs"]: 245 | if tab_index in configuration: 246 | tab_info = configuration[tab_index] 247 | if "title" in tab_info and "script_info" in tab_info: 248 | title = tab_info["title"] 249 | print("- Load tab: {}".format(title)) 250 | # Parse the plugin's configuration 251 | try: 252 | script_info = IntelBaseConfiguration(tab_info["script_info"]) 253 | except: 254 | traceback.print_exc(file=self.extender.callbacks.getStderr()) 255 | script_info = IntelBaseConfiguration() 256 | # Load and add plugin UI with the configuration 257 | component = self.create_component(script_info) 258 | self.addTab(title, None, component) 259 | tab_count += 1 260 | # Launch plugin script it has been running at the last unload 261 | if script_info.activated: 262 | component.ide_pane.activated = True 263 | component.ide_pane.start_stop_script() 264 | if tab_count == 0: 265 | self.addTab("1", None, self.create_component()) 266 | tab_count += 1 267 | self.addTab("...", None, JPanel()) 268 | self.addChangeListener(JTabbedPaneClosableChangeListener(self, tab_count)) 269 | 270 | def create_component(self, configuration=None): 271 | return self._component_class(extender=self.extender, configuration=configuration, closable_tabbed_pane=self) 272 | 273 | def addTab(self, title, icon, component, tip=None): 274 | JTabbedPane.addTab(self, title, icon, component, tip) 275 | # Register Burp Suite listeners 276 | self.register_listener(self.extender.callbacks, component) 277 | 278 | def insertTab(self, title, icon, component, tip, index): 279 | JTabbedPane.insertTab(self, title, icon, component, tip, index) 280 | if title != "...": 281 | self.setTabComponentAt(index, CloseButtonTab(self.extender, component, title, icon)) 282 | 283 | def get_json(self): 284 | """ 285 | This method returns the tab's current state. The method is used by Turbo Miner to persist the current 286 | configuration. 287 | :return: 288 | """ 289 | tabs = [] 290 | result = {"tabs": tabs} 291 | for i in range(0, self.getTabCount() - 1): 292 | index = str(i) 293 | tab_component = self.getTabComponentAt(i) 294 | component = self.getComponentAt(i) 295 | title = tab_component.get_title() 296 | tabs.append(index) 297 | result[index] = {"title": title, "script_info": component.get_json()} 298 | return result 299 | 300 | def stop_scripts(self): 301 | """ 302 | This method sends the stop signal to all intel tabs. This method is called by the extender when the app 303 | is unloaded. 304 | :return: 305 | """ 306 | for i in range(0, self.getTabCount() - 1): 307 | component = self.getComponentAt(i) 308 | # Unloading the app is like clicking the Stop button and as a result, we re-enable the IDE pane components 309 | # as well as call the cleanup functions. 310 | component.ide_pane.activated = False 311 | component.ide_pane.stop_analysis_function() 312 | 313 | @staticmethod 314 | def register_listener(callbacks, component): 315 | """ 316 | This static method performs all Burp Suite listener registrations 317 | :param callbacks: 318 | :param component: 319 | :return: 320 | """ 321 | # TODO: Update in case of new intel component 322 | if isinstance(component, HttpListenerAnalyzer): 323 | callbacks.registerHttpListener(component) 324 | elif isinstance(component, HttpListenerModifier): 325 | callbacks.registerHttpListener(component) 326 | elif isinstance(component, ProxyListenerModifier): 327 | callbacks.registerProxyListener(component) 328 | # Note that CustomMessageEditorTab is registered in turbodataminer.ui.custommessage.CustomMessageEditorTab 329 | # Note that CustomScannerCheckTab is registered in turbodataminer.ui.scannercheck.CustomScannerCheckTab 330 | 331 | @staticmethod 332 | def remove_listener(callbacks, component): 333 | """ 334 | This static method performs all Burp Suite listener registrations 335 | :param callbacks: 336 | :param component: 337 | :return: 338 | """ 339 | # TODO: Update in case of new intel component 340 | if isinstance(component, HttpListenerAnalyzer): 341 | callbacks.removeHttpListener(component) 342 | elif isinstance(component, HttpListenerModifier): 343 | callbacks.removeHttpListener(component) 344 | elif isinstance(component, ProxyListenerModifier): 345 | callbacks.removeProxyListener(component) 346 | # Note that CustomMessageEditorTab is registered in turbodataminer.ui.custommessage.CustomMessageEditorTab 347 | # Note that CustomScannerCheckTab is registered in turbodataminer.ui.scannercheck.CustomScannerCheckTab 348 | --------------------------------------------------------------------------------