sqlmap/lib/controller/checks.py

1013 lines
42 KiB
Python
Raw Normal View History

2008-10-15 19:38:22 +04:00
#!/usr/bin/env python
"""
2008-10-15 19:56:32 +04:00
$Id$
2008-10-15 19:38:22 +04:00
2012-01-11 18:59:46 +04:00
Copyright (c) 2006-2012 sqlmap developers (http://www.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
"""
2011-12-16 03:33:44 +04:00
import httplib
import re
2010-05-21 17:36:49 +04:00
import socket
2008-10-15 19:38:22 +04:00
import time
from lib.core.agent import agent
from lib.core.common import arrayizeValue
from lib.core.common import Backend
2010-10-25 23:16:42 +04:00
from lib.core.common import beep
2010-12-06 18:50:19 +03:00
from lib.core.common import extractRegexResult
from lib.core.common import extractTextTagContent
2010-12-29 22:39:32 +03:00
from lib.core.common import findDynamicContent
from lib.core.common import Format
from lib.core.common import getComparePageRatio
2012-01-14 00:56:06 +04:00
from lib.core.common import getLastRequestHTTPError
from lib.core.common import getSortedInjectionTests
2010-06-02 16:45:40 +04:00
from lib.core.common import getUnicode
from lib.core.common import intersect
from lib.core.common import listToStrValue
from lib.core.common import parseFilePaths
2010-12-04 18:47:02 +03:00
from lib.core.common import popValue
from lib.core.common import pushValue
2008-10-15 19:38:22 +04:00
from lib.core.common import randomInt
from lib.core.common import randomStr
2010-10-11 15:47:07 +04:00
from lib.core.common import readInput
from lib.core.common import showStaticWords
2011-06-08 18:35:23 +04:00
from lib.core.common import singleTimeWarnMessage
from lib.core.common import wasLastRequestDBMSError
2010-12-26 16:20:52 +03:00
from lib.core.common import wasLastRequestHTTPError
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 logger
2011-07-08 10:02:31 +04:00
from lib.core.datatype import AttribDict
from lib.core.datatype import InjectionDict
2011-03-11 23:16:34 +03:00
from lib.core.enums import HTTPHEADER
2010-11-08 12:49:57 +03:00
from lib.core.enums import HTTPMETHOD
from lib.core.enums import NULLCONNECTION
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 sqlmapConnectionException
2010-02-10 12:39:36 +03:00
from lib.core.exception import sqlmapNoneDataException
from lib.core.exception import sqlmapSilentQuitException
2010-10-11 15:47:07 +04:00
from lib.core.exception import sqlmapUserQuitException
from lib.core.settings import CONSTANT_RATIO
from lib.core.settings import UNKNOWN_DBMS_VERSION
from lib.core.settings import LOWER_RATIO_BOUND
2010-12-29 22:01:29 +03:00
from lib.core.settings import UPPER_RATIO_BOUND
from lib.core.settings import IDS_WAF_CHECK_PAYLOAD
from lib.core.threads import getCurrentThreadData
2008-10-15 19:38:22 +04:00
from lib.request.connect import Connect as Request
from lib.request.inject import checkBooleanExpression
from lib.request.templates import getPageTemplate
2011-06-18 16:34:41 +04:00
from lib.techniques.union.test import unionTest
from lib.techniques.union.use import configUnion
def checkSqlInjection(place, parameter, value):
# Store here the details about boundaries and payload used to
# successfully inject
2011-07-08 10:02:31 +04:00
injection = InjectionDict()
2011-03-17 12:23:46 +03:00
# Localized thread data needed for some methods
threadData = getCurrentThreadData()
# Set the flag for sql injection test mode
2010-12-07 16:34:06 +03:00
kb.testMode = True
2010-12-04 18:47:02 +03:00
for test in getSortedInjectionTests():
try:
2011-01-01 22:22:44 +03:00
if kb.endDetection:
break
2010-12-18 13:42:09 +03:00
title = test.title
stype = test.stype
clause = test.clause
if stype == PAYLOAD.TECHNIQUE.UNION:
configUnion(test.request.char)
if "[CHAR]" in title:
if conf.uChar is None:
continue
else:
title = title.replace("[CHAR]", conf.uChar)
elif "[RANDNUM]" in title or "(NULL)" in title:
title = title.replace("[RANDNUM]", "random number")
if test.request.columns == "[COLSTART]-[COLSTOP]":
if conf.uCols is None:
continue
else:
title = title.replace("[COLSTART]", str(conf.uColsStart))
title = title.replace("[COLSTOP]", str(conf.uColsStop))
2011-05-11 01:33:06 +04:00
elif conf.uCols is not None:
debugMsg = "skipping test '%s' because the user " % title
debugMsg += "provided custom column range %s" % conf.uCols
logger.debug(debugMsg)
continue
2010-12-18 13:42:09 +03:00
# Skip test if the user's wants to test only for a specific
# technique
if conf.tech and isinstance(conf.tech, list) and stype not in conf.tech:
2010-12-18 13:42:09 +03:00
debugMsg = "skipping test '%s' because the user " % title
debugMsg += "specified to test only for "
2011-05-03 01:48:08 +04:00
debugMsg += "%s techniques" % " & ".join(map(lambda x: PAYLOAD.SQLINJECTION[x], conf.tech))
logger.debug(debugMsg)
continue
2011-02-02 01:04:48 +03:00
# Skip test if it is the same SQL injection type already
# identified by another test
if injection.data and stype in injection.data:
debugMsg = "skipping test '%s' because " % title
debugMsg += "the payload for %s has " % PAYLOAD.SQLINJECTION[stype]
debugMsg += "already been identified"
logger.debug(debugMsg)
continue
# Skip tests if title is not included by the given filter
2011-09-27 18:31:58 +04:00
if conf.testFilter:
if not any(re.search(conf.testFilter, str(item), re.I) for item in [test.title, test.vector,\
test.details.dbms if "details" in test and "dbms" in test.details else ""]):
debugMsg = "skipping test '%s' because " % title
2012-03-14 02:03:23 +04:00
debugMsg += "its name/vector/dbms is not included by the given filter"
2011-09-27 18:31:58 +04:00
logger.debug(debugMsg)
continue
else:
# Skip test if the risk is higher than the provided (or default)
# value
# Parse test's <risk>
if test.risk > conf.risk:
debugMsg = "skipping test '%s' because the risk (%d) " % (title, test.risk)
debugMsg += "is higher than the provided (%d)" % conf.risk
logger.debug(debugMsg)
continue
# Skip test if the level is higher than the provided (or default)
# value
# Parse test's <level>
if test.level > conf.level:
debugMsg = "skipping test '%s' because the level (%d) " % (title, test.level)
debugMsg += "is higher than the provided (%d)" % conf.level
logger.debug(debugMsg)
continue
2010-12-18 13:42:09 +03:00
# Skip DBMS-specific test if it does not match either the
# previously identified or the user's provided DBMS (either
# from program switch or from parsed error message(s))
2010-12-18 13:42:09 +03:00
if "details" in test and "dbms" in test.details:
dbms = test.details.dbms
else:
2010-12-18 13:42:09 +03:00
dbms = None
if dbms is not None:
if injection.dbms is not None and not intersect(injection.dbms, dbms):
2010-12-18 13:42:09 +03:00
debugMsg = "skipping test '%s' because " % title
debugMsg += "the back-end DBMS identified is "
debugMsg += "%s" % injection.dbms
logger.debug(debugMsg)
continue
if conf.dbms is not None and not intersect(conf.dbms.lower(), [value.lower() for value in arrayizeValue(dbms)]):
2010-12-18 13:42:09 +03:00
debugMsg = "skipping test '%s' because " % title
debugMsg += "the provided DBMS is %s" % conf.dbms
logger.debug(debugMsg)
continue
if len(Backend.getErrorParsedDBMSes()) > 0 and not intersect(dbms, Backend.getErrorParsedDBMSes()) and kb.skipOthersDbms is None:
msg = "parsed error message(s) showed that the "
msg += "back-end DBMS could be %s. " % Format.getErrorParsedDBMSes()
msg += "Do you want to skip test payloads specific for other DBMSes? [Y/n]"
if conf.realTest or readInput(msg, default="Y") in ("y", "Y"):
kb.skipOthersDbms = Backend.getErrorParsedDBMSes()
else:
kb.skipOthersDbms = []
if kb.skipOthersDbms and not intersect(dbms, kb.skipOthersDbms):
debugMsg = "skipping test '%s' because " % title
2011-01-02 10:09:04 +03:00
debugMsg += "the parsed error message(s) showed "
debugMsg += "that the back-end DBMS could be "
debugMsg += "%s" % Format.getErrorParsedDBMSes()
logger.debug(debugMsg)
continue
2010-12-18 13:42:09 +03:00
# Skip test if it does not match the same SQL injection clause
# already identified by another test
clauseMatch = False
2010-12-18 13:42:09 +03:00
for clauseTest in clause:
if injection.clause is not None and clauseTest in injection.clause:
clauseMatch = True
break
2012-02-22 19:53:36 +04:00
if clause != [0] and injection.clause and injection.clause != [0] and not clauseMatch:
2010-12-18 13:42:09 +03:00
debugMsg = "skipping test '%s' because the clauses " % title
debugMsg += "differs from the clause already identified"
logger.debug(debugMsg)
continue
# Skip test if the user provided custom character
if conf.uChar is not None and ("random number" in title or "(NULL)" in title):
debugMsg = "skipping test '%s' because the user " % title
debugMsg += "provided a specific character, %s" % conf.uChar
2011-01-30 19:23:19 +03:00
logger.debug(debugMsg)
continue
2010-12-18 13:42:09 +03:00
infoMsg = "testing '%s'" % title
logger.info(infoMsg)
# Force back-end DBMS according to the current
# test value for proper payload unescaping
Backend.forceDbms(dbms[0] if isinstance(dbms, list) else dbms)
2010-12-18 13:42:09 +03:00
# Parse test's <request>
comment = agent.getComment(test.request) if len(conf.boundaries) > 1 else None
fstPayload = agent.cleanupPayload(test.request.payload, origValue=value)
2010-12-18 13:42:09 +03:00
for boundary in conf.boundaries:
injectable = False
# Skip boundary if the level is higher than the provided (or
# default) value
# Parse boundary's <level>
if boundary.level > conf.level:
continue
# Skip boundary if it does not match against test's <clause>
# Parse test's <clause> and boundary's <clause>
clauseMatch = False
for clauseTest in test.clause:
if clauseTest in boundary.clause:
clauseMatch = True
break
2012-02-22 19:53:36 +04:00
if test.clause != [0] and boundary.clause != [0] and not clauseMatch:
2010-12-18 13:42:09 +03:00
continue
# Skip boundary if it does not match against test's <where>
# Parse test's <where> and boundary's <where>
whereMatch = False
for where in test.where:
if where in boundary.where:
whereMatch = True
break
if not whereMatch:
continue
# Parse boundary's <prefix>, <suffix> and <ptype>
prefix = boundary.prefix if boundary.prefix else ""
suffix = boundary.suffix if boundary.suffix else ""
ptype = boundary.ptype
# If the previous injections succeeded, we know which prefix,
# suffix and parameter type to use for further tests, no
# need to cycle through the boundaries for the following tests
condBound = (injection.prefix is not None and injection.suffix is not None)
condBound &= (injection.prefix != prefix or injection.suffix != suffix)
condType = injection.ptype is not None and injection.ptype != ptype
if condBound or condType:
continue
# For each test's <where>
for where in test.where:
templatePayload = None
vector = None
2010-12-18 13:42:09 +03:00
# Threat the parameter original value according to the
# test's <where> tag
2011-02-02 16:34:09 +03:00
if where == PAYLOAD.WHERE.ORIGINAL:
2010-12-18 13:42:09 +03:00
origValue = value
2011-02-02 16:34:09 +03:00
elif where == PAYLOAD.WHERE.NEGATIVE:
# Use different page template than the original
# one as we are changing parameters value, which
# will likely result in a different content
if not conf.logicalNegate:
2011-10-24 04:40:06 +04:00
origValue = "-%s" % randomInt()
else:
origValue = "%s AND %s=%s" % (origValue, randomInt(), randomInt())
templatePayload = agent.payload(place, parameter, newValue=origValue, where=where)
2011-02-02 16:34:09 +03:00
elif where == PAYLOAD.WHERE.REPLACE:
2010-12-18 13:42:09 +03:00
origValue = ""
kb.pageTemplate, kb.errorIsNone = getPageTemplate(templatePayload, place)
2010-12-18 13:42:09 +03:00
# Forge request payload by prepending with boundary's
# prefix and appending the boundary's suffix to the
# test's ' <payload><comment> ' string
boundPayload = agent.prefixQuery(fstPayload, prefix, where, clause)
boundPayload = agent.suffixQuery(boundPayload, comment, suffix, where)
reqPayload = agent.payload(place, parameter, newValue=boundPayload, where=where)
2010-12-18 13:42:09 +03:00
# Perform the test's request and check whether or not the
# payload was successful
# Parse test's <response>
for method, check in test.response.items():
check = agent.cleanupPayload(check, origValue=value)
2010-12-18 13:42:09 +03:00
# In case of boolean-based blind SQL injection
if method == PAYLOAD.METHOD.COMPARISON:
# Generate payload used for comparison
def genCmpPayload():
sndPayload = agent.cleanupPayload(test.response.comparison, origValue=value)
# Forge response payload by prepending with
# boundary's prefix and appending the boundary's
# suffix to the test's ' <payload><comment> '
# string
boundPayload = agent.prefixQuery(sndPayload, prefix, where, clause)
boundPayload = agent.suffixQuery(boundPayload, comment, suffix, where)
cmpPayload = agent.payload(place, parameter, newValue=boundPayload, where=where)
2011-02-08 03:02:54 +03:00
return cmpPayload
2010-12-18 13:42:09 +03:00
# Useful to set kb.matchRatio at first based on
# the False response content
kb.matchRatio = None
kb.negativeLogic = (where == PAYLOAD.WHERE.NEGATIVE)
2012-02-22 19:53:36 +04:00
Request.queryPage(genCmpPayload(), place, raise404=False)
falsePage = threadData.lastComparisonPage
2010-12-18 13:42:09 +03:00
# Perform the test's True request
trueResult = Request.queryPage(reqPayload, place, raise404=False)
truePage = threadData.lastComparisonPage
2010-12-18 13:42:09 +03:00
if trueResult:
falseResult = Request.queryPage(genCmpPayload(), place, raise404=False)
2010-12-18 13:42:09 +03:00
# Perform the test's False request
if not falseResult:
infoMsg = "%s parameter '%s' is '%s' injectable " % (place, parameter, title)
logger.info(infoMsg)
2010-12-18 13:42:09 +03:00
injectable = True
2012-04-11 01:57:00 +04:00
2012-04-11 02:29:39 +04:00
if not injectable and not conf.string:
trueSet = set(extractTextTagContent(truePage))
falseSet = set(extractTextTagContent(falsePage))
candidate = reduce(lambda x, y: x or (y.strip() if y.strip() in (kb.pageTemplate or "") else None), (trueSet - falseSet), None)
if candidate:
conf.string = candidate
2012-04-11 01:57:00 +04:00
infoMsg = "%s parameter '%s' seems to be '%s' injectable (with --string=%s)" % (place, parameter, title, repr(candidate).lstrip('u'))
logger.info(infoMsg)
2012-04-11 01:57:00 +04:00
injectable = True
# In case of error-based SQL injection
2010-12-18 13:42:09 +03:00
elif method == PAYLOAD.METHOD.GREP:
# Perform the test's request and grep the response
# body for the test's <grep> regular expression
try:
page, headers = Request.queryPage(reqPayload, place, content=True, raise404=False)
output = extractRegexResult(check, page, re.DOTALL | re.IGNORECASE) \
or extractRegexResult(check, listToStrValue(headers.headers \
if headers else None), re.DOTALL | re.IGNORECASE) \
or extractRegexResult(check, threadData.lastRedirectMsg[1] \
if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \
threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)
if output:
result = output == "1"
if result:
infoMsg = "%s parameter '%s' is '%s' injectable " % (place, parameter, title)
logger.info(infoMsg)
injectable = True
except sqlmapConnectionException, msg:
2011-04-30 17:20:05 +04:00
debugMsg = "problem occured most likely because the "
debugMsg += "server hasn't recovered as expected from the "
debugMsg += "error-based payload used ('%s')" % msg
logger.debug(debugMsg)
2010-12-18 13:42:09 +03:00
# In case of time-based blind or stacked queries
# SQL injections
elif method == PAYLOAD.METHOD.TIME:
# Perform the test's request
trueResult = Request.queryPage(reqPayload, place, timeBasedCompare=True, raise404=False)
2010-12-18 13:42:09 +03:00
if trueResult:
# Confirm test's results
trueResult = Request.queryPage(reqPayload, place, timeBasedCompare=True, raise404=False)
2010-12-18 13:42:09 +03:00
if trueResult:
infoMsg = "%s parameter '%s' is '%s' injectable " % (place, parameter, title)
logger.info(infoMsg)
injectable = True
# In case of UNION query SQL injection
elif method == PAYLOAD.METHOD.UNION:
# Test for UNION injection and set the sample
# payload as well as the vector.
# NOTE: vector is set to a tuple with 6 elements,
# used afterwards by Agent.forgeInbandQuery()
# method to forge the UNION query payload
2011-01-15 19:59:53 +03:00
2011-01-18 01:57:33 +03:00
configUnion(test.request.char, test.request.columns)
2011-04-20 03:04:10 +04:00
if not Backend.getIdentifiedDbms():
2011-01-15 19:59:53 +03:00
warnMsg = "using unescaped version of the test "
warnMsg += "because of zero knowledge of the "
2011-12-30 18:20:06 +04:00
warnMsg += "back-end DBMS. You can try to "
2011-06-16 16:12:30 +04:00
warnMsg += "explicitly set it using the --dbms "
warnMsg += "option"
2011-06-09 03:32:44 +04:00
singleTimeWarnMessage(warnMsg)
# Test for UNION query SQL injection
2011-01-16 04:17:09 +03:00
reqPayload, vector = unionTest(comment, place, parameter, value, prefix, suffix)
if isinstance(reqPayload, basestring):
infoMsg = "%s parameter '%s' is '%s' injectable" % (place, parameter, title)
logger.info(infoMsg)
injectable = True
2011-01-16 03:15:30 +03:00
# Overwrite 'where' because it can be set
# by unionTest() directly
where = vector[6]
kb.previousMethod = method
2010-12-18 13:42:09 +03:00
# If the injection test was successful feed the injection
# object with the test's details
if injectable is True:
# Feed with the boundaries details only the first time a
# test has been successful
if injection.place is None or injection.parameter is None:
if place in (PLACE.UA, PLACE.REFERER, PLACE.HOST):
2011-02-12 02:36:23 +03:00
injection.parameter = place
2010-12-18 13:42:09 +03:00
else:
injection.parameter = parameter
injection.place = place
injection.ptype = ptype
injection.prefix = prefix
injection.suffix = suffix
injection.clause = clause
# Feed with test details every time a test is successful
if hasattr(test, "details"):
for dKey, dValue in test.details.items():
if dKey == "dbms":
if not isinstance(dValue, list):
injection.dbms = Backend.setDbms(dValue)
else:
2011-06-02 03:00:18 +04:00
Backend.forceDbms(dValue[0], True)
elif dKey == "dbms_version" and injection.dbms_version is None:
injection.dbms_version = Backend.setVersion(dValue)
elif dKey == "os" and injection.os is None:
injection.os = Backend.setOs(dValue)
if vector is None and "vector" in test and test.vector is not None:
vector = "%s%s" % (test.vector, comment or "")
2010-12-18 13:42:09 +03:00
if method == PAYLOAD.METHOD.TIME:
reqPayload = reqPayload.replace(test.request.payload.replace("[SLEEPTIME]", str(conf.timeSec)), test.request.payload)
2011-07-08 10:02:31 +04:00
injection.data[stype] = AttribDict()
2010-12-18 13:42:09 +03:00
injection.data[stype].title = title
injection.data[stype].payload = agent.removePayloadDelimiters(reqPayload)
2010-12-18 13:42:09 +03:00
injection.data[stype].where = where
injection.data[stype].vector = vector
2010-12-18 13:42:09 +03:00
injection.data[stype].comment = comment
injection.data[stype].templatePayload = templatePayload
2011-01-14 17:55:59 +03:00
injection.data[stype].matchRatio = kb.matchRatio
2010-12-18 13:42:09 +03:00
2011-01-14 17:55:59 +03:00
injection.conf.textOnly = conf.textOnly
2011-06-11 12:33:36 +04:00
injection.conf.titles = conf.titles
2011-01-16 02:11:36 +03:00
injection.conf.string = conf.string
injection.conf.regexp = conf.regexp
injection.conf.optimize = conf.optimize
2010-12-23 17:06:22 +03:00
if conf.beep or conf.realTest:
2010-12-18 13:42:09 +03:00
beep()
# There is no need to perform this test for other
# <where> tags
break
if injectable is True:
# There is no need to perform this test with others
# boundaries
break
2008-10-15 19:38:22 +04:00
# Reset forced back-end DBMS value
Backend.flushForcedDbms()
2010-12-18 13:42:09 +03:00
except KeyboardInterrupt:
2011-04-08 14:39:07 +04:00
warnMsg = "user aborted during detection phase"
2010-12-18 13:42:09 +03:00
logger.warn(warnMsg)
message = "How do you want to proceed? [(S)kip current test/(e)nd detection phase/(n)ext parameter/(q)uit]"
choice = readInput(message, default="S", checkBatch=False)
if choice[0] in ("s", "S"):
pass
elif choice[0] in ("n", "N"):
return None
elif choice[0] in ("e", "E"):
2011-06-08 18:44:11 +04:00
kb.endDetection = True
elif choice[0] in ("q", "Q"):
raise sqlmapUserQuitException
2010-12-04 18:47:02 +03:00
finally:
# Reset forced back-end DBMS value
Backend.flushForcedDbms()
2011-06-02 03:00:18 +04:00
Backend.flushForcedDbms(True)
# Return the injection object
if injection.place is not None and injection.parameter is not None:
2011-06-03 19:43:50 +04:00
if not conf.dropSetCookie and PAYLOAD.TECHNIQUE.BOOLEAN in injection.data and injection.data[PAYLOAD.TECHNIQUE.BOOLEAN].vector.startswith('OR'):
2011-06-08 18:40:42 +04:00
warnMsg = "in OR boolean-based injections, please consider usage "
2012-02-01 18:49:42 +04:00
warnMsg += "of switch '--drop-set-cookie' if you experience any "
2011-06-03 19:43:50 +04:00
warnMsg += "problems during data retrieval"
logger.warn(warnMsg)
injection = checkFalsePositives(injection)
return injection
else:
return None
2008-10-15 19:38:22 +04:00
def checkFalsePositives(injection):
"""
Checks for false positives (only in single special cases)
"""
retVal = injection
2011-10-10 21:29:54 +04:00
if len(injection.data) == 1 and any(map(lambda x: x in injection.data, [PAYLOAD.TECHNIQUE.BOOLEAN, PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED]))\
or len(injection.data) == 2 and all(map(lambda x: x in injection.data, [PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED]))\
or len(injection.data) == 1 and 'Generic' in injection.data.values()[0].title and not Backend.getDbms():
pushValue(kb.injection)
2011-04-25 04:36:09 +04:00
infoMsg = "checking if the injection point on %s " % injection.place
infoMsg += "parameter '%s' is a false positive" % injection.parameter
logger.info(infoMsg)
def _():
return int(randomInt(2)) + 1
kb.injection = injection
randInt1, randInt2, randInt3 = (_() for i in xrange(3))
2011-06-08 18:40:42 +04:00
# Just in case (also, they have to be different than 0 because of the last test)
while randInt1 == randInt2:
randInt2 = _()
2011-06-08 18:40:42 +04:00
# Simple arithmetic operations which should show basic
# arithmetic ability of the backend if it's really injectable
if not checkBooleanExpression("(%d+%d)=%d" % (randInt1, randInt2, randInt1 + randInt2)):
retVal = None
elif checkBooleanExpression("%d>(%d+%d)" % (min(randInt1, randInt2), randInt3, max(randInt1, randInt2))):
retVal = None
elif checkBooleanExpression("(%d+%d)>%d" % (randInt3, min(randInt1, randInt2), randInt1 + randInt2 + randInt3)):
retVal = None
elif not checkBooleanExpression("%d=(%d+%d)" % (randInt1 + randInt2, randInt1, randInt2)):
retVal = None
if retVal is None:
warnMsg = "false positive or unexploitable injection point detected"
logger.warn(warnMsg)
kb.injection = popValue()
return retVal
2011-01-15 16:15:10 +03:00
def heuristicCheckSqlInjection(place, parameter):
if kb.nullConnection:
debugMsg = "heuristic checking skipped "
debugMsg += "because NULL connection used"
logger.debug(debugMsg)
2012-03-05 13:42:52 +04:00
return None
if wasLastRequestDBMSError():
debugMsg = "heuristic checking skipped "
debugMsg += "because original page content "
debugMsg += "contains DBMS error"
logger.debug(debugMsg)
return None
2010-10-11 16:26:35 +04:00
prefix = ""
suffix = ""
2010-10-11 16:26:35 +04:00
if conf.prefix or conf.suffix:
2010-10-11 16:26:35 +04:00
if conf.prefix:
prefix = conf.prefix
if conf.suffix:
suffix = conf.suffix
2010-10-11 16:26:35 +04:00
payload = "%s%s%s" % (prefix, randomStr(length=10, alphabet=['"', '\'', ')', '(']), suffix)
payload = agent.payload(place, parameter, newValue=payload)
page, _ = Request.queryPage(payload, place, content=True, raise404=False)
parseFilePaths(page)
2010-12-22 16:09:04 +03:00
result = wasLastRequestDBMSError()
2010-10-16 19:10:48 +04:00
infoMsg = "heuristic test shows that %s " % place
2010-11-16 13:52:49 +03:00
infoMsg += "parameter '%s' might " % parameter
2010-10-16 19:10:48 +04:00
kb.heuristicTest = result
2010-10-11 16:26:35 +04:00
if result:
infoMsg += "be injectable (possible DBMS: %s)" % (Format.getErrorParsedDBMSes() or UNKNOWN_DBMS_VERSION)
2010-10-11 16:26:35 +04:00
logger.info(infoMsg)
else:
2010-11-16 13:52:49 +03:00
infoMsg += "not be injectable"
2010-12-08 17:46:07 +03:00
logger.warn(infoMsg)
2010-10-11 16:26:35 +04:00
2010-12-20 13:13:14 +03:00
return result
def simpletonCheckSqlInjection(place, parameter, value):
"""
This is a function for the quickest and simplest
sql injection check (e.g. AND 1=1) - only works
with integer parameters
"""
result = False
randInt = randomInt()
2011-01-03 12:16:42 +03:00
if value.isdigit():
payload = "%s AND %d=%d" % (value, randInt, randInt)
2011-01-03 12:16:42 +03:00
else:
return False
2011-01-03 12:16:42 +03:00
payload = agent.payload(place, parameter, value, payload)
firstPage, _ = Request.queryPage(payload, place, content=True, raise404=False)
if not (wasLastRequestDBMSError() or wasLastRequestHTTPError()):
2011-01-03 11:46:20 +03:00
if getComparePageRatio(kb.originalPage, firstPage, filtered=True) > CONSTANT_RATIO:
2012-02-22 19:53:36 +04:00
payload = "%s AND %d=%d" % (value, randInt, randInt + 1)
2011-01-03 12:16:42 +03:00
payload = agent.payload(place, parameter, value, payload)
secondPage, _ = Request.queryPage(payload, place, content=True, raise404=False)
2011-01-03 11:46:20 +03:00
result = getComparePageRatio(firstPage, secondPage, filtered=True) <= CONSTANT_RATIO
infoMsg = "simpleton test shows that %s " % place
infoMsg += "parameter '%s' might " % parameter
if result:
infoMsg += "be injectable"
logger.info(infoMsg)
else:
infoMsg += "not be injectable"
logger.warn(infoMsg)
return result
2008-10-15 19:38:22 +04:00
def checkDynParam(place, parameter, value):
"""
This function checks if the url parameter is dynamic. If it is
dynamic, the content of the page differs, otherwise the
dynamicity might depend on another parameter.
"""
if kb.redirectChoice:
return None
2010-12-18 12:51:34 +03:00
kb.matchRatio = None
2012-01-14 00:56:06 +04:00
dynResult = None
randInt = randomInt()
2010-11-10 01:44:23 +03:00
infoMsg = "testing if %s parameter '%s' is dynamic" % (place, parameter)
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
2012-01-14 00:56:06 +04:00
try:
payload = agent.payload(place, parameter, value, getUnicode(randInt))
dynResult = Request.queryPage(payload, place, raise404=False)
2008-10-15 19:38:22 +04:00
2012-01-14 00:56:06 +04:00
if not dynResult:
infoMsg = "confirming that %s parameter '%s' is dynamic" % (place, parameter)
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
2012-01-14 00:56:06 +04:00
randInt = randomInt()
payload = agent.payload(place, parameter, value, getUnicode(randInt))
dynResult = Request.queryPage(payload, place, raise404=False)
except sqlmapConnectionException:
pass
2008-10-15 19:38:22 +04:00
2012-01-14 00:56:06 +04:00
if dynResult is None:
return None
else:
return not dynResult
2008-10-15 19:38:22 +04:00
def checkDynamicContent(firstPage, secondPage):
"""
2010-12-29 22:39:32 +03:00
This function checks for the dynamic content in the provided pages
"""
2010-11-04 12:18:32 +03:00
2010-11-04 00:51:36 +03:00
if kb.nullConnection:
debugMsg = "dynamic content checking skipped "
2010-11-04 12:18:32 +03:00
debugMsg += "because NULL connection used"
logger.debug(debugMsg)
2010-11-04 00:51:36 +03:00
return
if any(page is None for page in (firstPage, secondPage)):
warnMsg = "can't check dynamic content "
warnMsg += "because of lack of page content"
logger.critical(warnMsg)
return
seqMatcher = getCurrentThreadData().seqMatcher
seqMatcher.set_seq1(firstPage)
seqMatcher.set_seq2(secondPage)
2010-10-07 02:29:52 +04:00
2010-12-29 22:39:32 +03:00
# In case of an intolerable difference turn on dynamicity removal engine
if seqMatcher.quick_ratio() <= UPPER_RATIO_BOUND:
2010-12-29 22:39:32 +03:00
findDynamicContent(firstPage, secondPage)
2010-10-25 23:45:53 +04:00
2010-12-29 22:39:32 +03:00
count = 0
while not Request.queryPage():
count += 1
2010-12-29 22:39:32 +03:00
if count > conf.retries:
warnMsg = "target url is too dynamic. "
warnMsg += "Switching to '--text-only' "
logger.warn(warnMsg)
conf.textOnly = True
return
2010-10-25 23:45:53 +04:00
2010-12-29 22:39:32 +03:00
warnMsg = "target url is heavily dynamic"
warnMsg += ", sqlmap is going to retry the request"
logger.critical(warnMsg)
2010-10-25 23:45:53 +04:00
2010-12-29 22:39:32 +03:00
secondPage, _ = Request.queryPage(content=True)
findDynamicContent(firstPage, secondPage)
2010-09-13 17:31:01 +04:00
2008-10-15 19:38:22 +04:00
def checkStability():
"""
This function checks if the URL content is stable requesting the
2010-09-13 19:19:47 +04:00
same page two times with a small delay within each request to
2008-10-15 19:38:22 +04:00
assume that it is stable.
In case the content of the page differs when requesting
the same page, the dynamicity might depend on other parameters,
like for instance string matching (--string).
"""
infoMsg = "testing if the url is stable, wait a few seconds"
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
2012-02-22 19:53:36 +04:00
firstPage = kb.originalPage # set inside checkConnection()
time.sleep(1)
2011-11-29 20:59:06 +04:00
secondPage, _ = Request.queryPage(content=True, raise404=False)
2008-10-15 19:38:22 +04:00
if kb.redirectChoice:
return None
2010-10-25 17:52:21 +04:00
kb.pageStable = (firstPage == secondPage)
2010-10-25 17:52:21 +04:00
if kb.pageStable:
if firstPage:
2011-04-30 19:29:59 +04:00
infoMsg = "url is stable"
logger.info(infoMsg)
else:
errMsg = "there was an error checking the stability of page "
errMsg += "because of lack of content. please check the "
errMsg += "page request results (and probable errors) by "
errMsg += "using higher verbosity levels"
logger.error(errMsg)
2010-10-25 17:52:21 +04:00
else:
warnMsg = "url is not stable, sqlmap will base the page "
2010-10-16 19:10:48 +04:00
warnMsg += "comparison on a sequence matcher. If no dynamic nor "
warnMsg += "injectable parameters are detected, or in case of "
warnMsg += "junk results, refer to user's manual paragraph "
warnMsg += "'Page comparison' and provide a string or regular "
warnMsg += "expression to match on"
logger.warn(warnMsg)
2008-10-15 19:38:22 +04:00
message = "how do you want to proceed? [(C)ontinue/(s)tring/(r)egex/(q)uit] "
2010-12-23 17:06:22 +03:00
if not conf.realTest:
2010-12-20 13:32:58 +03:00
test = readInput(message, default="C")
else:
test = None
2010-10-16 19:10:48 +04:00
if test and test[0] in ("q", "Q"):
raise sqlmapUserQuitException
2010-10-16 19:10:48 +04:00
elif test and test[0] in ("s", "S"):
showStaticWords(firstPage, secondPage)
2010-10-16 19:10:48 +04:00
message = "please enter value for parameter 'string': "
test = readInput(message)
2010-10-16 19:10:48 +04:00
if test:
conf.string = test
2010-11-04 12:18:32 +03:00
if kb.nullConnection:
debugMsg = "turning off NULL connection "
2010-11-04 12:18:32 +03:00
debugMsg += "support because of string checking"
logger.debug(debugMsg)
kb.nullConnection = None
else:
2010-11-10 22:44:51 +03:00
errMsg = "Empty value supplied"
raise sqlmapNoneDataException, errMsg
2010-10-16 19:10:48 +04:00
elif test and test[0] in ("r", "R"):
message = "please enter value for parameter 'regex': "
test = readInput(message)
2010-10-16 19:10:48 +04:00
if test:
conf.regex = test
2010-11-04 12:18:32 +03:00
if kb.nullConnection:
debugMsg = "turning off NULL connection "
2010-11-04 12:18:32 +03:00
debugMsg += "support because of regex checking"
logger.debug(debugMsg)
kb.nullConnection = None
else:
2010-11-10 22:44:51 +03:00
errMsg = "Empty value supplied"
raise sqlmapNoneDataException, errMsg
2011-01-06 11:54:50 +03:00
else:
2010-12-29 22:39:32 +03:00
checkDynamicContent(firstPage, secondPage)
2010-11-29 18:25:45 +03:00
2010-10-25 17:52:21 +04:00
return kb.pageStable
2010-03-12 15:23:05 +03:00
2008-10-15 19:38:22 +04:00
def checkString():
if not conf.string:
return True
infoMsg = "testing if the provided string is within the "
infoMsg += "target URL page content"
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
page, headers = Request.queryPage(content=True)
rawResponse = "%s%s" % (listToStrValue(headers.headers if headers else ""), page)
2008-10-15 19:38:22 +04:00
if conf.string not in rawResponse:
warnMsg = "you provided '%s' as the string to " % conf.string
warnMsg += "match, but such a string is not within the target "
warnMsg += "URL raw response, sqlmap will carry on anyway"
logger.warn(warnMsg)
2008-10-15 19:38:22 +04:00
return True
2008-10-15 19:38:22 +04:00
def checkRegexp():
if not conf.regexp:
return True
infoMsg = "testing if the provided regular expression matches within "
infoMsg += "the target URL page content"
logger.info(infoMsg)
page, headers = Request.queryPage(content=True)
rawResponse = "%s%s" % (listToStrValue(headers.headers if headers else ""), page)
if not re.search(conf.regexp, rawResponse, re.I | re.M):
warnMsg = "you provided '%s' as the regular expression to " % conf.regexp
warnMsg += "match, but such a regular expression does not have any "
warnMsg += "match within the target URL raw response, sqlmap "
warnMsg += "will carry on anyway"
logger.warn(warnMsg)
return True
def checkWaf():
"""
Reference: http://seclists.org/nmap-dev/2011/q2/att-1005/http-waf-detect.nse
"""
if not conf.checkWaf:
return False
infoMsg = "testing if the target is protected by "
infoMsg += "some kind of WAF/IPS/IDS"
logger.info(infoMsg)
retVal = False
backup = dict(conf.parameters)
conf.parameters = dict(backup)
conf.parameters[PLACE.GET] = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + "&"
conf.parameters[PLACE.GET] += "%s=%d %s" % (randomStr(), randomInt(), IDS_WAF_CHECK_PAYLOAD)
kb.matchRatio = None
Request.queryPage()
if kb.errorIsNone and kb.matchRatio is None:
kb.matchRatio = LOWER_RATIO_BOUND
conf.parameters = dict(backup)
conf.parameters[PLACE.GET] = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + "&"
conf.parameters[PLACE.GET] += "%s=%d" % (randomStr(), randomInt())
trueResult = Request.queryPage()
if trueResult:
conf.parameters = dict(backup)
conf.parameters[PLACE.GET] = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + "&"
conf.parameters[PLACE.GET] += "%s=%d %s" % (randomStr(), randomInt(), IDS_WAF_CHECK_PAYLOAD)
falseResult = Request.queryPage()
if not falseResult:
retVal = True
conf.parameters = dict(backup)
if retVal:
2011-07-07 00:41:13 +04:00
warnMsg = "it appears that the target is protected. Please "
2012-02-01 18:49:42 +04:00
warnMsg += "consider usage of tamper scripts (option '--tamper')"
logger.warn(warnMsg)
else:
infoMsg = "it appears that the target is not protected"
logger.info(infoMsg)
return retVal
2010-09-16 12:43:10 +04:00
def checkNullConnection():
2010-10-15 15:17:17 +04:00
"""
Reference: http://www.wisec.it/sectou.php?id=472f952d79293
"""
2010-09-16 12:43:10 +04:00
infoMsg = "testing NULL connection to the target url"
logger.info(infoMsg)
try:
page, headers, _ = Request.getPage(method=HTTPMETHOD.HEAD)
2010-12-06 18:50:19 +03:00
2011-03-11 23:16:34 +03:00
if not page and HTTPHEADER.CONTENT_LENGTH in headers:
2010-11-08 12:49:57 +03:00
kb.nullConnection = NULLCONNECTION.HEAD
2010-10-15 16:46:41 +04:00
infoMsg = "NULL connection is supported with HEAD header"
logger.info(infoMsg)
2010-09-16 12:43:10 +04:00
else:
page, headers, _ = Request.getPage(auxHeaders={HTTPHEADER.RANGE: "bytes=-1"})
2010-12-06 18:50:19 +03:00
2011-03-11 23:16:34 +03:00
if page and len(page) == 1 and HTTPHEADER.CONTENT_RANGE in headers:
2010-11-08 12:49:57 +03:00
kb.nullConnection = NULLCONNECTION.RANGE
2010-09-16 12:43:10 +04:00
2010-10-15 16:46:41 +04:00
infoMsg = "NULL connection is supported with GET header "
infoMsg += "'%s'" % kb.nullConnection
logger.info(infoMsg)
2010-12-06 18:50:19 +03:00
2010-09-16 12:43:10 +04:00
except sqlmapConnectionException, errMsg:
errMsg = getUnicode(errMsg)
raise sqlmapConnectionException, errMsg
return kb.nullConnection is not None
2010-11-15 15:19:22 +03:00
def checkConnection(suppressOutput=False):
if not any([conf.proxy, conf.tor]):
2011-06-24 21:19:24 +04:00
try:
socket.getaddrinfo(conf.hostname, None)
except socket.gaierror:
errMsg = "host '%s' does not exist" % conf.hostname
raise sqlmapConnectionException, errMsg
2010-05-21 17:36:49 +04:00
2010-11-15 15:19:22 +03:00
if not suppressOutput:
infoMsg = "testing connection to the target url"
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
try:
page, _ = Request.queryPage(content=True, noteResponseTime=False)
kb.originalPage = kb.pageTemplate = page
2011-01-01 23:19:55 +03:00
kb.errorIsNone = False
if not kb.originalPage and wasLastRequestHTTPError():
errMsg = "unable to retrieve page content"
raise sqlmapConnectionException, errMsg
elif wasLastRequestDBMSError():
warnMsg = "there is a DBMS error found in the HTTP response body"
warnMsg += "which could interfere with the results of the tests"
2011-01-01 23:19:55 +03:00
logger.warn(warnMsg)
elif wasLastRequestHTTPError():
2012-01-14 00:56:06 +04:00
warnMsg = "the web server responded with an HTTP error code (%d) " % getLastRequestHTTPError()
warnMsg += "which could interfere with the results of the tests"
2011-01-01 23:19:55 +03:00
logger.warn(warnMsg)
else:
kb.errorIsNone = True
except sqlmapConnectionException, errMsg:
2010-06-02 16:45:40 +04:00
errMsg = getUnicode(errMsg)
logger.critical(errMsg)
2011-12-16 03:33:44 +04:00
if any(code in kb.httpErrorCodes for code in (httplib.NOT_FOUND, )):
2012-02-07 15:16:03 +04:00
if conf.multipleTargets:
return False
2011-12-16 03:29:11 +04:00
msg = "it is not recommended to continue in this kind of cases. Do you want to quit and make sure that everything is set up properly? [Y/n] "
if readInput(msg, default="Y") not in ("n", "N"):
raise sqlmapSilentQuitException
else:
kb.ignoreNotFound = True
2011-12-05 13:25:56 +04:00
else:
2011-12-16 03:29:11 +04:00
raise
2008-10-15 19:38:22 +04:00
return True