Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a33b045

Browse files
committed
Implementation for an Issue #1360
1 parent 2c2f83f commit a33b045

5 files changed

Lines changed: 49 additions & 23 deletions

File tree

lib/core/enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ class HASHDB_KEYS:
197197
KB_CHARS = "KB_CHARS"
198198
KB_DYNAMIC_MARKINGS = "KB_DYNAMIC_MARKINGS"
199199
KB_INJECTIONS = "KB_INJECTIONS"
200+
KB_ERROR_CHUNK_LENGTH = "KB_ERROR_CHUNK_LENGTH"
200201
KB_XP_CMDSHELL_AVAILABLE = "KB_XP_CMDSHELL_AVAILABLE"
201202
OS = "OS"
202203

lib/core/option.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1792,6 +1792,7 @@ def _setKnowledgeBaseAttributes(flushAll=True):
17921792
kb.endDetection = False
17931793
kb.explicitSettings = set()
17941794
kb.extendTests = None
1795+
kb.errorChunkLength = None
17951796
kb.errorIsNone = True
17961797
kb.fileReadMode = False
17971798
kb.followSitemapRecursion = None

lib/core/settings.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -323,11 +323,11 @@
323323
# Other way to declare injection position
324324
INJECT_HERE_MARK = '%INJECT HERE%'
325325

326-
# Maximum length used for retrieving data over MySQL error based payload due to "known" problems with longer result strings
327-
MYSQL_ERROR_CHUNK_LENGTH = 50
326+
# Minimum chunk length used for retrieving data over error based payloads
327+
MIN_ERROR_CHUNK_LENGTH = 8
328328

329-
# Maximum length used for retrieving data over MSSQL error based payload due to trimming problems with longer result strings
330-
MSSQL_ERROR_CHUNK_LENGTH = 100
329+
# Maximum chunk length used for retrieving data over error based payloads
330+
MAX_ERROR_CHUNK_LENGTH = 1024
331331

332332
# Do not escape the injected statement if it contains any of the following SQL keywords
333333
EXCLUDE_UNESCAPE = ("WAITFOR DELAY ", " INTO DUMPFILE ", " INTO OUTFILE ", "CREATE ", "BULK ", "EXEC ", "RECONFIGURE ", "DECLARE ", "'%s'" % CHAR_INFERENCE_MARK)

lib/core/target.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,12 +403,18 @@ def _resumeHashDBValues():
403403
"""
404404

405405
kb.absFilePaths = hashDBRetrieve(HASHDB_KEYS.KB_ABS_FILE_PATHS, True) or kb.absFilePaths
406-
kb.chars = hashDBRetrieve(HASHDB_KEYS.KB_CHARS, True) or kb.chars
407-
kb.dynamicMarkings = hashDBRetrieve(HASHDB_KEYS.KB_DYNAMIC_MARKINGS, True) or kb.dynamicMarkings
408406
kb.brute.tables = hashDBRetrieve(HASHDB_KEYS.KB_BRUTE_TABLES, True) or kb.brute.tables
409407
kb.brute.columns = hashDBRetrieve(HASHDB_KEYS.KB_BRUTE_COLUMNS, True) or kb.brute.columns
408+
kb.chars = hashDBRetrieve(HASHDB_KEYS.KB_CHARS, True) or kb.chars
409+
kb.dynamicMarkings = hashDBRetrieve(HASHDB_KEYS.KB_DYNAMIC_MARKINGS, True) or kb.dynamicMarkings
410410
kb.xpCmdshellAvailable = hashDBRetrieve(HASHDB_KEYS.KB_XP_CMDSHELL_AVAILABLE) or kb.xpCmdshellAvailable
411411

412+
kb.errorChunkLength = hashDBRetrieve(HASHDB_KEYS.KB_ERROR_CHUNK_LENGTH)
413+
if kb.errorChunkLength and kb.errorChunkLength.isdigit():
414+
kb.errorChunkLength = int(kb.errorChunkLength)
415+
else:
416+
kb.errorChunkLength = None
417+
412418
conf.tmpPath = conf.tmpPath or hashDBRetrieve(HASHDB_KEYS.CONF_TMP_PATH)
413419

414420
for injection in hashDBRetrieve(HASHDB_KEYS.KB_INJECTIONS, True) or []:

lib/techniques/error/use.py

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@
3535
from lib.core.data import queries
3636
from lib.core.dicts import FROM_DUMMY_TABLE
3737
from lib.core.enums import DBMS
38+
from lib.core.enums import HASHDB_KEYS
3839
from lib.core.enums import HTTP_HEADER
3940
from lib.core.settings import CHECK_ZERO_COLUMNS_THRESHOLD
40-
from lib.core.settings import MYSQL_ERROR_CHUNK_LENGTH
41-
from lib.core.settings import MSSQL_ERROR_CHUNK_LENGTH
41+
from lib.core.settings import MIN_ERROR_CHUNK_LENGTH
42+
from lib.core.settings import MAX_ERROR_CHUNK_LENGTH
4243
from lib.core.settings import NULL
4344
from lib.core.settings import PARTIAL_VALUE_MARKER
4445
from lib.core.settings import SLOW_ORDER_COUNT_THRESHOLD
@@ -50,7 +51,7 @@
5051
from lib.request.connect import Connect as Request
5152
from lib.utils.progress import ProgressBar
5253

53-
def _oneShotErrorUse(expression, field=None):
54+
def _oneShotErrorUse(expression, field=None, chunkTest=False):
5455
offset = 1
5556
partialValue = None
5657
threadData = getCurrentThreadData()
@@ -63,12 +64,28 @@ def _oneShotErrorUse(expression, field=None):
6364

6465
threadData.resumed = retVal is not None and not partialValue
6566

66-
if Backend.isDbms(DBMS.MYSQL):
67-
chunkLength = MYSQL_ERROR_CHUNK_LENGTH
68-
elif Backend.isDbms(DBMS.MSSQL):
69-
chunkLength = MSSQL_ERROR_CHUNK_LENGTH
70-
else:
71-
chunkLength = None
67+
if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)) and kb.errorChunkLength is None and not chunkTest and not kb.testMode:
68+
debugMsg = "searching for error chunk length..."
69+
logger.debug(debugMsg)
70+
71+
current = MAX_ERROR_CHUNK_LENGTH
72+
while current >= MIN_ERROR_CHUNK_LENGTH:
73+
testChar = str(current % 10)
74+
testQuery = "SELECT %s('%s',%d)" % ("REPEAT" if Backend.isDbms(DBMS.MYSQL) else "REPLICATE", testChar, current)
75+
result = unArrayizeValue(_oneShotErrorUse(testQuery, chunkTest=True))
76+
if result and testChar in result:
77+
if result == testChar * current:
78+
kb.errorChunkLength = current
79+
break
80+
else:
81+
current = len(result) - len(kb.chars.stop)
82+
else:
83+
current = current / 2
84+
85+
if kb.errorChunkLength:
86+
hashDBWrite(HASHDB_KEYS.KB_ERROR_CHUNK_LENGTH, kb.errorChunkLength)
87+
else:
88+
kb.errorChunkLength = 0
7289

7390
if retVal is None or partialValue:
7491
try:
@@ -79,12 +96,12 @@ def _oneShotErrorUse(expression, field=None):
7996
if field:
8097
nulledCastedField = agent.nullAndCastField(field)
8198

82-
if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)) and not any(_ in field for _ in ("COUNT", "CASE")): # skip chunking of scalar expression (unneeded)
99+
if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)) and not any(_ in field for _ in ("COUNT", "CASE")) and kb.errorChunkLength and not chunkTest:
83100
extendedField = re.search(r"[^ ,]*%s[^ ,]*" % re.escape(field), expression).group(0)
84101
if extendedField != field: # e.g. MIN(surname)
85102
nulledCastedField = extendedField.replace(field, nulledCastedField)
86103
field = extendedField
87-
nulledCastedField = queries[Backend.getIdentifiedDbms()].substring.query % (nulledCastedField, offset, chunkLength)
104+
nulledCastedField = queries[Backend.getIdentifiedDbms()].substring.query % (nulledCastedField, offset, kb.errorChunkLength)
88105

89106
# Forge the error-based SQL injection request
90107
vector = kb.injection.data[kb.technique].vector
@@ -125,10 +142,11 @@ def _oneShotErrorUse(expression, field=None):
125142
threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)
126143

127144
if trimmed:
128-
warnMsg = "possible server trimmed output detected "
129-
warnMsg += "(due to its length and/or content): "
130-
warnMsg += safecharencode(trimmed)
131-
logger.warn(warnMsg)
145+
if not chunkTest:
146+
warnMsg = "possible server trimmed output detected "
147+
warnMsg += "(due to its length and/or content): "
148+
warnMsg += safecharencode(trimmed)
149+
logger.warn(warnMsg)
132150

133151
if not kb.testMode:
134152
check = "(?P<result>.*?)%s" % kb.chars.stop[:2]
@@ -146,8 +164,8 @@ def _oneShotErrorUse(expression, field=None):
146164
else:
147165
retVal += output if output else ''
148166

149-
if output and len(output) >= chunkLength:
150-
offset += chunkLength
167+
if output and kb.errorChunkLength and len(output) >= kb.errorChunkLength and not chunkTest:
168+
offset += kb.errorChunkLength
151169
else:
152170
break
153171

0 commit comments

Comments
 (0)