Major enhancement to the engine to parse XML files and matches on DBMS banner

and HTTP response headers.
Initial web application technology fingerprint (for the moment based only on
X-Powered-By HTTP response header and not shown yet to the user).
Minor layout adjustments.
This commit is contained in:
Bernardo Damele 2008-11-17 17:41:02 +00:00
parent 66fb3c3033
commit 7d0724843f
13 changed files with 222 additions and 133 deletions

View File

@ -130,7 +130,11 @@ def formatDBMSfp(versions=None):
return "%s %s" % (kb.dbms, " and ".join([version for version in versions]))
def formatOSfp(info):
def __formatOSfpString(values):
return " or ".join([v for v in values])
def formatOSfp():
"""
This function format the back-end operating system fingerprint value
and return its values formatted as a human readable string.
@ -142,31 +146,40 @@ def formatOSfp(info):
infoStr = ""
# Example of 'info' dictionary:
# Examples of kb.bannerFp dictionary:
#
# {
# 'distrib': 'Ubuntu',
# 'release': '8.10',
# 'codename': 'Intrepid',
# 'version': '5.0.67',
# 'type': 'Linux'
# "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 info or 'type' not in info:
if not kb.bannerFp or "type" not in kb.bannerFp:
return infoStr
elif info['type'] != "None":
infoStr += "back-end DBMS operating system: %s" % info['type']
else:
infoStr += "back-end DBMS operating system: %s" % __formatOSfpString(kb.bannerFp["type"])
if 'distrib' in info and info['distrib'] != "None":
infoStr += " %s" % info['distrib']
if "distrib" in kb.bannerFp:
infoStr += " %s" % __formatOSfpString(kb.bannerFp["distrib"])
if 'release' in info and info['release'] != "None":
infoStr += " %s" % info['release']
if "release" in kb.bannerFp:
infoStr += " %s" % __formatOSfpString(kb.bannerFp["release"])
if 'sp' in info and info['sp'] != "None":
infoStr += " %s" % info['sp']
if "sp" in kb.bannerFp:
infoStr += " %s" % __formatOSfpString(kb.bannerFp["sp"])
if 'codename' in info and info['codename'] != "None":
infoStr += " (%s)" % info['codename']
if "codename" in kb.bannerFp:
infoStr += " (%s)" % __formatOSfpString(kb.bannerFp["codename"])
return infoStr
@ -248,7 +261,7 @@ def getDirectories():
if kb.docRoot:
directories.add(kb.docRoot)
pagePath = re.search('^/(.*)/', conf.path)
pagePath = re.search("^/(.*)/", conf.path)
if kb.docRoot and pagePath:
pagePath = pagePath.groups()[0]

View File

@ -453,6 +453,7 @@ def __setKnowledgeBaseAttributes():
kb.dbms = None
kb.dbmsDetected = False
kb.dbmsVersion = None
kb.bannerFp = {}
kb.headersFp = {}
kb.htmlFp = []
kb.injParameter = None

View File

@ -44,11 +44,24 @@ class BannerHandler(ContentHandler):
def __init__(self, banner):
self.__banner = sanitizeStr(banner)
self.__regexp = None
self.__match = None
self.__position = None
self.__regexp = None
self.__match = None
self.__version = None
self.info = {}
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):
@ -57,22 +70,23 @@ class BannerHandler(ContentHandler):
self.__match = re.search(self.__regexp, self.__banner, re.I | re.M)
if name == "info" and self.__match:
self.__position = sanitizeStr(attrs.get("version"))
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"))
self.info['type'] = sanitizeStr(attrs.get("type"))
self.info['distrib'] = sanitizeStr(attrs.get("distrib"))
self.info['release'] = sanitizeStr(attrs.get("release"))
self.info['codename'] = sanitizeStr(attrs.get("codename"))
if self.__position.isdigit():
self.info['version'] = self.__match.group(int(self.__position))
if self.__version.isdigit():
self.__feedInfo("version", self.__match.group(int(self.__version)))
if self.__sp.isdigit():
self.info['sp'] = "Service Pack %s" % self.__match.group(int(self.__sp))
self.__feedInfo("sp", "Service Pack %s" % self.__match.group(int(self.__sp)))
self.__match = None
self.__position = None
self.__regexp = None
self.__match = None
self.__version = None
class MSSQLBannerHandler(ContentHandler):
@ -90,7 +104,14 @@ class MSSQLBannerHandler(ContentHandler):
self.__version = ""
self.__servicePack = ""
self.info = {}
def __feedInfo(self, key, value):
value = sanitizeStr(value)
if value in ( None, "None" ):
return
kb.bannerFp[key] = value
def startElement(self, name, attrs):
@ -114,9 +135,9 @@ class MSSQLBannerHandler(ContentHandler):
def endElement(self, name):
if name == "signature":
if re.search(" %s[\.\ ]+" % self.__version, self.__banner):
self.info['dbmsRelease'] = self.__release
self.info['dbmsVersion'] = self.__version
self.info['dbmsServicePack'] = self.__servicePack
self.__feedInfo("dbmsRelease", self.__release)
self.__feedInfo("dbmsVersion", self.__version)
self.__feedInfo("dbmsServicePack", self.__servicePack)
self.__version = ""
self.__servicePack = ""
@ -137,9 +158,6 @@ def bannerParser(banner):
DBMS banner based upon the data in XML file
"""
banner = sanitizeStr(banner)
info = {}
if kb.dbms == "Microsoft SQL Server":
xmlfile = paths.MSSQL_XML
elif kb.dbms == "MySQL":
@ -154,24 +172,9 @@ def bannerParser(banner):
if kb.dbms == "Microsoft SQL Server":
handler = MSSQLBannerHandler(banner)
parse(xmlfile, handler)
info = handler.info
handler = BannerHandler(banner)
parse(paths.GENERIC_XML, handler)
for title, value in handler.info.items():
info[title] = value
else:
handler = BannerHandler(banner)
parse(xmlfile, handler)
info = handler.info
if "type" not in info or info["type"] == "None":
parse(paths.GENERIC_XML, handler)
info["type"] = handler.info["type"]
if "distrib" not in info or info["distrib"] == "None":
parse(paths.GENERIC_XML, handler)
info["distrib"] = handler.info["distrib"]
return info
parse(paths.GENERIC_XML, handler)

View File

@ -26,10 +26,68 @@ 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
from lib.parse.banner import BannerHandler
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
def headersParser(headers):
@ -39,17 +97,23 @@ def headersParser(headers):
and the web application technology
"""
topHeaders = (
"cookie",
"microsoftsharepointteamservices",
"server",
"servlet-engine",
"www-authenticate",
"x-aspnet-version",
"x-powered-by",
)
# 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,
#"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,
#"x-aspnet-version": "%s/x-aspnet-version.xml" % paths.SQLMAP_XML_BANNER_PATH,
"x-powered-by": "%s/x-powered-by.xml" % paths.SQLMAP_XML_BANNER_PATH,
}
for header in headers:
if header in topHeaders:
# TODO: fill me
pass
if header in topHeaders.keys():
value = headers[header]
xmlfile = topHeaders[header]
checkFile(xmlfile)
handler = HeadersHandler(value)
parse(xmlfile, handler)
parse(paths.GENERIC_XML, handler)

View File

@ -124,15 +124,13 @@ class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Takeover):
def getFingerprint(self):
value = ""
info = None
formatInfo = None
if self.banner:
info = bannerParser(self.banner)
formatInfo = formatOSfp(info)
formatInfo = formatOSfp()
if formatInfo:
value += "%s\n" % formatInfo
if formatInfo:
value += "%s\n" % formatInfo
value += "back-end DBMS: "
actVer = formatDBMSfp()
@ -145,10 +143,10 @@ class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Takeover):
formatInfo = None
value += "active fingerprint: %s" % actVer
if info:
release = info["dbmsRelease"]
version = info["dbmsVersion"]
servicepack = info["dbmsServicePack"]
if kb.bannerFp:
release = kb.bannerFp["dbmsRelease"]
version = kb.bannerFp["dbmsVersion"]
servicepack = kb.bannerFp["dbmsServicePack"]
if release and version and servicepack:
banVer = "Microsoft SQL Server %s " % release
@ -169,8 +167,7 @@ class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Takeover):
if conf.dbms in MSSQL_ALIASES and kb.dbmsVersion and kb.dbmsVersion[0].isdigit():
setDbms("Microsoft SQL Server %s" % kb.dbmsVersion[0])
if conf.getBanner:
self.banner = inject.getValue("@@VERSION")
self.getPrematureBanner("@@VERSION")
if not conf.extensiveFp:
return True
@ -197,8 +194,7 @@ class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Takeover):
else:
setDbms("Microsoft SQL Server")
if conf.getBanner:
self.banner = inject.getValue("@@VERSION")
self.getPrematureBanner("@@VERSION")
return True
else:

View File

@ -182,15 +182,13 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
def getFingerprint(self):
value = ""
info = None
formatInfo = None
if self.banner:
info = bannerParser(self.banner)
formatInfo = formatOSfp(info)
formatInfo = formatOSfp()
if formatInfo:
value += "%s\n" % formatInfo
if formatInfo:
value += "%s\n" % formatInfo
value += "back-end DBMS: "
actVer = formatDBMSfp()
@ -208,9 +206,9 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
comVer = formatDBMSfp([comVer])
value += "\n%scomment injection fingerprint: %s" % (blank, comVer)
if info:
if kb.bannerFp:
# TODO: move to the XML banner file
banVer = info['version']
banVer = kb.bannerFp['version']
if re.search("-log$", self.banner):
banVer += ", logging enabled"
@ -241,8 +239,7 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
if int(kb.dbmsVersion[0]) >= 5:
self.has_information_schema = True
if conf.getBanner:
self.banner = inject.getValue("VERSION()")
self.getPrematureBanner("VERSION()")
if not conf.extensiveFp:
return True
@ -270,8 +267,7 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
setDbms("MySQL 5")
self.has_information_schema = True
if conf.getBanner:
self.banner = inject.getValue("VERSION()")
self.getPrematureBanner("VERSION()")
if not conf.extensiveFp:
kb.dbmsVersion = [">= 5.0.0"]
@ -318,8 +314,7 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
setDbms("MySQL 4")
kb.dbmsVersion = ["< 5.0.0"]
if conf.getBanner:
self.banner = inject.getValue("VERSION()")
self.getPrematureBanner("VERSION()")
if not conf.extensiveFp:
return True

View File

@ -118,15 +118,13 @@ class OracleMap(Fingerprint, Enumeration, Filesystem, Takeover):
def getFingerprint(self):
value = ""
info = None
formatInfo = None
if self.banner:
info = bannerParser(self.banner)
formatInfo = formatOSfp(info)
formatInfo = formatOSfp()
if formatInfo:
value += "%s\n" % formatInfo
if formatInfo:
value += "%s\n" % formatInfo
value += "back-end DBMS: "
@ -139,8 +137,8 @@ class OracleMap(Fingerprint, Enumeration, Filesystem, Takeover):
formatInfo = None
value += "active fingerprint: %s" % actVer
if info:
banVer = info['version']
if kb.bannerFp:
banVer = kb.bannerFp['version']
banVer = formatDBMSfp([banVer])
value += "\n%sbanner parsing fingerprint: %s" % (blank, banVer)
@ -156,8 +154,7 @@ class OracleMap(Fingerprint, Enumeration, Filesystem, Takeover):
if conf.dbms in ORACLE_ALIASES:
setDbms("Oracle")
if conf.getBanner:
self.banner = inject.getValue("SELECT banner FROM v$version WHERE ROWNUM=1")
self.getPrematureBanner("SELECT banner FROM v$version WHERE ROWNUM=1")
if not conf.extensiveFp:
return True
@ -183,8 +180,7 @@ class OracleMap(Fingerprint, Enumeration, Filesystem, Takeover):
setDbms("Oracle")
if conf.getBanner:
self.banner = inject.getValue("SELECT banner FROM v$version WHERE ROWNUM=1")
self.getPrematureBanner("SELECT banner FROM v$version WHERE ROWNUM=1")
if not conf.extensiveFp:
return True

View File

@ -118,15 +118,13 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
def getFingerprint(self):
value = ""
info = None
formatInfo = None
if self.banner:
info = bannerParser(self.banner)
formatInfo = formatOSfp(info)
formatInfo = formatOSfp()
if formatInfo:
value += "%s\n" % formatInfo
if formatInfo:
value += "%s\n" % formatInfo
value += "back-end DBMS: "
@ -139,8 +137,8 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
formatInfo = None
value += "active fingerprint: %s" % actVer
if info:
banVer = info['version']
if kb.bannerFp:
banVer = kb.bannerFp['version']
banVer = formatDBMSfp([banVer])
value += "\n%sbanner parsing fingerprint: %s" % (blank, banVer)
@ -160,8 +158,7 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
if conf.dbms in PGSQL_ALIASES:
setDbms("PostgreSQL")
if conf.getBanner:
self.banner = inject.getValue("VERSION()")
self.getPrematureBanner("VERSION()")
if not conf.extensiveFp:
return True
@ -186,8 +183,7 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
setDbms("PostgreSQL")
if conf.getBanner:
self.banner = inject.getValue("VERSION()")
self.getPrematureBanner("VERSION()")
if not conf.extensiveFp:
return True

View File

@ -41,6 +41,7 @@ from lib.core.exception import sqlmapUndefinedMethod
from lib.core.exception import sqlmapUnsupportedFeatureException
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
@ -72,6 +73,13 @@ class Enumeration:
pass
def getPrematureBanner(self, query):
if conf.getBanner:
self.banner = inject.getValue(query)
bannerParser(self.banner)
def getBanner(self):
infoMsg = "fetching banner"
logger.info(infoMsg)

View File

@ -6,37 +6,26 @@
<info type="Windows"/>
</regexp>
<regexp value="Microsoft.*7\.0">
<info type="Windows" distrib="Vista"/>
<regexp value="Service Pack (\d)">
<info sp="1"/>
</regexp>
<regexp value="Microsoft.*7\.0.*Service Pack (\d)">
<info type="Windows" distrib="Vista" sp="1"/>
<regexp value="Microsoft.*7\.0">
<info type="Windows" distrib="Vista"/>
</regexp>
<regexp value="Microsoft.*6\.0">
<info type="Windows" distrib="2003"/>
</regexp>
<regexp value="Microsoft.*6\.0.*Service Pack (\d)">
<info type="Windows" distrib="2003" sp="1"/>
</regexp>
<regexp value="Microsoft.*5\.1">
<info type="Windows" distrib="XP"/>
</regexp>
<regexp value="Microsoft.*5\.1.*Service Pack (\d)">
<info type="Windows" distrib="XP" sp="1"/>
</regexp>
<regexp value="Microsoft.*5\.0">
<info type="Windows" distrib="2000"/>
</regexp>
<regexp value="Microsoft.*5\.0.*Service Pack (\d)">
<info type="Windows" distrib="2000" sp="1"/>
</regexp>
<!-- Linux -->
<regexp value="Linux">
@ -83,6 +72,7 @@
<info type="Linux" distrib="Ubuntu"/>
</regexp>
<!-- Unices -->
<regexp value="FreeBSD">
<info type="FreeBSD"/>

View File

@ -6,11 +6,13 @@
<info version="1"/>
</regexp>
<!-- Windows -->
<regexp value="^([\d\.\-]+)[\-\_\ ].*nt$">
<info version="1" type="Windows"/>
</regexp>
<!-- Debian -->
<regexp value="^([\d\.]+)[\-\_]Debian[\-\_][\d\.]+potato">
<info version="1" type="Linux" distrib="Debian" release="2.1" codename="Potato"/>
@ -36,6 +38,7 @@
<info version="1" type="Linux" distrib="Debian" codename="Testing"/>
</regexp>
<!-- Ubuntu -->
<regexp value="(5\.0\.67)-0ubuntu6">
<info version="1" type="Linux" distrib="Ubuntu" release="8.10" codename="Intrepid"/>

View File

@ -6,6 +6,7 @@
<info version="1"/>
</regexp>
<!-- Ubuntu -->
<regexp value="PostgreSQL\s+(8\.2\.7)\s+on\s+.*?\s+\(Ubuntu 4\.2\.3-2ubuntu4\)">
<info version="1" type="Linux" distrib="Ubuntu" release="8.10" codename="Intrepid"/>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<regexp value="PHP[\-\_\/\ ]([\d\.]+)">
<info technology="PHP" tech_version="1"/>
</regexp>
<regexp value="JSP[\-\_\/\ ]([\d\.]+)">
<info technology="JSP" tech_version="1"/>
</regexp>
<regexp value="ASP">
<info technology="ASP" type="Windows" distrib="2000|XP|2003|2008|Vista"/>
</regexp>
<regexp value="ASP\.NET">
<info technology="ASP.NET" type="Windows" distrib="2000|XP|2003|2008|Vista"/>
</regexp>
<regexp value="(JBoss|Servlet|Tomcat)[\-\_\/\ ]([\d\.]+)">
<info technology="Tomcat" tech_version="2"/>
</regexp>
</root>