diff --git a/doc/ChangeLog b/doc/ChangeLog index 4ecc84a71..704593cbe 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,11 +1,14 @@ sqlmap (0.6.3-1) stable; urgency=low - * Major bug fix to correctly handle httplib.BadStatusLine exception; - * Minor enhancement to support stacked queries which will be used - sometimes by takeover functionality and time based blind SQL injection - technique; + * Major enhancement to support stacked queries when the web application + supports it which will be used in the long run by takeover + functionality; + * Minor enhancement to test if the injectable parameter is affected by + a time based blind SQL injection technique; + * 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 both -f and -b are provided; + parsing the DBMS banner value when -b option is provided; * Minor enhancement to be able to specify the number of seconds to wait between each HTTP request providing option --delay #; * Minor enhancement to be able to enumerate table columns and dump table @@ -16,6 +19,7 @@ sqlmap (0.6.3-1) stable; urgency=low HTTP headers (Accept, Accept-Encoding, etc); * Minor improvements to sqlmap Debian package files: sqlmap uploaded to official Debian project repository; + * Major bug fix to correctly handle httplib.BadStatusLine exception; * Minor bug fix to handle session.error and session.timeout in HTTP requests; * Minor bug fix so that when the user provide a SELECT statement to be diff --git a/lib/core/common.py b/lib/core/common.py index b2a862c95..48c0e1b4b 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -130,15 +130,34 @@ def formatDBMSfp(versions=None): return "%s %s" % (kb.dbms, " and ".join([version for version in versions])) -def __formatOSfpString(values): - return " or ".join([v for v in values]) +def __formatFingerprintString(values, chain="or"): + string = "|".join([v for v in values]) + return string.replace("|", " %s " % chain) -def formatOSfp(): +def formatFingerprint(target, info): """ This function format the back-end operating system fingerprint value and return its values formatted as a human readable string. + Examples of info dictionary: + + { + "distrib": set(["2000"]), + "dbmsVersion": "8.00.194", + "dbmsRelease": "2000", + "dbmsServicePack": "0", + "type": set(["Windows"]) + } + + { + "distrib": set(["Ubuntu"]), + "release": set(["8.10"]), + "codename": set(["Intrepid"]), + "version": "5.0.67", + "type": set(["Linux"]) + } + @return: detected back-end operating system based upon fingerprint techniques. @rtype: C{str} @@ -146,40 +165,25 @@ def formatOSfp(): infoStr = "" - # Examples of kb.bannerFp dictionary: - # - # { - # "distrib": set(["2000"]), - # "dbmsVersion": "8.00.194", - # "dbmsRelease": "2000", - # "dbmsServicePack": "0", - # "type": set(["Windows"]) - # } - # - # { - # "distrib": set(["Ubuntu"]), - # "release": set(["8.10"]), - # "codename": set(["Intrepid"]), - # "version": "5.0.67", - # "type": set(["Linux"]) - # } - - if not kb.bannerFp or "type" not in kb.bannerFp: + if not info or "type" not in info: return infoStr else: - infoStr += "back-end DBMS operating system: %s" % __formatOSfpString(kb.bannerFp["type"]) + infoStr += "%s operating system: %s" % (target, __formatFingerprintString(info["type"])) - if "distrib" in kb.bannerFp: - infoStr += " %s" % __formatOSfpString(kb.bannerFp["distrib"]) + if "distrib" in info: + infoStr += " %s" % __formatFingerprintString(info["distrib"]) - if "release" in kb.bannerFp: - infoStr += " %s" % __formatOSfpString(kb.bannerFp["release"]) + if "release" in info: + infoStr += " %s" % __formatFingerprintString(info["release"]) - if "sp" in kb.bannerFp: - infoStr += " %s" % __formatOSfpString(kb.bannerFp["sp"]) + if "sp" in info: + infoStr += " %s" % __formatFingerprintString(info["sp"]) - if "codename" in kb.bannerFp: - infoStr += " (%s)" % __formatOSfpString(kb.bannerFp["codename"]) + if "codename" in info: + infoStr += " (%s)" % __formatFingerprintString(info["codename"]) + + if "technology" in info: + infoStr += "\nweb application technology: %s" % __formatFingerprintString(info["technology"], "and") return infoStr diff --git a/lib/core/option.py b/lib/core/option.py index da8099fd0..44114cd3f 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -454,6 +454,7 @@ def __setKnowledgeBaseAttributes(): kb.dbmsDetected = False kb.dbmsVersion = None kb.bannerFp = {} + kb.headersCount = 0 kb.headersFp = {} kb.htmlFp = [] kb.injParameter = None diff --git a/lib/parse/banner.py b/lib/parse/banner.py index 7b4f0ea87..57afe1669 100644 --- a/lib/parse/banner.py +++ b/lib/parse/banner.py @@ -33,60 +33,7 @@ from lib.core.common import checkFile from lib.core.common import sanitizeStr from lib.core.data import kb from lib.core.data import paths - - -class BannerHandler(ContentHandler): - """ - This class defines methods to parse and extract information from - the given DBMS banner based upon the data in XML file - """ - - def __init__(self, banner): - self.__banner = sanitizeStr(banner) - - self.__regexp = None - self.__match = None - self.__version = None - - - def __feedInfo(self, key, value): - value = sanitizeStr(value) - - if value in ( None, "None" ): - return - - if key == "version": - kb.bannerFp[key] = value - else: - if key not in kb.bannerFp.keys(): - kb.bannerFp[key] = set() - - kb.bannerFp[key].add(value) - - - def startElement(self, name, attrs): - if name == "regexp": - self.__regexp = sanitizeStr(attrs.get("value")) - self.__match = re.search(self.__regexp, self.__banner, re.I | re.M) - - if name == "info" and self.__match: - self.__feedInfo("type", attrs.get("type")) - self.__feedInfo("distrib", attrs.get("distrib")) - self.__feedInfo("release", attrs.get("release")) - self.__feedInfo("codename", attrs.get("codename")) - - self.__version = sanitizeStr(attrs.get("version")) - self.__sp = sanitizeStr(attrs.get("sp")) - - if self.__version.isdigit(): - self.__feedInfo("version", self.__match.group(int(self.__version))) - - if self.__sp.isdigit(): - self.__feedInfo("sp", "Service Pack %s" % self.__match.group(int(self.__sp))) - - self.__regexp = None - self.__match = None - self.__version = None +from lib.parse.handler import FingerprintHandler class MSSQLBannerHandler(ContentHandler): @@ -172,9 +119,10 @@ def bannerParser(banner): if kb.dbms == "Microsoft SQL Server": handler = MSSQLBannerHandler(banner) parse(xmlfile, handler) - handler = BannerHandler(banner) + + handler = FingerprintHandler(banner, kb.bannerFp) parse(paths.GENERIC_XML, handler) else: - handler = BannerHandler(banner) + handler = FingerprintHandler(banner, kb.bannerFp) parse(xmlfile, handler) parse(paths.GENERIC_XML, handler) diff --git a/lib/parse/handler.py b/lib/parse/handler.py new file mode 100644 index 000000000..eae2bc8f1 --- /dev/null +++ b/lib/parse/handler.py @@ -0,0 +1,95 @@ +#!/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 re + +from xml.sax.handler import ContentHandler + +from lib.core.common import sanitizeStr +from lib.core.data import kb + + +class FingerprintHandler(ContentHandler): + """ + This class defines methods to parse and extract information from + the given DBMS banner based upon the data in XML file + """ + + def __init__(self, banner, info): + self.__banner = sanitizeStr(banner) + + self.__regexp = None + self.__match = None + self.__dbmsVersion = None + self.__techVersion = None + self.__info = info + + + def __feedInfo(self, key, value): + value = sanitizeStr(value) + + if value in ( None, "None" ): + return + + if key in ( "dbmsVersion" ): + self.__info[key] = value + else: + if key not in self.__info.keys(): + self.__info[key] = set() + + self.__info[key].add(value) + + + def startElement(self, name, attrs): + if name == "regexp": + self.__regexp = sanitizeStr(attrs.get("value")) + self.__match = re.search(self.__regexp, self.__banner, re.I | re.M) + + if name == "info" and self.__match: + self.__feedInfo("type", attrs.get("type")) + self.__feedInfo("distrib", attrs.get("distrib")) + self.__feedInfo("release", attrs.get("release")) + self.__feedInfo("codename", attrs.get("codename")) + + self.__dbmsVersion = sanitizeStr(attrs.get("dbms_version")) + self.__techVersion = sanitizeStr(attrs.get("tech_version")) + self.__sp = sanitizeStr(attrs.get("sp")) + + if self.__dbmsVersion.isdigit(): + self.__feedInfo("dbmsVersion", self.__match.group(int(self.__dbmsVersion))) + + if self.__techVersion.isdigit(): + self.__feedInfo("technology", "%s %s" % (attrs.get("technology"), self.__match.group(int(self.__techVersion)))) + else: + self.__feedInfo("technology", attrs.get("technology")) + + if self.__sp.isdigit(): + self.__feedInfo("sp", "Service Pack %s" % self.__match.group(int(self.__sp))) + + self.__regexp = None + self.__match = None + self.__dbmsVersion = None + self.__techVersion = None diff --git a/lib/parse/headers.py b/lib/parse/headers.py index f3c8dc25c..524d453b8 100644 --- a/lib/parse/headers.py +++ b/lib/parse/headers.py @@ -27,67 +27,11 @@ Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import re from xml.sax import parse -from xml.sax.handler import ContentHandler from lib.core.common import checkFile -from lib.core.common import sanitizeStr from lib.core.data import kb from lib.core.data import paths - - -class HeadersHandler(ContentHandler): - """ - This class defines methods to parse and extract information from - the given HTTP header based upon the data in XML file - """ - - def __init__(self, header): - self.__header = sanitizeStr(header) - - self.__regexp = None - self.__match = None - self.__techVersion = None - - - def __feedInfo(self, key, value): - value = sanitizeStr(value) - - if value in ( None, "None" ): - return - - if key == "techVersion": - kb.headersFp[key] = value - else: - if key not in kb.headersFp.keys(): - kb.headersFp[key] = set() - - kb.headersFp[key].add(value) - - - def startElement(self, name, attrs): - if name == "regexp": - self.__regexp = sanitizeStr(attrs.get("value")) - self.__match = re.search(self.__regexp, self.__header, re.I | re.M) - - if name == "info" and self.__match: - self.__feedInfo("type", attrs.get("type")) - self.__feedInfo("distrib", attrs.get("distrib")) - self.__feedInfo("release", attrs.get("release")) - self.__feedInfo("codename", attrs.get("codename")) - self.__feedInfo("technology", attrs.get("codename")) - - self.__techVersion = sanitizeStr(attrs.get("tech_version")) - self.__sp = sanitizeStr(attrs.get("sp")) - - if self.__techVersion.isdigit(): - self.__feedInfo("techVersion", self.__match.group(int(self.__techVersion))) - - if self.__sp.isdigit(): - self.__feedInfo("sp", "Service Pack %s" % self.__match.group(int(self.__sp))) - - self.__regexp = None - self.__match = None - self.__techVersion = None +from lib.parse.handler import FingerprintHandler def headersParser(headers): @@ -97,11 +41,16 @@ def headersParser(headers): and the web application technology """ + if kb.headersCount > 3: + return + + kb.headersCount += 1 + # TODO: ahead here topHeaders = { #"cookie": "%s/cookie.xml" % paths.SQLMAP_XML_BANNER_PATH, #"microsoftsharepointteamservices": "%s/microsoftsharepointteamservices.xml" % paths.SQLMAP_XML_BANNER_PATH, - #"server": "%s/server.xml" % paths.SQLMAP_XML_BANNER_PATH, + "server": "%s/server.xml" % paths.SQLMAP_XML_BANNER_PATH, #"servlet-engine": "%s/servlet-engine.xml" % paths.SQLMAP_XML_BANNER_PATH, #"set-cookie": "%s/cookie.xml" % paths.SQLMAP_XML_BANNER_PATH, #"www-authenticate": "%s/www-authenticate.xml" % paths.SQLMAP_XML_BANNER_PATH, @@ -114,6 +63,6 @@ def headersParser(headers): value = headers[header] xmlfile = topHeaders[header] checkFile(xmlfile) - handler = HeadersHandler(value) + handler = FingerprintHandler(value, kb.headersFp) parse(xmlfile, handler) parse(paths.GENERIC_XML, handler) diff --git a/plugins/dbms/mssqlserver.py b/plugins/dbms/mssqlserver.py index 32deb7cad..150e85577 100644 --- a/plugins/dbms/mssqlserver.py +++ b/plugins/dbms/mssqlserver.py @@ -29,7 +29,7 @@ import time from lib.core.agent import agent from lib.core.common import dataToStdout from lib.core.common import formatDBMSfp -from lib.core.common import formatOSfp +from lib.core.common import formatFingerprint from lib.core.common import getHtmlErrorFp from lib.core.common import randomInt from lib.core.common import readInput @@ -43,7 +43,6 @@ from lib.core.session import setDbms from lib.core.settings import MSSQL_ALIASES from lib.core.settings import MSSQL_SYSTEM_DBS from lib.core.unescaper import unescaper -from lib.parse.banner import bannerParser from lib.request import inject from lib.request.connect import Connect as Request @@ -123,14 +122,17 @@ class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Takeover): def getFingerprint(self): - value = "" - formatInfo = None + value = "" + wsOsFp = formatFingerprint("web server", kb.headersFp) + + if wsOsFp: + value += "%s\n" % wsOsFp if self.banner: - formatInfo = formatOSfp() + dbmsOsFp = formatFingerprint("back-end DBMS", kb.bannerFp) - if formatInfo: - value += "%s\n" % formatInfo + if dbmsOsFp: + value += "%s\n" % dbmsOsFp value += "back-end DBMS: " actVer = formatDBMSfp() @@ -140,7 +142,6 @@ class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Takeover): return value blank = " " * 15 - formatInfo = None value += "active fingerprint: %s" % actVer if kb.bannerFp: diff --git a/plugins/dbms/mysql.py b/plugins/dbms/mysql.py index 768f9f0d4..ddf3330a0 100644 --- a/plugins/dbms/mysql.py +++ b/plugins/dbms/mysql.py @@ -29,7 +29,7 @@ import re from lib.core.agent import agent from lib.core.common import fileToStr from lib.core.common import formatDBMSfp -from lib.core.common import formatOSfp +from lib.core.common import formatFingerprint from lib.core.common import getDirectories from lib.core.common import getHtmlErrorFp from lib.core.common import randomInt @@ -44,7 +44,6 @@ from lib.core.settings import MYSQL_ALIASES from lib.core.settings import MYSQL_SYSTEM_DBS from lib.core.shell import autoCompletion from lib.core.unescaper import unescaper -from lib.parse.banner import bannerParser from lib.request import inject from lib.request.connect import Connect as Request @@ -181,14 +180,17 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover): def getFingerprint(self): - value = "" - formatInfo = None + value = "" + wsOsFp = formatFingerprint("web server", kb.headersFp) + + if wsOsFp: + value += "%s\n" % wsOsFp if self.banner: - formatInfo = formatOSfp() + dbmsOsFp = formatFingerprint("back-end DBMS", kb.bannerFp) - if formatInfo: - value += "%s\n" % formatInfo + if dbmsOsFp: + value += "%s\n" % dbmsOsFp value += "back-end DBMS: " actVer = formatDBMSfp() @@ -199,7 +201,6 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover): comVer = self.__commentCheck() blank = " " * 15 - formatInfo = None value += "active fingerprint: %s" % actVer if comVer: @@ -208,7 +209,7 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover): if kb.bannerFp: # TODO: move to the XML banner file - banVer = kb.bannerFp['version'] + banVer = kb.bannerFp["dbmsVersion"] if re.search("-log$", self.banner): banVer += ", logging enabled" diff --git a/plugins/dbms/oracle.py b/plugins/dbms/oracle.py index 4a78a19a4..e39b76d33 100644 --- a/plugins/dbms/oracle.py +++ b/plugins/dbms/oracle.py @@ -27,7 +27,7 @@ Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import re from lib.core.common import formatDBMSfp -from lib.core.common import formatOSfp +from lib.core.common import formatFingerprint from lib.core.common import getHtmlErrorFp from lib.core.data import conf from lib.core.data import kb @@ -37,7 +37,6 @@ from lib.core.session import setDbms from lib.core.settings import ORACLE_ALIASES from lib.core.settings import ORACLE_SYSTEM_DBS from lib.core.unescaper import unescaper -from lib.parse.banner import bannerParser from lib.request import inject from plugins.generic.enumeration import Enumeration @@ -117,14 +116,17 @@ class OracleMap(Fingerprint, Enumeration, Filesystem, Takeover): def getFingerprint(self): - value = "" - formatInfo = None + value = "" + wsOsFp = formatFingerprint("web server", kb.headersFp) + + if wsOsFp: + value += "%s\n" % wsOsFp if self.banner: - formatInfo = formatOSfp() + dbmsOsFp = formatFingerprint("back-end DBMS", kb.bannerFp) - if formatInfo: - value += "%s\n" % formatInfo + if dbmsOsFp: + value += "%s\n" % dbmsOsFp value += "back-end DBMS: " @@ -134,11 +136,10 @@ class OracleMap(Fingerprint, Enumeration, Filesystem, Takeover): actVer = formatDBMSfp() blank = " " * 15 - formatInfo = None value += "active fingerprint: %s" % actVer if kb.bannerFp: - banVer = kb.bannerFp['version'] + banVer = kb.bannerFp["dbmsVersion"] banVer = formatDBMSfp([banVer]) value += "\n%sbanner parsing fingerprint: %s" % (blank, banVer) diff --git a/plugins/dbms/postgresql.py b/plugins/dbms/postgresql.py index 0155ff0b9..903cfcf6e 100644 --- a/plugins/dbms/postgresql.py +++ b/plugins/dbms/postgresql.py @@ -27,7 +27,7 @@ Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import re from lib.core.common import formatDBMSfp -from lib.core.common import formatOSfp +from lib.core.common import formatFingerprint from lib.core.common import getHtmlErrorFp from lib.core.common import randomInt from lib.core.data import conf @@ -38,7 +38,6 @@ from lib.core.session import setDbms from lib.core.settings import PGSQL_ALIASES from lib.core.settings import PGSQL_SYSTEM_DBS from lib.core.unescaper import unescaper -from lib.parse.banner import bannerParser from lib.request import inject from plugins.generic.enumeration import Enumeration @@ -117,14 +116,17 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Takeover): def getFingerprint(self): - value = "" - formatInfo = None + value = "" + wsOsFp = formatFingerprint("web server", kb.headersFp) + + if wsOsFp: + value += "%s\n" % wsOsFp if self.banner: - formatInfo = formatOSfp() + dbmsOsFp = formatFingerprint("back-end DBMS", kb.bannerFp) - if formatInfo: - value += "%s\n" % formatInfo + if dbmsOsFp: + value += "%s\n" % dbmsOsFp value += "back-end DBMS: " @@ -134,11 +136,10 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Takeover): actVer = formatDBMSfp() blank = " " * 15 - formatInfo = None value += "active fingerprint: %s" % actVer if kb.bannerFp: - banVer = kb.bannerFp['version'] + banVer = kb.bannerFp["dbmsVersion"] banVer = formatDBMSfp([banVer]) value += "\n%sbanner parsing fingerprint: %s" % (blank, banVer) diff --git a/xml/banner/generic.xml b/xml/banner/generic.xml index 950d9715d..8b0d3fe1c 100644 --- a/xml/banner/generic.xml +++ b/xml/banner/generic.xml @@ -26,12 +26,27 @@ + + + + + + + + + + + + + + + @@ -61,7 +76,7 @@ - + @@ -72,7 +87,6 @@ - diff --git a/xml/banner/mysql.xml b/xml/banner/mysql.xml index 1d806f963..c51c0296f 100644 --- a/xml/banner/mysql.xml +++ b/xml/banner/mysql.xml @@ -1,46 +1,42 @@ - - + - - + - - + - + - + - + - + - + - - + diff --git a/xml/banner/oracle.xml b/xml/banner/oracle.xml index ad8bc36d9..61ce9948c 100644 --- a/xml/banner/oracle.xml +++ b/xml/banner/oracle.xml @@ -1,8 +1,7 @@ - - + diff --git a/xml/banner/postgresql.xml b/xml/banner/postgresql.xml index aad23bfee..3f58f101c 100644 --- a/xml/banner/postgresql.xml +++ b/xml/banner/postgresql.xml @@ -1,14 +1,12 @@ - - + - - + diff --git a/xml/banner/server.xml b/xml/banner/server.xml new file mode 100644 index 000000000..d0cb771a7 --- /dev/null +++ b/xml/banner/server.xml @@ -0,0 +1,369 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +