diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 0bd5cc64d..69bba347f 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -376,9 +376,8 @@ def start(): for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets: targetCount += 1 - # --report-json / REST API: give each target its own taskid in the shared collector, so a - # multi-target run (e.g. '-m' bulk file) doesn't have later targets overwrite earlier ones - # (see _reportData for the report collector and StdDbOut.write for the REST API IPC database) + # --report-json / REST API: give each target its own taskid in the shared collector, so a multi-target + # run (e.g. '-m' bulk file) doesn't have later targets overwrite earlier ones (see _reportData/StdDbOut.write) if conf.reportJson or conf.api: kb.reportTaskId = targetCount diff --git a/lib/core/settings.py b/lib/core/settings.py index 95e1469ec..5e0accc99 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from lib.core.enums import OS from thirdparty import six # sqlmap version (...) -VERSION = "1.10.10.3" +VERSION = "1.10.10.4" 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/utils/api.py b/lib/utils/api.py index d58ab26db..6048047e5 100644 --- a/lib/utils/api.py +++ b/lib/utils/api.py @@ -444,16 +444,8 @@ class StdDbOut(object): def write(self, value, status=CONTENT_STATUS.IN_PROGRESS, content_type=None): if self.messagetype == "stdout": - # A multi-target scan (e.g. crawling or a '-m' bulk file inside a single API task) runs - # every target through this one StdDbOut instance. Key each target's data rows by - # kb.reportTaskId (set per target in controller.start()) so that a later target's - # COMPLETE row cannot delete an earlier target's rows of the same content_type in - # _storeData() - the REST API counterpart of the --report-json fix for #6123, and the - # reason a crawl via sqlmapapi used to return only the last target's results (#2503). - # scan_data() folds the per-target rows back into the task's flat result. - taskid = self.taskid - if kb.get("reportTaskId"): - taskid = "%s-%s" % (self.taskid, kb.reportTaskId) + # Note: per-target key in multi-target scans, so a later target's COMPLETE row doesn't delete an earlier one's (#2503) + taskid = "%s-%s" % (self.taskid, kb.reportTaskId) if kb.get("reportTaskId") else self.taskid _storeData(conf.databaseCursor, taskid, value, status, content_type) else: conf.databaseCursor.execute("INSERT INTO errors VALUES(NULL, ?, ?)", (self.taskid, str(value) if value else "")) @@ -828,16 +820,10 @@ def scan_data(taskid): # Read all data and error messages from the IPC database (shared assembler - same output as --report-json) result = _assembleData(DataStore.current_db, taskid) - # Multi-target scans store each target's data rows under a '-' key (see - # StdDbOut.write); fold them into this task's flat result, in target order. Real taskids - # are dash-less hex strings, so a '-' prefix can only match this task's own rows. + # Note: fold in per-target rows ('-', see StdDbOut.write) of multi-target scans, in target order prefix = "%s-" % taskid - subtaskids = [] - for row in DataStore.current_db.execute("SELECT DISTINCT taskid FROM data"): - stored = str(row[0]) - if stored.startswith(prefix) and stored[len(prefix):].isdigit(): - subtaskids.append(stored) - for subtaskid in sorted(subtaskids, key=lambda _: int(_.rsplit("-", 1)[1])): + subtaskids = [_[0] for _ in DataStore.current_db.execute("SELECT DISTINCT taskid FROM data WHERE taskid LIKE ?", (prefix + '%',)) if _[0][len(prefix):].isdigit()] + for subtaskid in sorted(subtaskids, key=lambda _: int(_[len(prefix):])): result["data"].extend(_assembleData(DataStore.current_db, subtaskid)["data"]) logger.debug("(%s) Retrieved scan data and error messages" % taskid) diff --git a/tests/test_api.py b/tests/test_api.py index 8668559e2..0a76bd768 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -353,7 +353,8 @@ class TestStdDbOutMultiTarget(unittest.TestCase): rows = self.db.execute("SELECT taskid FROM data ORDER BY id") self.assertEqual([row[0] for row in rows], ["t1-1", "t1-2"]) # neither target's row was deleted - def test_single_target_without_report_task_id_keeps_plain_taskid(self): + def test_without_report_task_id_keeps_plain_taskid(self): + # e.g. output written before controller.start() reaches its target loop if "reportTaskId" in kb: del kb.reportTaskId std = api.StdDbOut("t1", messagetype="stdout")