diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 1c44cdae1..d39c46234 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -163,10 +163,10 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 9af5fdfa8b2425d404d86ab08d3644caa95bcf77605551f5da482a59d1e54a22 extra/vulnserver/vulnserver.py a2bf70d7f87c3a4e0675c0bad54119a4e04efa6ea2730a8338d5aebcd995630e lib/controller/action.py 736715a73941a06e5d3d349dd01a1f1b171f54eb4c374c6752b2cc44b0977ffe lib/controller/checks.py -666935b658074dc9c42153622b75d4ec7bfe56fbe0742de827a5d30a1a0f9d96 lib/controller/controller.py +2086100cd7a78a4e8c12d72bd4f5b414ec6b3f49926e83285494534140e60ce7 lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py -9c5764c92ce536d1f0f96200359ee5ef1f37f9128769bf990cb77f1d1f8e17b1 lib/core/agent.py +48ffe93d61734e16c3b20153b51595853d9ac1fbcf0b537e0e61e957b0c0bfa6 lib/core/agent.py c51c33501cc905586a9aaac93b06f2ac6f71628d032a7dc39fd0ef05d7ee3856 lib/core/bigarray.py f73bbb05c1cfd642e8f556f3047f8418bed07b06f555d445b6f14c03c105b87a lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py @@ -189,7 +189,7 @@ b14628a6c9327d110afe50b01f3171f64f61823343b8de89596e854b00b74928 lib/core/dump. 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -459f3adf2d8acfe810410faea7fa5bddfc2ee0b1af284413a4a9fd1d11334047 lib/core/settings.py +c84d55438df9338804398ec3d8bc7b95cb4024dd356db9aeb4ea1cb19edcb794 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py @@ -250,7 +250,7 @@ bde75d41ac3e5747b96d2af4c33922573158cb43b48714a28490d6720dd85d89 lib/techniques 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/ssti/__init__.py 14637b64878248e5965887b07aa68e62615dac88e2ffc6c3a581430bdd4e309e lib/techniques/ssti/inject.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/union/__init__.py -ceec65f8cb7c3254c4671351c837418c76ac5bc55ccbc40779f67231b54d7085 lib/techniques/union/test.py +f6678ac1342f8d234ed32ae69be5ac5d7837393e9348929ec029c9764c030e82 lib/techniques/union/test.py c68f8259e0a89a556d049f227041849df584313bd1b5349b02f74a47778c901c lib/techniques/union/use.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/xpath/__init__.py c61816c9dba9f6cc2223aed1a923f95130979e5f0a88ec254ee667d955ed2734 lib/techniques/xpath/inject.py diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 67b9278b1..ba27f49aa 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -561,9 +561,10 @@ def start(): checkNullConnection() if (len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None)) and (kb.injection.place is None or kb.injection.parameter is None): - if not any((conf.string, conf.notString, conf.regexp)) and PAYLOAD.TECHNIQUE.BOOLEAN in conf.technique: - # NOTE: this is not needed anymore, leaving only to display - # a warning message to the user in case the page is not stable + if not any((conf.string, conf.notString, conf.regexp)) and any(_ in conf.technique for _ in (PAYLOAD.TECHNIQUE.BOOLEAN, PAYLOAD.TECHNIQUE.UNION)): + # NOTE: besides the not-stable warning, this marks dynamic content for removal, which + # UNION column-count detection relies on too (it compares pages) - so it must run when + # UNION is tested even if BOOLEAN is excluded (e.g. '--technique=U' on a dynamic page) checkStability() # Do a little prioritization reorder of a testable parameter list diff --git a/lib/core/agent.py b/lib/core/agent.py index ec781a43e..ad67ade14 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -958,12 +958,19 @@ class Agent(object): if not infoFile: query = _collate(query) + # A fuzzy-discovered per-column type template (kb.unionTemplate, e.g. ['1234', '%s', '5678']) + # forces type-compatible fillers on strict DBMSes (e.g. Apache Derby, which rejects bare NULL + # and demands UNION column-type parity); '%s' marks the slot carrying the injected expression. + template = kb.unionTemplate if isinstance(kb.unionTemplate, (list, tuple)) and len(kb.unionTemplate) == count else None + for element in xrange(0, count): if element > 0: unionQuery += ',' if conf.uValues and conf.uValues.count(',') + 1 == count: unionQuery += conf.uValues.split(',')[element] + elif template is not None: + unionQuery += query if template[element] == "%s" else template[element] elif element == position: unionQuery += query else: @@ -985,7 +992,9 @@ class Agent(object): if element > 0: unionQuery += ',' - if element == position: + if template is not None: + unionQuery += _collate(multipleUnions) if template[element] == "%s" else template[element] + elif element == position: unionQuery += _collate(multipleUnions) else: unionQuery += char diff --git a/lib/core/settings.py b/lib/core/settings.py index fdfb62707..f6a5115e6 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from lib.core.enums import OS from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.8" +VERSION = "1.10.7.9" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) @@ -141,6 +141,9 @@ FUZZ_UNION_ERROR_REGEX = r"(?i)data\s?type|mismatch|comparable|compatible|conver # Upper threshold for starting the fuzz(y) UNION test FUZZ_UNION_MAX_COLUMNS = 10 +# Maximum number of probe requests the fuzz(y) UNION test may issue (bounds its otherwise exponential type-combination search when run automatically) +FUZZ_UNION_MAX_REQUESTS = 80 + # Regular expression used for recognition of generic maximum connection messages MAX_CONNECTIONS_REGEX = r"\bmax.{1,100}\bconnection" diff --git a/lib/techniques/union/test.py b/lib/techniques/union/test.py index 0a8facf78..5c2022c3a 100644 --- a/lib/techniques/union/test.py +++ b/lib/techniques/union/test.py @@ -38,6 +38,7 @@ from lib.core.enums import FUZZ_UNION_COLUMN from lib.core.enums import PAYLOAD from lib.core.settings import FUZZ_UNION_ERROR_REGEX from lib.core.settings import FUZZ_UNION_MAX_COLUMNS +from lib.core.settings import FUZZ_UNION_MAX_REQUESTS from lib.core.settings import LIMITED_ROWS_TEST_NUMBER from lib.core.settings import MAX_RATIO from lib.core.settings import MIN_RATIO @@ -190,12 +191,14 @@ def _fuzzUnionCols(place, parameter, prefix, suffix): choices = getPublicTypeMembers(FUZZ_UNION_COLUMN, True) random.shuffle(choices) + attempts = 0 for candidate in itertools.product(choices, repeat=kb.orderByColumns): - if retVal: + if retVal or attempts >= FUZZ_UNION_MAX_REQUESTS: # bound the exponential type-combination search break elif FUZZ_UNION_COLUMN.STRING not in candidate: continue else: + attempts += 1 candidate = [_.replace(FUZZ_UNION_COLUMN.INTEGER, str(randomInt())).replace(FUZZ_UNION_COLUMN.STRING, "'%s'" % randomStr(20)) for _ in candidate] query = agent.prefixQuery("UNION ALL SELECT %s%s" % (','.join(candidate), FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), "")), prefix=prefix) @@ -332,16 +335,21 @@ def _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix) if Backend.getIdentifiedDbms() and kb.orderByColumns and kb.orderByColumns < FUZZ_UNION_MAX_COLUMNS: if kb.fuzzUnionTest is None: msg = "do you want to (re)try to find proper " - msg += "UNION column types with fuzzy test? [y/N] " + msg += "UNION column types with a fuzzy test? [Y/n] " - kb.fuzzUnionTest = readInput(msg, default='N', boolean=True) + kb.fuzzUnionTest = readInput(msg, default='Y', boolean=True) if kb.fuzzUnionTest: kb.unionTemplate = _fuzzUnionCols(place, parameter, prefix, suffix) + # apply the discovered per-column type template through a normal confirmation so + # the resulting vector (and later extraction) is built with type-compatible columns + if kb.unionTemplate: + validPayload, vector = _unionConfirm(comment, place, parameter, prefix, suffix, len(kb.unionTemplate)) + warnMsg = "if UNION based SQL injection is not detected, " warnMsg += "please consider " - if not conf.uChar and count > 1 and kb.uChar == NULL and conf.uValues is None: + if not all((validPayload, vector)) and not conf.uChar and count > 1 and kb.uChar == NULL and conf.uValues is None: message = "injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] " if not readInput(message, default='Y', boolean=True): @@ -380,6 +388,8 @@ def unionTest(comment, place, parameter, value, prefix, suffix): negativeLogic = kb.negativeLogic setTechnique(PAYLOAD.TECHNIQUE.UNION) + kb.unionTemplate = None # reset any per-column type template carried over from a previous parameter + try: if negativeLogic: pushValue(kb.negativeLogic)