From 19fb2e3dcf8b2ffafc0b4e22e53708e7fbf74a1f Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 13 Sep 2010 13:31:01 +0000 Subject: [PATCH] fix for Bug #165 --- lib/controller/checks.py | 29 ++++++++++++++ lib/core/common.py | 79 ++++++++++++++++++++++++--------------- lib/core/option.py | 1 + lib/request/comparison.py | 19 +++++++++- 4 files changed, 96 insertions(+), 32 deletions(-) diff --git a/lib/controller/checks.py b/lib/controller/checks.py index eba1abb7b..e03c6eeb3 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -28,8 +28,10 @@ import time from lib.core.agent import agent from lib.core.common import getUnicode +from lib.core.common import preparePageForLineComparison from lib.core.common import randomInt from lib.core.common import randomStr +from lib.core.common import DynamicContentItem from lib.core.convert import md5hash from lib.core.data import conf from lib.core.data import kb @@ -278,6 +280,31 @@ def checkDynParam(place, parameter, value): return condition +def checkDynamicContent(firstPage, secondPage): + infoMsg = "testing for dynamic content lines" + logger.info(infoMsg) + + linesFirst = preparePageForLineComparison(firstPage) + linesSecond = preparePageForLineComparison(secondPage) + + if len(linesFirst) == len(linesSecond): + lastLineNumber = None + pageLinesNumber = len(linesFirst) + for i in range(0, pageLinesNumber): + if (linesFirst[i] != linesSecond[i]): + if lastLineNumber == i - 1: + item = kb.dynamicContent[-1] + if isinstance(item.lineNumber, int): + item.lineNumber = [item.lineNumber] + item.lineNumber.append(i) + else: + kb.dynamicContent.append(DynamicContentItem(i, pageLinesNumber, linesFirst[i-1] if i > 0 else None, linesFirst[i+1] if i < pageLinesNumber - 1 else None)) + lastLineNumber = i + + if kb.dynamicContent: + infoMsg = "found probably removable dynamic lines" + logger.info(infoMsg) + def checkStability(): """ This function checks if the URL content is stable requesting the @@ -318,6 +345,8 @@ def checkStability(): warnMsg += "string or regular expression to match on" logger.warn(warnMsg) + checkDynamicContent(firstPage, secondPage) + return condition def checkString(): diff --git a/lib/core/common.py b/lib/core/common.py index 590f9efff..5858a6ab9 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -522,7 +522,7 @@ def randomStr(length=4, lowercase=False): rndStr = "".join([random.choice(string.letters) for _ in xrange(0, length)]) return rndStr - + def sanitizeStr(inpStr): """ @param inpStr: inpStr to sanitize: cast to str datatype and replace @@ -566,7 +566,7 @@ def banner(): %s - %s %s """ % (VERSION_STRING, DESCRIPTION, SITE) - + def parsePasswordHash(password): blank = " " * 8 @@ -597,7 +597,7 @@ def cleanQuery(query): upperQuery = upperQuery.replace(queryMatch.group(1), sqlStatement.upper()) return upperQuery - + def setPaths(): # sqlmap paths paths.SQLMAP_CONTRIB_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "lib", "contrib") @@ -623,7 +623,7 @@ def setPaths(): paths.MYSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "mysql.xml") paths.ORACLE_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "oracle.xml") paths.PGSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "postgresql.xml") - + def weAreFrozen(): """ Returns whether we are frozen via py2exe. @@ -646,7 +646,7 @@ def parseTargetDirect(): for dbms in SUPPORTED_DBMS: details = re.search("^(?P%s)://(?P(?P.+?)\:(?P.*?)\@)?(?P(?P.+?)\:(?P[\d]+)\/)?(?P[\w\d\ \:\.\_\-\/\\\\]+?)$" % dbms, conf.direct, re.I) - + if details: conf.dbms = details.group('dbms') @@ -1068,6 +1068,12 @@ def sanitizeAsciiString(subject): else: return None +def preparePageForLineComparison(page): + retVal = page + if isinstance(page, basestring): + return page.replace("><", ">\n<").replace("
", "\n").splitlines() + return retVal + def decloakToNamedTemporaryFile(filepath, name=None): retVal = NamedTemporaryFile() @@ -1410,32 +1416,6 @@ def getBruteUnicode(string): retVal += unichr(ord(char)) return retVal -class UnicodeRawConfigParser(RawConfigParser): - def write(self, fp): - """ - Write an .ini-format representation of the configuration state. - """ - - if self._defaults: - fp.write("[%s]\n" % DEFAULTSECT) - - for (key, value) in self._defaults.items(): - fp.write("%s = %s\n" % (key, getUnicode(value).replace('\n', '\n\t'))) - - fp.write("\n") - - for section in self._sections: - fp.write("[%s]\n" % section) - - for (key, value) in self._sections[section].items(): - if key != "__name__": - if value is None: - fp.write("%s\n" % (key)) - else: - fp.write("%s = %s\n" % (key, getUnicode(value).replace('\n', '\n\t'))) - - fp.write("\n") - # http://boredzo.org/blog/archives/2007-01-06/longest-common-prefix-in-python-2 def longestCommonPrefix(*sequences): if len(sequences) == 1: @@ -1489,3 +1469,40 @@ def smokeTest(): infoMsg += "FAILED" logger.error(infoMsg) return retVal + +class UnicodeRawConfigParser(RawConfigParser): + def write(self, fp): + """ + Write an .ini-format representation of the configuration state. + """ + + if self._defaults: + fp.write("[%s]\n" % DEFAULTSECT) + + for (key, value) in self._defaults.items(): + fp.write("%s = %s\n" % (key, getUnicode(value).replace('\n', '\n\t'))) + + fp.write("\n") + + for section in self._sections: + fp.write("[%s]\n" % section) + + for (key, value) in self._sections[section].items(): + if key != "__name__": + if value is None: + fp.write("%s\n" % (key)) + else: + fp.write("%s = %s\n" % (key, getUnicode(value).replace('\n', '\n\t'))) + + fp.write("\n") + +class DynamicContentItem: + """ + Represents line in content page with dynamic properties (candidate for removal prior detection phase) + """ + + def __init__(self, lineNumber, pageTotal, lineContentBefore, lineContentAfter): + self.lineNumber = lineNumber + self.pageTotal = pageTotal + self.lineContentBefore = lineContentBefore + self.lineContentAfter = lineContentAfter diff --git a/lib/core/option.py b/lib/core/option.py index 55c7cc5a7..14141967f 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1001,6 +1001,7 @@ def __setKnowledgeBaseAttributes(): kb.dep = None kb.docRoot = None + kb.dynamicContent = [] kb.headersCount = 0 kb.headersFp = {} kb.htmlFp = [] diff --git a/lib/request/comparison.py b/lib/request/comparison.py index 99dfda594..c35c5f67f 100644 --- a/lib/request/comparison.py +++ b/lib/request/comparison.py @@ -24,7 +24,9 @@ Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import re +from lib.core.common import preparePageForLineComparison from lib.core.data import conf +from lib.core.data import kb from lib.core.data import logger from lib.core.session import setMatchRatio @@ -59,6 +61,21 @@ def comparison(page, headers=None, getSeqMatcher=False): if conf.regexp: return re.search(conf.regexp, page, re.I | re.M) is not None + # Dynamic content lines to be excluded before calculating page hash + if kb.dynamicContent: + lines = preparePageForLineComparison(page) + for item in kb.dynamicContent: + if len(lines) == item.pageTotal: + before = item.lineNumber - 1 if isinstance(item.lineNumber, int) else item.lineNumber[0] - 1 + after = item.lineNumber + 1 if isinstance(item.lineNumber, int) else item.lineNumber[-1] + 1 + if (item.lineContentBefore and lines[before] != item.lineContentBefore) or (item.lineContentAfter and lines[after] != item.lineContentAfter): + continue + if isinstance(item.lineNumber, int): + page = page.replace(lines[item.lineNumber], '') + else: + for i in item.lineNumber: + page = page.replace(lines[i], '') + if conf.seqLock: conf.seqLock.acquire() @@ -80,7 +97,7 @@ def comparison(page, headers=None, getSeqMatcher=False): conf.matchRatio = 0.900 if conf.matchRatio is not None: - setMatchRatio() + setMatchRatio() # If it has been requested to return the ratio and not a comparison # response