Skip to content

Commit 3e7d064

Browse files
committed
Adding some more tests
1 parent 39ba1bc commit 3e7d064

7 files changed

Lines changed: 102 additions & 18 deletions

File tree

data/txt/sha256sums.txt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ ca86d61d3349ed2d94a6b164d4648cff9701199b5e32378c3f40fca0f517b128 extra/shutils/
160160
df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/recloak.sh
161161
1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh
162162
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py
163-
617cec1b731e0baacafa6f58c2f56a85b6128d1416627cc1b2f61519c8539a2e extra/vulnserver/vulnserver.py
163+
9af5fdfa8b2425d404d86ab08d3644caa95bcf77605551f5da482a59d1e54a22 extra/vulnserver/vulnserver.py
164164
a2bf70d7f87c3a4e0675c0bad54119a4e04efa6ea2730a8338d5aebcd995630e lib/controller/action.py
165165
736715a73941a06e5d3d349dd01a1f1b171f54eb4c374c6752b2cc44b0977ffe lib/controller/checks.py
166166
666935b658074dc9c42153622b75d4ec7bfe56fbe0742de827a5d30a1a0f9d96 lib/controller/controller.py
@@ -181,26 +181,26 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor
181181
5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py
182182
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py
183183
914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py
184-
5a576f802f1298d0aa357e766ae6502fa53cacbbe0b1d328b7410a8b20a885b2 lib/core/optiondict.py
184+
4fe3ac4c0d354d1ac42ad3f5dc1b308993588f8a249ff880d273f5031d6b52b0 lib/core/optiondict.py
185185
98d3d61278794705c7039e40fab66a626e8d6ab765383c5379cec7a066b09301 lib/core/option.py
186186
21b2b1745107c211fc7593923a3da7a808d40763c00091c28de5f7c129bcf3bc lib/core/patch.py
187187
49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py
188188
0c36a65b6237732eb001d333f80f0c58c088ff01ae80cf07e4dcc6da2a806364 lib/core/readlineng.py
189189
9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py
190190
0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py
191191
888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py
192-
35c24cf138fdd68add3c8f6274d6ff735b5209c84eec635ba316f986b67325ef lib/core/settings.py
192+
61024490352e898a43f1cb001fb79276d185ef3579b6230df46badf573336833 lib/core/settings.py
193193
c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py
194194
a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py
195195
19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py
196-
540443bdc23965be80d80185d7f3b54b632228af220dc2cb2e9cbb3f4fd4cea4 lib/core/testing.py
196+
96d107a31bb9647a9b7c26f10beac528bf4edc6e607c8b776c624d494332c7f8 lib/core/testing.py
197197
95656c44bab1771f4808030dd6a17eae5b129cb1234443f00b19695c7b712b86 lib/core/threads.py
198198
b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py
199199
53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py
200200
2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py
201201
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py
202202
54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py
203-
403ebb5b54531cf907a30ed439fc881cf3cbae68c3a4ec600c75312e5f6b9001 lib/parse/cmdline.py
203+
d6ba23b8f3d40cb021de1ebe50eabf891f060df77e9643838ff8fd3850b507d0 lib/parse/cmdline.py
204204
02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py
205205
c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py
206206
5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py
@@ -509,7 +509,7 @@ cedf45d33461bd7e5400d06611a63c8a4ffae1a4510030c5696b9d46ed6a9883 plugins/generi
509509
46517f1444c202710e388873960130850ed092e17bd6f4dd5f2fedea3dbb8ffc sqlmapapi.py
510510
f09d1b06901e7e02d0dbf4de607f6a4a9889acc322ae9353b98ea9101fb9548a sqlmapapi.yaml
511511
627d90f1194335b800cbc9cc78db6697cf9e02e193a83598e0d4d0abb55b63b8 sqlmap.conf
512-
41fa63d55909cf00a0bb02e979c4f2c0ad7df4b32a89374150772b247fa96fc2 sqlmap.py
512+
d375c77f1f4270ec0967e67963fe410f14b5d2e51ed6483593dc1aaa4e8e106e sqlmap.py
513513
eb37a88357522fd7ad00d90cdc5da6b57442b4fec49366aadb2944c4fbf8b804 tamper/0eunion.py
514514
a9785a4c111d6fee2e6d26466ba5efb3b229c00520b26e8024b041553b53efba tamper/apostrophemask.py
515515
cf26bc8006519bd25ce06d347f72770cd75b61575cf65e5812274e8ab9392eb4 tamper/apostrophenullencode.py

extra/vulnserver/vulnserver.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import string
1818
import sys
1919
import threading
20+
import time
2021
import traceback
2122

2223
PY3 = sys.version_info >= (3, 0)
@@ -1044,6 +1045,57 @@ def do_REQUEST(self):
10441045
self.wfile.write(output.encode(UNICODE_ENCODING))
10451046
return
10461047

1048+
if self.url == "/fp":
1049+
# False-positive battery traps (exercised on demand by '--fp-test'). Every trap is
1050+
# deliberately NON-injectable but baits a specific FP defense; sqlmap must report "not
1051+
# injectable" for all of them (each is paired, in FP_TESTS, with a real injectable twin).
1052+
trap = self.params.get("trap", "reflect")
1053+
idv = self.params.get("id", "1")
1054+
1055+
def _rnd(n=8):
1056+
return "".join(random.choice("0123456789abcdef") for _ in range(n))
1057+
1058+
if trap == "intcast":
1059+
# parameterized int lookup: id=1 -> row, non-int (e.g. "1 AND 1=1") -> empty. A boolean
1060+
# payload yields a differential yet it is NOT SQLi -> the false-positive check must reject it.
1061+
try:
1062+
hit = int(idv) in (1, 2, 3)
1063+
except ValueError:
1064+
hit = False
1065+
output = "<html><body><b>SQL results:</b><table border=\"1\">%s</table></body></html>" % ("<tr><td>%s</td><td>luther</td><td>blisset</td></tr>" % idv if hit else "")
1066+
elif trap == "structrand":
1067+
# heavy dynamic TEXT (defeats dynamic-content removal) + STABLE structure; id is not
1068+
# reflected into the structure -> stresses the structure-aware comparison oracle.
1069+
rows = "".join("<tr><td>%s</td><td>%s</td></tr>" % (_rnd(), _rnd()) for _ in range(3))
1070+
output = ("<html><head><title>Report</title></head><body><div class=\"csrf\">%s</div>"
1071+
"<nav class=\"top\">token %s</nav><table id=\"grid\" class=\"res\">%s</table>"
1072+
"<div class=\"foot\">%s</div></body></html>" % (_rnd(), _rnd(), rows, _rnd()))
1073+
elif trap == "acceptall":
1074+
# 200 + identical content for EVERYTHING incl. garbage -> the reads-everything-true channel.
1075+
output = "<html><body><b>OK</b> welcome to the portal</body></html>"
1076+
elif trap == "reflect":
1077+
# echoes the parameter verbatim (reflection) with no SQL sink.
1078+
output = "<html><body>you searched for: %s</body></html>" % idv
1079+
elif trap == "errors":
1080+
# DB-error-looking text for any non-baseline input -> baits error-based detection.
1081+
output = "<html><body>Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result</body></html>" if idv != "1" else "<html><body><b>SQL results:</b><table><tr><td>1</td><td>luther</td></tr></table></body></html>"
1082+
elif trap == "lengthrand":
1083+
# response length varies at random (not with the payload) -> baits length-based heuristics.
1084+
output = "<html><body>ok %s</body></html>" % _rnd(random.choice([4, 40, 400]))
1085+
elif trap == "slowrand":
1086+
# random latency, uncorrelated with the payload -> baits time-based detection.
1087+
time.sleep(random.choice([0, 0, 0, 1]))
1088+
output = "<html><body>ok %s</body></html>" % _rnd()
1089+
else:
1090+
output = "<html><body>?</body></html>"
1091+
1092+
self.send_response(OK)
1093+
self.send_header("Content-type", "text/html; charset=%s" % UNICODE_ENCODING)
1094+
self.send_header("Connection", "close")
1095+
self.end_headers()
1096+
self.wfile.write(output.encode(UNICODE_ENCODING))
1097+
return
1098+
10471099
if self.url == '/':
10481100
if not any(_ in self.params for _ in ("id", "query")):
10491101
self.send_response(OK)

lib/core/optiondict.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@
282282
"forceDns": "boolean",
283283
"murphyRate": "integer",
284284
"smokeTest": "boolean",
285+
"fpTest": "boolean",
285286
"apiTest": "boolean",
286287
},
287288

lib/core/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from thirdparty import six
2121

2222
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
23-
VERSION = "1.10.7.3"
23+
VERSION = "1.10.7.4"
2424
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
2525
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34}
2626
VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)

lib/core/testing.py

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@
4141
from lib.core.settings import IS_WIN
4242
from lib.core.settings import RESTAPI_VERSION
4343

44-
def vulnTest():
44+
def vulnTest(tests=None, label="vuln"):
4545
"""
46-
Runs the testing against 'vulnserver'
46+
Runs the testing against 'vulnserver' (default suite, or a caller-supplied one e.g. FP_TESTS)
4747
"""
4848

49-
TESTS = (
49+
TESTS = tests if tests is not None else (
5050
("-h", ("to see full list of options run with '-hh'",)),
5151
("--dependencies", ("sqlmap requires", "third-party library")),
5252
("-u <url> --data=\"reflect=1\" --flush-session --wizard --disable-coloring", ("Please choose:", "back-end DBMS: SQLite", "current user is DBA: True", "banner: '3.")),
@@ -63,7 +63,7 @@ def vulnTest():
6363
("-u <url> --data=\"security_level=3\" -p id --flush-session --technique=B", ("bypassed the WAF/IPS by using tamper script", "Type: boolean-based blind")), # automatic WAF-bypass: SQL-tamper dimension at a stricter signature threshold
6464
("-u <url> --data=\"security_level=4\" -p id --flush-session --technique=B --banner", ("random (non-scanner) User-Agent and browser-like headers to bypass the WAF/IPS", "Type: boolean-based blind", "banner: '3.")), # automatic WAF-bypass against a libinjection-class WAF: tampers cannot help, only the non-scanner User-Agent does
6565
("-u <url> --data=\"security_level=5\" -p id --flush-session --technique=B", ("unable to automatically bypass the WAF/IPS", "does not seem to be injectable")), # automatic WAF-bypass honest bail: a libinjection-class WAF that no User-Agent or tamper can defeat
66-
("-u <url> -p id --flush-session --proof", ("sqlmap proved exploitation of the following injection point", "Parameter: id (GET)", "Technique: boolean-based blind", "TRUE (5/5)", "repeatably", "Retrieved: back-end DBMS banner '3.")), # --proof: report-grade proof in the injection-point style - forces the boolean technique (so a multi-technique point still proves), and actively reads a value out as the strongest proof
66+
("-u <url> -p id --flush-session --technique=B --proof", ("sqlmap proved exploitation of the following injection point", "Parameter: id (GET)", "Technique: boolean-based blind", "TRUE (5/5)", "repeatably", "Retrieved: back-end DBMS banner '3.")), # --proof: report-grade proof in the injection-point style - forces the boolean technique (so a multi-technique point still proves), and actively reads a value out as the strongest proof
6767
("-r <request> --flush-session -v 5 --test-skip=\"heavy\" --save=<config>", ("CloudFlare", "web application technology: Express", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind", "saved command line options to the configuration file")),
6868
("-c <config>", ("CloudFlare", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind")),
6969
("-l <log> --flush-session --skip-waf -vvvvv --technique=U --union-from=users --banner --parse-errors", ("banner: '3.", "ORDER BY term out of range", "~xp_cmdshell", "Connection: keep-alive")),
@@ -73,7 +73,7 @@ def vulnTest():
7373
("-u <base64> -p id --base64=id --data=\"base64=true\" --flush-session --tables --technique=U", (" users ",)),
7474
("-u <url> --flush-session --banner --technique=B --disable-precon --not-string \"no results\"", ("banner: '3.",)),
7575
("-u <url> --flush-session --encoding=gbk --banner --technique=B --first=1 --last=2", ("banner: '3.'",)),
76-
("-u <url> --flush-session --encoding=ascii --forms --crawl=2 --threads=2 --banner", ("total of 2 targets", "might be injectable", "Type: UNION query", "banner: '3.")),
76+
("-u <url> --flush-session --technique=BU --encoding=ascii --forms --crawl=2 --threads=2 --banner", ("total of 2 targets", "might be injectable", "Type: UNION query", "banner: '3.")),
7777
("-u <base> --flush-session --technique=BU --data=\"{\\\"id\\\": 1}\" --banner", ("might be injectable", "3 columns", "Payload: {\"id\"", "Type: boolean-based blind", "Type: UNION query", "banner: '3.")),
7878
("-u <base> --flush-session -H \"Foo: Bar\" -H \"Sna: Fu\" --data=\"<root><param name=\\\"id\\\" value=\\\"1*\\\"/></root>\" --union-char=1 --mobile --answers=\"smartphone=3\" --banner --smart -v 5", ("might be injectable", "Payload: <root><param name=\"id\" value=\"1", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", "banner: '3.", "Nexus", "Sna: Fu", "Foo: Bar")),
7979
("-u <base> --flush-session --technique=BU --method=PUT --data=\"a=1;id=1;b=2\" --param-del=\";\" --skip-static --har=<tmpfile> --dump -T users --start=1 --stop=2", ("might be injectable", "Parameter: id (PUT)", "Type: boolean-based blind", "Type: UNION query", "2 entries")),
@@ -83,7 +83,7 @@ def vulnTest():
8383
("-u <url> --flush-session --null-connection --technique=B --tamper=between,randomcase --banner --count -T users", ("NULL connection is supported with HEAD method", "banner: '3.", "users | 30")),
8484
("-u <base> --data=\"aWQ9MQ==\" --flush-session --base64=POST -v 6", ("aWQ9MTtXQUlURk9SIERFTEFZICcwOjA",)),
8585
("-u <url> --flush-session --parse-errors --test-filter=\"subquery\" --eval=\"import hashlib; id2=2; id3=hashlib.md5(id.encode()).hexdigest()\" --referer=\"localhost\"", ("might be injectable", ": syntax error", "back-end DBMS: SQLite", "WHERE or HAVING clause (subquery")),
86-
("-u <url> --banner --schema --dump -T users --binary-fields=surname --where \"id>3\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "27 entries", "6E616D6569736E756C6C")),
86+
("-u <url> --technique=BU --banner --schema --dump -T users --binary-fields=surname --where \"id>3\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "27 entries", "6E616D6569736E756C6C")),
8787
("-u <url> --technique=U --fresh-queries --force-partial --dump -T users --dump-format=HTML --answers=\"crack=n\" -v 3", ("performed 31 queries", "nameisnull", "~using default dictionary", "dumped to HTML file")),
8888
("-u <url> --flush-session --technique=BU --all", ("30 entries", "Type: boolean-based blind", "Type: UNION query", "luther", "blisset", "fluffy", "179ad45c6ce2cb97cf1029e212046e81", "NULL", "nameisnull", "testpass")),
8989
("-u <url> --flush-session --technique=B --keyset --dump -T users", ("using keyset (seek) pagination", "30 entries", "luther", "nameisnull")), # keyset/seek dump via the SQLite rowid cursor
@@ -97,7 +97,7 @@ def vulnTest():
9797
("-u \"<url>&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")),
9898
("-d \"<direct>\" --flush-session --dump -T creds --dump-format=SQLITE --binary-fields=password_hash --where \"user_id=5\"", ("3137396164343563366365326362393763663130323965323132303436653831", "dumped to SQLITE database")),
9999
("-d \"<direct>\" --flush-session --banner --schema --sql-query=\"UPDATE users SET name='foobar' WHERE id=4; SELECT * FROM users; SELECT 987654321\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "4,foobar,nameisnull", "'987654321'",)),
100-
("-u <base>csrf --data=\"id=1&csrf_token=1\" --banner --answers=\"update=y\" --flush-session", ("back-end DBMS: SQLite", "banner: '3.")),
100+
("-u <base>csrf --data=\"id=1&csrf_token=1\" --banner --answers=\"update=y\" --flush-session --technique=B", ("back-end DBMS: SQLite", "banner: '3.")),
101101
("--purge -v 3", ("~ERROR", "~CRITICAL", "deleting the whole directory tree")),
102102
)
103103

@@ -263,9 +263,9 @@ def _thread():
263263

264264
clearConsoleLine()
265265
if retVal:
266-
logger.info("vuln test final result: PASSED")
266+
logger.info("%s test final result: PASSED" % label)
267267
else:
268-
logger.error("vuln test final result: FAILED")
268+
logger.error("%s test final result: FAILED" % label)
269269

270270
for filename in cleanups:
271271
try:
@@ -280,6 +280,31 @@ def _thread():
280280

281281
return retVal
282282

283+
def fpTest():
284+
"""
285+
On-demand false-positive battery ('--fp-test'): a set of deliberately NON-injectable traps that
286+
each bait a specific FP defense (boolean confirmation, dynamic-content removal, structure-aware
287+
comparison, canary/sanity gate, reflection, error-regex specificity, length and time heuristics),
288+
paired with real injectable twins. An A+ engine rejects every trap AND still detects every twin.
289+
Kept out of the default '--vuln-test' (CI budget); run explicitly against 'vulnserver'.
290+
"""
291+
292+
FP_TESTS = (
293+
# false-positive traps -> sqlmap MUST NOT flag these as injectable
294+
("-u \"<base>fp?trap=intcast&id=1\" -p id --technique=BEU --level=3 --risk=2 --flush-session", ("~identified the following injection point", "do not appear to be injectable")), # boolean confirmation / checkFalsePositives
295+
("-u \"<base>fp?trap=structrand&id=1\" -p id --technique=BEU --level=3 --risk=2 --flush-session", ("~identified the following injection point", "do not appear to be injectable")), # structure-aware comparison
296+
("-u \"<base>fp?trap=acceptall&id=1\" -p id --technique=BEU --level=3 --risk=2 --flush-session", ("~identified the following injection point", "do not appear to be injectable")), # canary / sanity gate (reads-everything-true)
297+
("-u \"<base>fp?trap=reflect&id=1\" -p id --technique=BEU --level=3 --risk=2 --flush-session", ("~identified the following injection point", "do not appear to be injectable")), # reflection handling
298+
("-u \"<base>fp?trap=errors&id=1\" -p id --technique=BE --level=3 --risk=2 --flush-session", ("~identified the following injection point", "do not appear to be injectable")), # error-regex specificity
299+
("-u \"<base>fp?trap=lengthrand&id=1\" -p id --technique=BEU --level=3 --risk=2 --flush-session", ("~identified the following injection point", "do not appear to be injectable")), # length heuristics
300+
("-u \"<base>fp?trap=slowrand&id=1\" -p id --technique=T --flush-session", ("~identified the following injection point", "do not appear to be injectable")), # time-based statistical model
301+
# true-positive twins -> sqlmap MUST still detect real injection (the discrimination that makes it A+)
302+
("-u <url> -p id --technique=B --flush-session", ("identified the following injection point", "Type: boolean-based blind")),
303+
("-u \"<url>&json=1\" -p id --technique=B --flush-session", ("identified the following injection point",)),
304+
)
305+
306+
return vulnTest(tests=FP_TESTS, label="fp")
307+
283308
def apiTest():
284309
"""
285310
Runs a basic live test of the REST API: launches the server in a separate process

0 commit comments

Comments
 (0)