diff --git a/lib/core/common.py b/lib/core/common.py index 6a4ecd97b..4b8c8abee 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -5645,7 +5645,7 @@ def pollProcess(process, suppress_errors=False): def parseRequestFile(reqFile, checkParams=True): """ - Parses WebScarab and Burp logs and adds results to the target URL list + Parses WebScarab, Burp and HAR logs and adds results to the target URL list >>> handle, reqFile = tempfile.mkstemp(suffix=".req") >>> content = b"POST / HTTP/1.0\\nUser-agent: foobar\\nHost: www.example.com\\n\\nid=1\\n" @@ -5689,7 +5689,23 @@ def parseRequestFile(reqFile, checkParams=True): Parses Burp logs """ - if not re.search(BURP_REQUEST_REGEX, content, re.I | re.S): + if re.search(r"\A\s*\{.*\"entries\"", content[:1000], re.S): + reqResList = [] + try: + entries = json.loads(content)["log"]["entries"] + except (ValueError, KeyError, TypeError): + entries = [] + for entry in entries: # Note: HAR (HTTP Archive) logs (e.g. ZAP, browsers) - re-serialized into raw requests + try: + request = entry["request"] + url = request["url"] + headers = [(_["name"], _["value"]) for _ in request.get("headers", []) if not _["name"].startswith(':')] + if not any(_[0].upper() == HTTP_HEADER.HOST.upper() for _ in headers): + headers.insert(0, (HTTP_HEADER.HOST, _urllib.parse.urlsplit(url).netloc)) + reqResList.append("%s %s HTTP/1.1\n%s\n\n%s" % (request["method"], url, "\n".join("%s: %s" % _ for _ in headers), (request.get("postData") or {}).get("text") or "")) + except (KeyError, TypeError, AttributeError): + continue + elif not re.search(BURP_REQUEST_REGEX, content, re.I | re.S): if re.search(BURP_XML_HISTORY_REGEX, content, re.I | re.S): reqResList = [] for match in re.finditer(BURP_XML_HISTORY_REGEX, content, re.I | re.S): diff --git a/lib/core/settings.py b/lib/core/settings.py index f0f18be3a..5ab376503 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from lib.core.enums import OS from thirdparty import six # sqlmap version (...) -VERSION = "1.10.11.7" +VERSION = "1.10.11.8" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index da15f83fe..75cb725aa 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -130,7 +130,7 @@ def cmdLineParser(argv=None): help="Connection string for direct database connection") target.add_argument("-l", dest="logFile", - help="Parse target(s) from Burp or WebScarab proxy log file") + help="Parse target(s) from Burp, WebScarab or HAR log file") target.add_argument("-m", dest="bulkFile", help="Scan multiple targets given in a textual file") diff --git a/sqlmap.conf b/sqlmap.conf index b6bbb7d5f..9af192c95 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -12,10 +12,10 @@ url = # oracle://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_SID direct = -# Parse targets from Burp or WebScarab logs +# Parse targets from Burp, WebScarab or HAR logs # Valid: Burp proxy (http://portswigger.net/suite/) requests log file path # or WebScarab proxy (http://www.owasp.org/index.php/Category:OWASP_WebScarab_Project) -# 'conversations/' folder path +# 'conversations/' folder path or HAR (HTTP Archive) file path logFile = # Scan multiple targets enlisted in a given textual file diff --git a/tests/test_common.py b/tests/test_common.py index a17ecd668..a346caa99 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -21,6 +21,7 @@ stdlib unittest only (no pytest / no pip); works on Python 2.7 and 3.x. import atexit import base64 +import json import os import shutil import sys @@ -537,6 +538,22 @@ class TestParseRequestFileBurp(unittest.TestCase): self.assertEqual([_[0] for _ in targets], ["https://www.target.com:8443/vuln.php?id=1", "http://www.target.com:443/vuln.php?id=1", "https://www.target.com:443/vuln.php?id=1", "http://www.target.com:8080/vuln.php?id=1"]) self.assertTrue(all("://" not in value for _ in targets for key, value in _[4] if key == "Host")) + def test_har_log(self): + # Note: HAR (HTTP Archive) logs (e.g. ZAP, browsers), including HTTP/2 entries (pseudo-headers, no Host) + entries = [ + {"request": {"method": "GET", "url": "https://www.target.com:8443/vuln.php?id=1", "headers": [{"name": "Host", "value": "www.target.com:8443"}, {"name": "Cookie", "value": "SID=xyz"}]}}, + {"request": {"method": "POST", "url": "https://www.target.com/api", "headers": [{"name": ":authority", "value": "www.target.com"}, {"name": "content-type", "value": "application/json"}], "postData": {"text": '{"id": 1}'}}}, + {"request": {"method": "GET", "url": "https://www.target.com/logo.png", "headers": []}}, + ] + path = _write_temp(json.dumps({"log": {"version": "1.2", "entries": entries}}), ".har") + try: + targets = list(parseRequestFile(path)) + finally: + os.unlink(path) + + self.assertEqual([_[:4] for _ in targets], [("https://www.target.com:8443/vuln.php?id=1", HTTPMETHOD.GET, None, "SID=xyz"), ("https://www.target.com/api", HTTPMETHOD.POST, '{"id": 1}', None)]) + self.assertEqual(targets[1][4], (("Host", "www.target.com"), ("content-type", "application/json"))) + def test_post_body_captured(self): content = ( "======================================================\n"