diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index e6be7c9ed..524c3bc5f 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -33,6 +33,7 @@ optDict = { "liveCookies": "string", "loadCookies": "string", "dropSetCookie": "boolean", + "http10": "boolean", "http2": "boolean", "agent": "string", "mobile": "boolean", @@ -120,6 +121,7 @@ optDict = { "Techniques": { "technique": "string", + "disableStats": "boolean", "nosql": "boolean", "graphql": "boolean", "ldap": "boolean", @@ -260,6 +262,7 @@ optDict = { "scope": "string", "skipHeuristics": "boolean", "skipWaf": "boolean", + "tablePrefix": "string", "testFilter": "string", "testSkip": "string", "timeLimit": "float", diff --git a/lib/core/settings.py b/lib/core/settings.py index 125492bf8..00ae7277e 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.8.24" +VERSION = "1.10.8.25" 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/sqlmap.conf b/sqlmap.conf index e56184d06..ed2c2c076 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -32,6 +32,10 @@ requestFile = # Example: +ext:php +inurl:"&id=" +intext:"powered by " googleDork = +# Load session from a stored (.sqlite) file +# Example: output/www.target.com/session.sqlite +sessionFile = + # These options can be used to specify how to connect to the target URL. [Request] @@ -240,10 +244,6 @@ evalCode = # Valid: True or False optimize = False -# Predict common queries output. -# Valid: True or False -predictOutput = False - # Use persistent HTTP(s) connections. keepAlive = False @@ -606,7 +606,7 @@ sqlFile = # These options can be used to run brute force checks. -[Brute force] +[Brute] # Check existence of common tables. # Valid: True or False @@ -720,10 +720,6 @@ regType = # These options can be used to set some general working parameters. [General] -# Load session from a stored (.sqlite) file -# Example: output/www.target.com/session.sqlite -sessionFile = - # Log all HTTP traffic into a textual file. trafficFile = @@ -861,10 +857,6 @@ alert = # Valid: True or False beep = False -# Offline WAF/IPS payload detection testing. -# Valid: True or False -checkPayload = False - # Check for missing (optional) sqlmap dependencies. # Valid: True or False dependencies = False diff --git a/tests/test_configfile.py b/tests/test_configfile.py new file mode 100644 index 000000000..c2b179148 --- /dev/null +++ b/tests/test_configfile.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Offline tests for the '-c' configuration-file contract. + +configFileParser() walks optDict and reads an option only from the section named after the family that +declares it, so an option is silently dropped - a debug line, nothing else - when the shipped template +and optDict disagree about either its NAME or its SECTION. Both halves of that contract are pinned here +because both had already drifted: three real switches were missing from optDict entirely, the +[Brute force] section could never match the "Brute" family, 'sessionFile' sat under [General] while it +is declared under Target, and two options outlived the switches they configured. +""" + +import os +import re +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.optiondict import optDict + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +CONFIG = os.path.join(ROOT, "sqlmap.conf") + +_SECTION_REGEX = re.compile(r"^\[(?P.+)\]\s*$") +_OPTION_REGEX = re.compile(r"^(?P[A-Za-z][A-Za-z0-9_]*)\s*=") + + +def _configOptions(): + """Every (section, option) pair the shipped template declares, in file order.""" + + retVal, section = [], None + + with open(CONFIG) as f: + for line in f: + line = line.strip() + match = _SECTION_REGEX.match(line) + if match: + section = match.group("name") + continue + match = _OPTION_REGEX.match(line) + if match: + retVal.append((section, match.group("name"))) + + return retVal + + +def _sources(): + retVal = [] + + for root in ("lib", "plugins"): + for base, _, files in os.walk(os.path.join(ROOT, root)): + for name in files: + if name.endswith(".py"): + with open(os.path.join(base, name)) as f: + retVal.append(f.read()) + + return "\n".join(retVal) + + +class TestConfigFileContract(unittest.TestCase): + def test_template_is_not_empty(self): + self.assertGreater(len(_configOptions()), 100) + + def test_every_option_is_readable_from_its_section(self): + """The actual lookup configFileProxy() performs: optDict[section][option].""" + + unreadable = ["[%s] %s" % (section, option) for section, option in _configOptions() + if option not in optDict.get(section, {})] + self.assertEqual(unreadable, [], "silently ignored when set via '-c': %s" % ", ".join(unreadable)) + + def test_every_section_names_a_family(self): + sections = set(section for section, _ in _configOptions()) + self.assertEqual(sorted(sections - set(optDict)), []) + + def test_no_option_outlives_its_switch(self): + """An option nothing reads any more is worse than absent: it reads as supported and does + nothing. Consumption is either a 'conf.' reference or a lookup by name (the API and the + library facade address options as strings).""" + + sources = _sources() + orphans = sorted(set(option for _, option in _configOptions() + if not re.search(r"\bconf\.%s\b" % option, sources) and '"%s"' % option not in sources)) + self.assertEqual(orphans, [], "configure nothing: %s" % ", ".join(orphans)) + + +class TestConfigFileRoundTrip(unittest.TestCase): + """'--save' writes through the same optDict families, and the library facade serializes its kwargs + that way before handing them over as '-c'. So a config sqlmap writes must be one sqlmap can read.""" + + def test_saved_config_reads_back(self): + import tempfile + + from lib.core.common import saveConfig + from lib.core.data import conf + from lib.parse.configfile import configFileParser + + handle, path = tempfile.mkstemp(suffix=".ini") + os.close(handle) + + try: + conf.url = "http://127.0.0.1:1/?id=1" + conf.commonTables = True + conf.tablePrefix = "zzz" + saveConfig(conf, path) + + conf.clear() + configFileParser(path) + + self.assertEqual(conf.url, "http://127.0.0.1:1/?id=1") + self.assertTrue(conf.commonTables) + self.assertEqual(conf.tablePrefix, "zzz") + finally: + os.unlink(path) + + +if __name__ == "__main__": + unittest.main(verbosity=2)