First update for Issue #75 (error-based dumping)

This commit is contained in:
Miroslav Stampar 2012-07-12 14:31:28 +02:00
parent 3fd5119f3f
commit 65639cdda6
5 changed files with 35 additions and 18 deletions

View File

@ -1449,7 +1449,7 @@ def __setKnowledgeBaseAttributes(flushAll=True):
kb.dnsMode = False kb.dnsMode = False
kb.dnsTest = None kb.dnsTest = None
kb.docRoot = None kb.docRoot = None
kb.dumpMode = False kb.dumpTable = None
kb.dynamicMarkings = [] kb.dynamicMarkings = []
kb.dynamicParameters = False kb.dynamicParameters = False
kb.endDetection = False kb.endDetection = False

View File

@ -506,3 +506,6 @@ GENERIC_SQL_COMMENT = "-- "
# Threshold value for turning back on time auto-adjustment mechanism # Threshold value for turning back on time auto-adjustment mechanism
VALID_TIME_CHARS_RUN_THRESHOLD = 100 VALID_TIME_CHARS_RUN_THRESHOLD = 100
# Check for empty columns only if table is sufficiently large
CHECK_ZERO_COLUMNS_THRESHOLD = 10

View File

@ -218,7 +218,7 @@ def decodePage(page, contentEncoding, contentType):
def processResponse(page, responseHeaders): def processResponse(page, responseHeaders):
kb.processResponseCounter += 1 kb.processResponseCounter += 1
if not kb.dumpMode: if not kb.dumpTable:
parseResponse(page, responseHeaders if kb.processResponseCounter < PARSE_HEADERS_LIMIT else None) parseResponse(page, responseHeaders if kb.processResponseCounter < PARSE_HEADERS_LIMIT else None)
if conf.parseErrors: if conf.parseErrors:

View File

@ -32,9 +32,11 @@ from lib.core.data import logger
from lib.core.data import queries from lib.core.data import queries
from lib.core.enums import DBMS from lib.core.enums import DBMS
from lib.core.enums import PAYLOAD from lib.core.enums import PAYLOAD
from lib.core.settings import CHECK_ZERO_COLUMNS_THRESHOLD
from lib.core.settings import FROM_DUMMY_TABLE from lib.core.settings import FROM_DUMMY_TABLE
from lib.core.settings import MYSQL_ERROR_CHUNK_LENGTH from lib.core.settings import MYSQL_ERROR_CHUNK_LENGTH
from lib.core.settings import MSSQL_ERROR_CHUNK_LENGTH from lib.core.settings import MSSQL_ERROR_CHUNK_LENGTH
from lib.core.settings import NULL
from lib.core.settings import PARTIAL_VALUE_MARKER from lib.core.settings import PARTIAL_VALUE_MARKER
from lib.core.settings import SLOW_ORDER_COUNT_THRESHOLD from lib.core.settings import SLOW_ORDER_COUNT_THRESHOLD
from lib.core.settings import SQL_SCALAR_REGEX from lib.core.settings import SQL_SCALAR_REGEX
@ -44,7 +46,7 @@ from lib.core.threads import runThreads
from lib.core.unescaper import unescaper from lib.core.unescaper import unescaper
from lib.request.connect import Connect as Request from lib.request.connect import Connect as Request
def __oneShotErrorUse(expression, field): def __oneShotErrorUse(expression, field=None):
offset = 1 offset = 1
partialValue = None partialValue = None
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
@ -56,6 +58,12 @@ def __oneShotErrorUse(expression, field):
offset += len(partialValue) offset += len(partialValue)
threadData.resumed = retVal is not None and not partialValue threadData.resumed = retVal is not None and not partialValue
if Backend.isDbms(DBMS.MYSQL):
chunk_length = MYSQL_ERROR_CHUNK_LENGTH
elif Backend.isDbms(DBMS.MSSQL):
chunk_length = MSSQL_ERROR_CHUNK_LENGTH
else:
chunk_length = None chunk_length = None
if retVal is None or partialValue: if retVal is None or partialValue:
@ -64,20 +72,17 @@ def __oneShotErrorUse(expression, field):
check = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) check = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop)
trimcheck = "%s(?P<result>.*?)</" % (kb.chars.start) trimcheck = "%s(?P<result>.*?)</" % (kb.chars.start)
if field:
nulledCastedField = agent.nullAndCastField(field) nulledCastedField = agent.nullAndCastField(field)
if Backend.isDbms(DBMS.MYSQL): if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)):
chunk_length = MYSQL_ERROR_CHUNK_LENGTH nulledCastedField = queries[Backend.getIdentifiedDbms()].substring.query % (nulledCastedField, offset, chunk_length)
nulledCastedField = queries[DBMS.MYSQL].substring.query % (nulledCastedField, offset, chunk_length)
elif Backend.isDbms(DBMS.MSSQL):
chunk_length = MSSQL_ERROR_CHUNK_LENGTH
nulledCastedField = queries[DBMS.MSSQL].substring.query % (nulledCastedField, offset, chunk_length)
# Forge the error-based SQL injection request # Forge the error-based SQL injection request
vector = kb.injection.data[PAYLOAD.TECHNIQUE.ERROR].vector vector = kb.injection.data[PAYLOAD.TECHNIQUE.ERROR].vector
query = agent.prefixQuery(vector) query = agent.prefixQuery(vector)
query = agent.suffixQuery(query) query = agent.suffixQuery(query)
injExpression = expression.replace(field, nulledCastedField, 1) injExpression = expression.replace(field, nulledCastedField, 1) if field else expression
injExpression = unescaper.unescape(injExpression) injExpression = unescaper.unescape(injExpression)
injExpression = query.replace("[QUERY]", injExpression) injExpression = query.replace("[QUERY]", injExpression)
payload = agent.payload(newValue=injExpression) payload = agent.payload(newValue=injExpression)
@ -148,7 +153,7 @@ def __oneShotErrorUse(expression, field):
return safecharencode(retVal) if kb.safeCharEncode else retVal return safecharencode(retVal) if kb.safeCharEncode else retVal
def __errorFields(expression, expressionFields, expressionFieldsList, expected=None, num=None): def __errorFields(expression, expressionFields, expressionFieldsList, expected=None, num=None, emptyFields=None):
outputs = [] outputs = []
origExpr = None origExpr = None
@ -169,14 +174,14 @@ def __errorFields(expression, expressionFields, expressionFieldsList, expected=N
else: else:
expressionReplaced = expression.replace(expressionFields, field, 1) expressionReplaced = expression.replace(expressionFields, field, 1)
output = __oneShotErrorUse(expressionReplaced, field) output = NULL if emptyFields and field in emptyFields else __oneShotErrorUse(expressionReplaced, field)
if not kb.threadContinue: if not kb.threadContinue:
return None return None
if kb.fileReadMode and output and output.strip(): if kb.fileReadMode and output and output.strip():
print print
elif output is not None and not (threadData.resumed and kb.suppressResumeInfo): elif output is not None and not (threadData.resumed and kb.suppressResumeInfo) and not (emptyFields and field in emptyFields):
dataToStdout("[%s] [INFO] %s: %s\r\n" % (time.strftime("%X"), "resumed" if threadData.resumed else "retrieved", safecharencode(output))) dataToStdout("[%s] [INFO] %s: %s\r\n" % (time.strftime("%X"), "resumed" if threadData.resumed else "retrieved", safecharencode(output)))
if isinstance(num, int): if isinstance(num, int):
@ -222,6 +227,7 @@ def errorUse(expression, expected=None, dump=False):
abortedFlag = False abortedFlag = False
count = None count = None
emptyFields = []
start = time.time() start = time.time()
startLimit = 0 startLimit = 0
stopLimit = None stopLimit = None
@ -349,6 +355,14 @@ def errorUse(expression, expected=None, dump=False):
numThreads = min(conf.threads, (stopLimit - startLimit)) numThreads = min(conf.threads, (stopLimit - startLimit))
threadData.shared.outputs = BigArray() threadData.shared.outputs = BigArray()
if kb.dumpTable and (len(expressionFieldsList) < (stopLimit - startLimit) > CHECK_ZERO_COLUMNS_THRESHOLD):
for field in expressionFieldsList:
if __oneShotErrorUse("SELECT COUNT(%s) FROM %s" % (field, kb.dumpTable)) == '0':
emptyFields.append(field)
debugMsg = "column '%s' for table '%s' appears to be empty. "
debugMsg += "It's values will not be dumped"
logger.debug(debugMsg)
if stopLimit > TURN_OFF_RESUME_INFO_LIMIT: if stopLimit > TURN_OFF_RESUME_INFO_LIMIT:
kb.suppressResumeInfo = True kb.suppressResumeInfo = True
debugMsg = "suppressing possible resume console info because of " debugMsg = "suppressing possible resume console info because of "
@ -366,7 +380,7 @@ def errorUse(expression, expected=None, dump=False):
except StopIteration: except StopIteration:
break break
output = __errorFields(expression, expressionFields, expressionFieldsList, expected, num) output = __errorFields(expression, expressionFields, expressionFieldsList, expected, num, emptyFields)
if not kb.threadContinue: if not kb.threadContinue:
break break

View File

@ -1573,7 +1573,7 @@ class Enumeration:
kb.data.cachedColumns = foundData kb.data.cachedColumns = foundData
try: try:
kb.dumpMode = True kb.dumpTable = "%s.%s" % (conf.db, tbl)
if not safeSQLIdentificatorNaming(conf.db) in kb.data.cachedColumns \ if not safeSQLIdentificatorNaming(conf.db) in kb.data.cachedColumns \
or safeSQLIdentificatorNaming(tbl, True) not in \ or safeSQLIdentificatorNaming(tbl, True) not in \
@ -1782,7 +1782,7 @@ class Enumeration:
logger.critical(errMsg) logger.critical(errMsg)
finally: finally:
kb.dumpMode = False kb.dumpTable = None
def dumpAll(self): def dumpAll(self):
if conf.db is not None and conf.tbl is None: if conf.db is not None and conf.tbl is None: