Skip to content

Commit 49a7740

Browse files
committed
Fixing some of WAF bypass bugs
1 parent dc42edc commit 49a7740

5 files changed

Lines changed: 37 additions & 8 deletions

File tree

lib/core/option.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,10 @@ def retrieve():
423423
conf.googlePage += 1
424424

425425
def _setStdinPipeTargets():
426-
if conf.url:
426+
# Note: an explicit target source takes precedence. Without this, any non-interactive run (CI,
427+
# cron, subprocess) would reroute '-m/-l/-r/-g' targets through the STDIN container, losing both
428+
# their count and their order
429+
if any((conf.url, conf.direct, conf.logFile, conf.bulkFile, conf.requestFile, conf.googleDork, conf.openApiFile)):
427430
return
428431

429432
if isinstance(conf.stdinPipe, _collections.Iterable):

lib/core/settings.py

Lines changed: 2 additions & 2 deletions
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.8.42"
23+
VERSION = "1.10.8.43"
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)
@@ -88,7 +88,7 @@
8888
)
8989

9090
# Maximum number of candidate tamper (chains) trialled during automatic WAF-bypass
91-
WAF_BYPASS_MAX_TRIALS = 8
91+
WAF_BYPASS_MAX_TRIALS = len(WAF_BYPASS_TAMPERS)
9292

9393
# Browser-like request headers applied alongside the random (non-scanner) User-Agent during
9494
# automatic WAF bypass: sqlmap's defaults ('Accept: */*', no 'Accept-Language') are themselves a

lib/parse/cmdline.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1215,7 +1215,10 @@ def _format_action_invocation(self, action):
12151215
if args.dummy:
12161216
args.url = args.url or DUMMY_URL
12171217

1218-
if hasattr(sys.stdin, "fileno") and not any((os.isatty(sys.stdin.fileno()), args.api, args.ignoreStdin, "GITHUB_ACTIONS" in os.environ)):
1218+
# Note: an explicit target source rules out reading targets from the standard input. Without
1219+
# this, any non-interactive run (CI, cron, subprocess) would turn '-d/-u/-m/-l/-r/-g' into a
1220+
# 'multiple targets' run reading from a pipe, which also resets per-target options in between
1221+
if hasattr(sys.stdin, "fileno") and not any((os.isatty(sys.stdin.fileno()), args.api, args.ignoreStdin, "GITHUB_ACTIONS" in os.environ, args.direct, args.url, args.logFile, args.bulkFile, args.requestFile, args.googleDork, args.configFile, args.openApiFile)):
12191222
args.stdinPipe = iter(sys.stdin.readline, None)
12201223
else:
12211224
args.stdinPipe = None

plugins/dbms/mysql/fingerprint.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def getFingerprint(self):
120120
fork = FORK.DORIS
121121
elif inject.checkBooleanExpression("@@VERSION_COMMENT LIKE '%StarRocks%'"):
122122
fork = FORK.STARROCKS
123-
elif inject.checkBooleanExpression("GEOGRAPHY_AREA(NULL) IS NULL"): # Note: MemSQL is the only one of the forks without SESSION_USER()
123+
elif inject.checkBooleanExpression("GEOGRAPHY_AREA(NULL) IS NULL"): # Note: GEOGRAPHY_AREA() is MemSQL specific
124124
fork = FORK.MEMSQL
125125
elif inject.checkBooleanExpression("AURORA_VERSION() LIKE '%'"): # Reference: https://aws.amazon.com/premiumsupport/knowledge-center/aurora-version-number/
126126
fork = FORK.AURORA

tamper/blindbinary.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
import re
99

10+
from lib.core.common import Backend
11+
from lib.core.enums import DBMS
1012
from lib.core.enums import PRIORITY
1113

1214
__priority__ = PRIORITY.NORMAL
@@ -43,7 +45,16 @@ def _unwrapIsnull(query):
4345
break
4446

4547
inner = retVal[match.end():end]
46-
separator = inner.rfind(',')
48+
separator, depth = -1, 0 # the argument separator is the comma at the top level
49+
50+
for index, char in enumerate(inner):
51+
if char == '(':
52+
depth += 1
53+
elif char == ')':
54+
depth -= 1
55+
elif char == ',' and depth == 0:
56+
separator = index
57+
4758
if separator < 1:
4859
break
4960

@@ -75,6 +86,11 @@ def _reshape(payload, opener, tail, build):
7586
pos = pos + match.end()
7687
continue
7788
replacement = build(query, rest)
89+
90+
if replacement is None: # builder declined, leave this occurrence alone
91+
pos = pos + match.end()
92+
continue
93+
7894
retVal = retVal[:start] + replacement + retVal[end + 1 + rest.end():]
7995
pos = start + len(replacement)
8096
return retVal
@@ -134,7 +150,12 @@ def _mysql(query, rest):
134150
def _mysqlSet(query, rest):
135151
# set-membership form of the same read ('... IN (<ordinals>)', used by the Huffman retrieval).
136152
# ORD('') is 0, so a past-the-end position matches the ordinal 0, which is the empty string here
137-
position, ordinals = rest.group(1), [int(_) for _ in rest.group(2).split(',')]
153+
position = rest.group(1)
154+
ordinals = [int(_) for _ in rest.group(2).split(',') if _.strip().isdigit()]
155+
156+
if not ordinals or any(_ > 255 for _ in ordinals): # a byte comparison cannot represent those
157+
return None
158+
138159
query = _unwrapIsnull(query)
139160
members = ",".join("''" if _ == 0 else "0x%02x" % _ for _ in ordinals)
140161
return "BINARY RIGHT(LEFT(%s,%s),(%s<=LENGTH(CONVERT(%s USING ascii)))) IN (%s)" % (query, position, position, query, members)
@@ -150,7 +171,9 @@ def _mssql(query, rest):
150171
comma_tail = r"\s*,\s*(\d+)\s*,\s*1\)\)\s*(>=|<=|>|<|=)\s*(\d+)"
151172
set_tail = r"\s*,\s*(\d+)\s*,\s*1\)\)\s+IN\s*\(([\d,\s]+)\)"
152173

153-
if re.search(r"(?i)IFNULL\(", payload): # also on payloads that are not single-character reads
174+
# also on payloads that are not single-character reads. Gated on MySQL, because IFNULL() is used
175+
# by H2, HSQLDB, Cubrid and others too, and IF() is not a function there
176+
if Backend.getIdentifiedDbms() == DBMS.MYSQL and re.search(r"(?i)IFNULL\(", payload):
154177
payload = _unwrapIsnull(payload)
155178

156179
retVal = _reshape(payload, r"(?i)ORD\(MID\(", set_tail, _mysqlSet)

0 commit comments

Comments
 (0)