diff --git a/lib/controller/checks.py b/lib/controller/checks.py index ab020b93c..cb5d57b8e 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -20,6 +20,7 @@ from lib.core.common import extractRegexResult from lib.core.common import extractTextTagContent from lib.core.common import findDynamicContent from lib.core.common import Format +from lib.core.common import getFilteredPageContent from lib.core.common import getLastRequestHTTPError from lib.core.common import getPublicTypeMembers from lib.core.common import getSafeExString @@ -63,6 +64,7 @@ from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapNoneDataException from lib.core.exception import SqlmapSilentQuitException from lib.core.exception import SqlmapUserQuitException +from lib.core.settings import CANDIDATE_SENTENCE_MIN_LENGTH from lib.core.settings import DEFAULT_GET_POST_DELIMITER from lib.core.settings import DUMMY_NON_SQLI_CHECK_APPENDIX from lib.core.settings import FI_ERROR_REGEX @@ -478,6 +480,26 @@ def checkSqlInjection(place, parameter, value): injectable = True + elif threadData.lastComparisonRatio > UPPER_RATIO_BOUND and not any((conf.string, conf.notString, conf.regexp, conf.code, kb.nullConnection)): + originalSet = set(getFilteredPageContent(kb.pageTemplate, True, "\n").split("\n")) + trueSet = set(getFilteredPageContent(truePage, True, "\n").split("\n")) + falseSet = set(getFilteredPageContent(falsePage, True, "\n").split("\n")) + + if originalSet == trueSet != falseSet: + candidates = trueSet - falseSet + + if candidates: + candidates = sorted(candidates, key=lambda _: len(_)) + for candidate in candidates: + if re.match(r"\A[\w.,! ]+\Z", candidate) and ' ' in candidate and len(candidate) > CANDIDATE_SENTENCE_MIN_LENGTH: + conf.string = candidate + injectable = True + + infoMsg = "%s parameter '%s' appears to be '%s' injectable (with --string=\"%s\")" % (paramType, parameter, title, repr(conf.string).lstrip('u').strip("'")) + logger.info(infoMsg) + + break + if injectable: if kb.pageStable and not any((conf.string, conf.notString, conf.regexp, conf.code, kb.nullConnection)): if all((falseCode, trueCode)) and falseCode != trueCode: diff --git a/lib/core/common.py b/lib/core/common.py index 2d9653778..da9e15aab 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1755,7 +1755,7 @@ def safeStringFormat(format_, params): break return retVal -def getFilteredPageContent(page, onlyText=True): +def getFilteredPageContent(page, onlyText=True, split=" "): """ Returns filtered page content without script, style and/or comments or all HTML tags @@ -1768,10 +1768,10 @@ def getFilteredPageContent(page, onlyText=True): # only if the page's charset has been successfully identified if isinstance(page, unicode): - retVal = re.sub(r"(?si)||%s" % (r"|<[^>]+>|\t|\n|\r" if onlyText else ""), " ", page) - while retVal.find(" ") != -1: - retVal = retVal.replace(" ", " ") - retVal = htmlunescape(retVal.strip()) + retVal = re.sub(r"(?si)||%s" % (r"|<[^>]+>|\t|\n|\r" if onlyText else ""), split, page) + while retVal.find(2 * split) != -1: + retVal = retVal.replace(2 * split, split) + retVal = htmlunescape(retVal.strip().strip(split)) return retVal diff --git a/lib/core/settings.py b/lib/core/settings.py index 46916e04b..fa49fbf41 100755 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from lib.core.enums import DBMS_DIRECTORY_NAME from lib.core.enums import OS # sqlmap version (...) -VERSION = "1.1.2.3" +VERSION = "1.1.2.4" 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) @@ -359,6 +359,9 @@ MIN_RATIO = 0.0 # Maximum value for comparison ratio MAX_RATIO = 1.0 +# Minimum length of sentence for automatic choosing of --string (in case of high matching ratio) +CANDIDATE_SENTENCE_MIN_LENGTH = 10 + # Character used for marking injectable position inside provided data CUSTOM_INJECTION_MARK_CHAR = '*' diff --git a/lib/core/threads.py b/lib/core/threads.py index 3b807ca27..67d428000 100644 --- a/lib/core/threads.py +++ b/lib/core/threads.py @@ -46,6 +46,7 @@ class _ThreadData(threading.local): self.lastComparisonPage = None self.lastComparisonHeaders = None self.lastComparisonCode = None + self.lastComparisonRatio = None self.lastErrorPage = None self.lastHTTPError = None self.lastRedirectMsg = None diff --git a/lib/request/comparison.py b/lib/request/comparison.py index 343656574..82915d281 100644 --- a/lib/request/comparison.py +++ b/lib/request/comparison.py @@ -144,6 +144,9 @@ def _comparison(page, headers, code, getRatioValue, pageLength): kb.matchRatio = ratio logger.debug("setting match ratio for current parameter to %.3f" % kb.matchRatio) + if kb.testMode: + threadData.lastComparisonRatio = ratio + # If it has been requested to return the ratio and not a comparison # response if getRatioValue: diff --git a/txt/checksum.md5 b/txt/checksum.md5 index 39ff84848..5ecb447c9 100644 --- a/txt/checksum.md5 +++ b/txt/checksum.md5 @@ -20,13 +20,13 @@ c55b400b72acc43e0e59c87dd8bb8d75 extra/shellcodeexec/windows/shellcodeexec.x32. 310efc965c862cfbd7b0da5150a5ad36 extra/sqlharvest/__init__.py 7713aa366c983cdf1f3dbaa7383ea9e1 extra/sqlharvest/sqlharvest.py 5df358defc488bee9b40084892e3d1cb lib/controller/action.py -699fd4757390aedb5ad17f4316d17972 lib/controller/checks.py +9cb94acd4c59822a5e1a258c4d1a4860 lib/controller/checks.py fa72e4d6eda241725d90738d61e7d55d lib/controller/controller.py b3eec7f44bcc5d784d171a187b7fe8cb lib/controller/handler.py 310efc965c862cfbd7b0da5150a5ad36 lib/controller/__init__.py 19905ecb4437b94512cf21d5f1720091 lib/core/agent.py 6cc95a117fbd34ef31b9aa25520f0e31 lib/core/bigarray.py -9ca4206c06f8a2a859b076ab7520c3ea lib/core/common.py +15c19630897cff73744fb2866719d1e9 lib/core/common.py 5065a4242a8cccf72f91e22e1007ae63 lib/core/convert.py a8143dab9d3a27490f7d49b6b29ea530 lib/core/data.py 7936d78b1a7f1f008ff92bf2f88574ba lib/core/datatype.py @@ -45,12 +45,12 @@ e544108e2238d756c94a240e8a1ce061 lib/core/optiondict.py d8e9250f3775119df07e9070eddccd16 lib/core/replication.py 785f86e3f963fa3798f84286a4e83ff2 lib/core/revision.py 40c80b28b3a5819b737a5a17d4565ae9 lib/core/session.py -38927d9aadc879d0ee2493813ee288b7 lib/core/settings.py +bb7501fea707b51516519ea84f42de7d lib/core/settings.py d91291997d2bd2f6028aaf371bf1d3b6 lib/core/shell.py 2ad85c130cc5f2b3701ea85c2f6bbf20 lib/core/subprocessng.py afd0636d2e93c23f4f0a5c9b6023ea17 lib/core/target.py 8970b88627902239d695280b1160e16c lib/core/testing.py -1504e8c6bdd69edc17b5f240eaa73fb2 lib/core/threads.py +5521241c750855a4e44747fbac7771c6 lib/core/threads.py ad74fc58fc7214802fd27067bce18dd2 lib/core/unescaper.py 1f1fa616b5b19308d78c610ec8046399 lib/core/update.py 4d13ed693401a498b6d073a2a494bd83 lib/core/wordlist.py @@ -66,7 +66,7 @@ ad74fc58fc7214802fd27067bce18dd2 lib/core/unescaper.py a0444cc351cd6d29015ad16d9eb46ff4 lib/parse/sitemap.py 403d873f1d2fd0c7f73d83f104e41850 lib/request/basicauthhandler.py 6d04ee525e75bf0082e9f1f6d8506546 lib/request/basic.py -4e89d0e13de2eb3576f5412b21e9b648 lib/request/comparison.py +ef48de622b0a6b4a71df64b0d2785ef8 lib/request/comparison.py 1d4955fa22ca6ba17ce4fc2e0d93f1e2 lib/request/connect.py fb6b788d0016ab4ec5e5f661f0f702ad lib/request/direct.py cc1163d38e9b7ee5db2adac6784c02bb lib/request/dns.py