Skip to content

Commit 1dcca2c

Browse files
committed
Implementing automatic auto-recover on charset mismatch (E/U)
1 parent ffffe2d commit 1dcca2c

3 files changed

Lines changed: 66 additions & 2 deletions

File tree

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.7.224"
23+
VERSION = "1.10.7.225"
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)
@@ -909,7 +909,7 @@
909909
HASHDB_END_TRANSACTION_RETRIES = 3
910910

911911
# Unique milestone value used for forced deprecation of old HashDB values (e.g. when changing the hash/serialization mechanism)
912-
HASHDB_MILESTONE_VALUE = "MvKpZrBqTn" # python -c 'import random, string; print "".join(random.sample(string.ascii_letters, 10))'
912+
HASHDB_MILESTONE_VALUE = "CvHUbaSNZL" # python -c 'import random, string; print "".join(random.sample(string.ascii_letters, 10))'
913913

914914
# Warn user of possible delay due to large page dump in full UNION query injections
915915
LARGE_OUTPUT_THRESHOLD = 1024 ** 2

lib/request/inject.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
from lib.core.settings import GET_VALUE_UPPERCASE_KEYWORDS
6363
from lib.core.settings import IS_TTY
6464
from lib.core.settings import INFERENCE_MARKER
65+
from lib.core.settings import INVALID_UNICODE_PRIVATE_AREA
6566
from lib.core.settings import MAX_TECHNIQUES_PER_VALUE
6667
from lib.core.settings import SQL_SCALAR_REGEX
6768
from lib.core.settings import UNICODE_ENCODING
@@ -575,6 +576,24 @@ def _etaTicker():
575576

576577
return results
577578

579+
def _pageCharsetCorrupted(value):
580+
"""
581+
True if a retrieved value carries reversibly-decoded (undecodable) bytes - a sign that the
582+
web page charset could not represent the DBMS data (cf. the 'reversible' codec). Such a
583+
UNION/error value is silently corrupt and should be re-fetched via DBMS-side hexadecimal.
584+
"""
585+
586+
retVal = [False]
587+
588+
def _(item):
589+
if not retVal[0] and isinstance(item, six.string_types):
590+
if re.search(r"\\x[89a-f][0-9a-f]", item) or (INVALID_UNICODE_PRIVATE_AREA and any(0xF0000 <= ord(_) <= 0xF00FF for _ in item)):
591+
retVal[0] = True
592+
return item
593+
594+
applyFunctionRecursively(value, _)
595+
return retVal[0]
596+
578597
@lockedmethod
579598
@stackedmethod
580599
def getValue(expression, blind=True, union=True, error=True, time=True, fromUser=False, expected=None, batch=False, unpack=True, resumeValue=True, charsetType=None, firstChar=None, lastChar=None, dump=False, suppressOutput=None, expectingNone=False, safeCharEncode=True):
@@ -672,6 +691,28 @@ def getValue(expression, blind=True, union=True, error=True, time=True, fromUser
672691
count += 1
673692
found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE
674693

694+
# Auto-recover from a page/DBMS charset mismatch: a UNION/error value carrying
695+
# undecodable bytes (the page charset couldn't represent the DBMS data) is silently
696+
# corrupt. Re-fetch it via DBMS-side hex, which travels as ASCII regardless of the
697+
# page charset - no user '--hex'/'--encoding' knowledge required. Gated, so clean
698+
# or ASCII data pays nothing.
699+
if (found and not conf.hexConvert and not conf.binaryFields and expected not in (EXPECTED.BOOL, EXPECTED.INT)
700+
and getTechnique() in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)
701+
and Backend.getIdentifiedDbms() and hasattr(queries[Backend.getIdentifiedDbms()], "hex")
702+
and _pageCharsetCorrupted(value)):
703+
warnMsg = "retrieved data appears corrupted because of a charset mismatch between the "
704+
warnMsg += "DBMS and the web page. Re-fetching using hexadecimal encoding"
705+
singleTimeWarnMessage(warnMsg)
706+
707+
conf.hexConvert = True
708+
try:
709+
_value = _goUnion(query, unpack, dump) if getTechnique() == PAYLOAD.TECHNIQUE.UNION else errorUse(query, dump)
710+
finally:
711+
conf.hexConvert = False
712+
713+
if _value is not None:
714+
value = _value
715+
675716
if found and conf.dnsDomain:
676717
_ = "".join(filterNone(key if isTechniqueAvailable(value) else None for key, value in {'E': PAYLOAD.TECHNIQUE.ERROR, 'Q': PAYLOAD.TECHNIQUE.QUERY, 'U': PAYLOAD.TECHNIQUE.UNION}.items()))
677718
warnMsg = "option '--dns-domain' will be ignored "

tests/test_techniques.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1778,6 +1778,29 @@ def test_timeless_is_concurrency_safe(self):
17781778
self.assertTrue(self._elig(8, True, {PAYLOAD.TECHNIQUE.TIME}, timeless=object()))
17791779

17801780

1781+
class TestCharsetCorruptionDetection(unittest.TestCase):
1782+
"""UNION/error charset-mismatch auto-hex trigger: _pageCharsetCorrupted fires on
1783+
reversibly-decoded high bytes (the '\\xNN' marker) and stays quiet on clean data."""
1784+
1785+
def test_detects_reversible_high_bytes(self):
1786+
# e.g. GBK bytes mis-decoded under utf-8 -> reversible '\xNN' escapes for the bad bytes
1787+
self.assertTrue(inject._pageCharsetCorrupted(u"\\xd6\\xd0\\xce\\xe2"))
1788+
1789+
def test_detects_inside_nested_rows(self):
1790+
self.assertTrue(inject._pageCharsetCorrupted([[u"1", u"caf\\xe9"], [u"2", u"ok"]]))
1791+
1792+
def test_clean_ascii_not_flagged(self):
1793+
self.assertFalse(inject._pageCharsetCorrupted(u"hello world"))
1794+
1795+
def test_clean_unicode_not_flagged(self):
1796+
# correctly-decoded unicode must not trigger a needless hex re-fetch
1797+
self.assertFalse(inject._pageCharsetCorrupted(u"\u4e2d\u6587\u6d4b\u8bd5"))
1798+
1799+
def test_low_hex_escape_not_flagged(self):
1800+
# a literal low '\x41' (ASCII 'A') is not an undecodable-byte marker
1801+
self.assertFalse(inject._pageCharsetCorrupted(u"literal \\x41 text"))
1802+
1803+
17811804
if __name__ == "__main__":
17821805
unittest.main(verbosity=2)
17831806

0 commit comments

Comments
 (0)