From 38c9627700dbbe43114ea1b3ad66b4373122c4ff Mon Sep 17 00:00:00 2001 From: Bernardo Damele Date: Fri, 5 Dec 2008 15:34:13 +0000 Subject: [PATCH] Minor enhancemet to support also --regexp, --excl-str and --excl-reg options rather than only --string when comparing HTTP responses page content --- doc/ChangeLog | 22 +++++++++---- doc/THANKS | 3 ++ lib/core/optiondict.py | 5 ++- lib/core/update.py | 6 ++-- lib/parse/cmdline.py | 16 +++++++-- lib/request/comparison.py | 69 +++++++++++++++++++++++++++++++++++++++ lib/request/connect.py | 21 +++++------- plugins/dbms/mysql.py | 6 ++-- sqlmap.conf | 33 +++++++++++++++---- 9 files changed, 145 insertions(+), 36 deletions(-) create mode 100644 lib/request/comparison.py diff --git a/doc/ChangeLog b/doc/ChangeLog index 4bebfcfbe..f3fa28ccf 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,26 +1,34 @@ sqlmap (0.6.3-1) stable; urgency=low - * Major enhancement to support stacked queries when the web application - supports it which will be used in the long run by takeover - functionality; * Major enhancement to get list of targets to test from Burp proxy (http://portswigger.net/suite/) requests log file path or WebScarab proxy (http://www.owasp.org/index.php/Category:OWASP_WebScarab_Project) - 'conversations/' folder path; + 'conversations/' folder path by providing option -l ; + * Major enhancement to support stacked queries (multiple staatements) + when the web application supports them which is useful for time based + blind sql injection test and will be used someday also by takeover + functionality; * Minor enhancement to test if the injectable parameter is affected by - a time based blind SQL injection technique; + a time based blind SQL injection technique by providing option + --time-test; * Minor enhancement to fingerprint the web server operating system and the web application technology by parsing some HTTP response headers; * Minor enhancement to fingerprint the back-end DBMS operating system by parsing the DBMS banner value when -b option is provided; * Minor enhancement to be able to specify the number of seconds before - timeout the connection, default is set to 10 seconds; + timeout the connection by providing option --timeout #, default is set + to 10 seconds and must be 3 or higher; * Minor enhancement to be able to specify the number of seconds to wait - between each HTTP request providing option --delay #; + between each HTTP request by providing option --delay #; * Minor enhancement to be able to enumerate table columns and dump table entries, also when the database name is not provided, by using the current database on MySQL and Microsoft SQL Server, the 'public' scheme on PostgreSQL and the 'USERS' TABLESPACE_NAME on Oracle; + * Minor enhancemet to support also --regexp, --excl-str and --excl-reg + options rather than only --string when comparing HTTP responses page + content; + * Minor improvement to be able to provide CU as user value (-U) when + enumerating users privileges or users passwords; * Minor improvement to set by default in all HTTP requests the standard client HTTP headers (Accept, Accept-Encoding, etc); * Minor improvements to sqlmap Debian package files: sqlmap uploaded diff --git a/doc/THANKS b/doc/THANKS index a5ec9bf86..9388c9202 100644 --- a/doc/THANKS +++ b/doc/THANKS @@ -5,6 +5,9 @@ Chip Andrews at SQLSecurity.com and permission to implement the update feature taking data from his site +Jack Butler + for providing me with the sqlmap site favicon + Karl Chen for providing with the multithreading patch for the inference algorithm diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index bf78bfc21..07dc6d165 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -49,8 +49,11 @@ optDict = { "Injection": { "testParameter": "string", - "string": "string", "dbms": "string", + "string": "string", + "regexp": "string", + "eString": "string", + "eRegexp": "string", }, "Techniques": { diff --git a/lib/core/update.py b/lib/core/update.py index fec2f80e0..4fcad8cc9 100644 --- a/lib/core/update.py +++ b/lib/core/update.py @@ -53,7 +53,7 @@ def __updateMSSQLXML(): logger.info(infoMsg) try: - mssqlVersionsHtmlString = Request.getPage(url=MSSQL_VERSIONS_URL, direct=True) + mssqlVersionsHtmlString, _ = Request.getPage(url=MSSQL_VERSIONS_URL, direct=True) except sqlmapConnectionException, _: __mssqlPath = urlparse.urlsplit(MSSQL_VERSIONS_URL) __mssqlHostname = __mssqlPath[1] @@ -231,7 +231,7 @@ def __updateSqlmap(): logger.debug(debugMsg) try: - sqlmapNewestVersion = Request.getPage(url=SQLMAP_VERSION_URL, direct=True) + sqlmapNewestVersion, _ = Request.getPage(url=SQLMAP_VERSION_URL, direct=True) except sqlmapConnectionException, _: __sqlmapPath = urlparse.urlsplit(SQLMAP_VERSION_URL) __sqlmapHostname = __sqlmapPath[1] @@ -271,7 +271,7 @@ def __updateSqlmap(): sqlmapBinaryStringUrl = SQLMAP_SOURCE_URL % sqlmapNewestVersion try: - sqlmapBinaryString = Request.getPage(url=sqlmapBinaryStringUrl, direct=True) + sqlmapBinaryString, _ = Request.getPage(url=sqlmapBinaryStringUrl, direct=True) except sqlmapConnectionException, _: __sqlmapPath = urlparse.urlsplit(sqlmapBinaryStringUrl) __sqlmapHostname = __sqlmapPath[1] diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index 59d59d4c6..cdeb7e213 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -109,12 +109,24 @@ def cmdLineParser(): injection.add_option("-p", dest="testParameter", help="Testable parameter(s)") + injection.add_option("--dbms", dest="dbms", + help="Force back-end DBMS to this value") + injection.add_option("--string", dest="string", help="String to match in page when the " "query is valid") - injection.add_option("--dbms", dest="dbms", - help="Force back-end DBMS to this value") + injection.add_option("--regexp", dest="regexp", + help="Regexp to match in page when the " + "query is valid") + + injection.add_option("--excl-str", dest="eString", + help="String to be excluded before calculating " + "page hash") + + injection.add_option("--excl-reg", dest="eRegexp", + help="Regexp matches to be excluded before " + "calculating page hash") # Techniques options techniques = OptionGroup(parser, "Techniques", "These options can " diff --git a/lib/request/comparison.py b/lib/request/comparison.py new file mode 100644 index 000000000..3fc021a43 --- /dev/null +++ b/lib/request/comparison.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +""" +$Id$ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. + and Daniele Bellucci + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import md5 +import re + +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger + + +def comparison(page, headers=None, content=False): + regExpResults = None + + if conf.eString and conf.eString in page: + index = page.index(conf.eString) + length = len(conf.eString) + pageWithoutString = page[:index] + pageWithoutString += page[index+length:] + page = pageWithoutString + + if conf.eRegexp: + regExpResults = re.findall(conf.eRegexp, page, re.I | re.M) + + if conf.eRegexp and regExpResults: + for regExpResult in regExpResults: + index = page.index(regExpResult) + length = len(regExpResult) + pageWithoutRegExp = page[:index] + pageWithoutRegExp += page[index+length:] + page = pageWithoutRegExp + + if conf.string: + if conf.string in page: + return True + else: + return False + + elif conf.regexp: + if re.search(conf.regexp, page, re.I | re.M): + return True + else: + return False + + else: + return md5.new(page).hexdigest() diff --git a/lib/request/connect.py b/lib/request/connect.py index 728621cdd..3fde43633 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -42,7 +42,7 @@ from lib.core.exception import sqlmapConnectionException from lib.core.settings import RETRIES from lib.request.basic import forgeHeaders from lib.request.basic import parseResponse - +from lib.request.comparison import comparison class Connect: @@ -190,15 +190,15 @@ class Connect: warnMsg += "status code, try to force the HTTP User-Agent " warnMsg += "header with option --user-agent or -a" + if "BadStatusLine" not in tbMsg: + warnMsg += " or proxy" + if conf.multipleTargets: warnMsg += ", skipping to next url" logger.warn(warnMsg) return None - if "BadStatusLine" not in tbMsg: - warnMsg += " or proxy" - if conf.retries < RETRIES: conf.retries += 1 @@ -207,6 +207,7 @@ class Connect: time.sleep(1) return Connect.__getPageProxy(get=get, post=post, cookie=cookie, ua=ua, direct=direct, multipart=multipart) + else: raise sqlmapConnectionException, warnMsg @@ -220,7 +221,7 @@ class Connect: logger.log(8, responseMsg) - return page + return page, responseHeaders @staticmethod @@ -263,15 +264,9 @@ class Connect: else: ua = conf.parameters["User-Agent"] - page = Connect.getPage(get=get, post=post, cookie=cookie, ua=ua) + page, headers = Connect.getPage(get=get, post=post, cookie=cookie, ua=ua) - # TODO: create a comparison library and move these checks there if content: return page - elif conf.string: - if conf.string in page: - return True - else: - return False else: - return md5.new(page).hexdigest() + return comparison(page, headers, content) diff --git a/plugins/dbms/mysql.py b/plugins/dbms/mysql.py index a8c31ba3a..48f3696e4 100644 --- a/plugins/dbms/mysql.py +++ b/plugins/dbms/mysql.py @@ -450,7 +450,7 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover): baseUrl = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, requestDir) uploaderUrl = "%s/%s" % (baseUrl, uploaderName) - page = Request.getPage(url=uploaderUrl, direct=True) + page, _ = Request.getPage(url=uploaderUrl, direct=True) if "sqlmap backdoor uploader" not in page: warnMsg = "unable to upload the uploader " @@ -470,7 +470,7 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover): "uploadDir": directory, } uploaderUrl = "%s/%s" % (baseUrl, uploaderName) - page = Request.getPage(url=uploaderUrl, multipart=multipartParams) + page, _ = Request.getPage(url=uploaderUrl, multipart=multipartParams) if "Backdoor uploaded" not in page: warnMsg = "unable to upload the backdoor through " @@ -522,7 +522,7 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover): break cmdUrl = "%s?cmd=%s" % (backdoorUrl, command) - page = Request.getPage(url=cmdUrl, direct=True) + page, _ = Request.getPage(url=cmdUrl, direct=True) output = re.search("
(.+?)
", page, re.I | re.S) if output: diff --git a/sqlmap.conf b/sqlmap.conf index 037d6a265..99925af62 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -99,19 +99,38 @@ timeout = 10 # parameters and HTTP User-Agent are tested by sqlmap. testParameter = -# String to match in page when the query is valid, only needed if the -# page content dynamically changes at each refresh, consequently changing -# the MD5 of the page which is the method used by default to determine -# if a query was valid or not. Read the documentation for further -# details. -string = - # Force back-end DBMS to this value. If this option is set, the back-end # DBMS identification process will be minimized as needed. # If not set, sqlmap will detect back-end DBMS automatically by default. # Valid: mssql, mysql, mysql 4, mysql 5, oracle, pgsql dbms = +# String to match within the page content when the query is valid, only +# needed if the page content dynamically changes at each refresh, +# consequently changing the MD5 hash of the page which is the method used +# by default to determine if a query was valid or not. Refer to the user's +# manual for further details. +string = + +# Regular expression to match within the page content when the query is +# valid, only needed if the needed if the page content dynamically changes +# at each refresh, consequently changing the MD5 hash of the page which is +# the method used by default to determine if a query was valid or not. +# Refer to the user's manual for further details. +# Valid: regular expression with Python syntax +# (http://www.python.org/doc/2.5.2/lib/re-syntax.html) +regexp = + +# String to be excluded by the page content before calculating the page +# MD5 hash +eString = + +# Regular expression matches to be excluded by the page content before +# calculating the page MD5 hash +# Valid: regular expression with Python syntax +# (http://www.python.org/doc/2.5.2/lib/re-syntax.html) +eRegexp = + [Techniques]