Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7e3b24a

Browse files
committed
Rewrite from scratch the detection engine. Now it performs checks defined in payload.xml. User can specify its own.
All (hopefully) functionalities should still be working. Added two switches, --level and --risk to specify which injection tests and boundaries to use. The main advantage now is that sqlmap is able to identify initially which injection types are present so for instance if boolean-based blind is not supported, but error-based is, sqlmap will keep going and work!
1 parent a8b38ba commit 7e3b24a

24 files changed

Lines changed: 1966 additions & 331 deletions

File tree

lib/controller/checks.py

Lines changed: 249 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from lib.core.agent import agent
1717
from lib.core.common import beep
18+
from lib.core.common import calculateDeltaSeconds
1819
from lib.core.common import getUnicode
1920
from lib.core.common import randomInt
2021
from lib.core.common import randomStr
@@ -26,87 +27,286 @@
2627
from lib.core.data import kb
2728
from lib.core.data import logger
2829
from lib.core.data import paths
30+
from lib.core.datatype import advancedDict
31+
from lib.core.datatype import injectionDict
2932
from lib.core.enums import HTTPMETHOD
3033
from lib.core.enums import NULLCONNECTION
34+
from lib.core.enums import PAYLOAD
3135
from lib.core.exception import sqlmapConnectionException
3236
from lib.core.exception import sqlmapGenericException
3337
from lib.core.exception import sqlmapNoneDataException
3438
from lib.core.exception import sqlmapSiteTooDynamic
3539
from lib.core.exception import sqlmapUserQuitException
3640
from lib.core.session import setString
3741
from lib.core.session import setRegexp
42+
from lib.core.settings import ERROR_SPACE
43+
from lib.core.settings import ERROR_EMPTY_CHAR
3844
from lib.request.connect import Connect as Request
45+
from plugins.dbms.firebird.syntax import Syntax as Firebird
46+
from plugins.dbms.postgresql.syntax import Syntax as PostgreSQL
47+
from plugins.dbms.mssqlserver.syntax import Syntax as MSSQLServer
48+
from plugins.dbms.oracle.syntax import Syntax as Oracle
49+
from plugins.dbms.mysql.syntax import Syntax as MySQL
50+
from plugins.dbms.access.syntax import Syntax as Access
51+
from plugins.dbms.sybase.syntax import Syntax as Sybase
52+
from plugins.dbms.sqlite.syntax import Syntax as SQLite
53+
from plugins.dbms.maxdb.syntax import Syntax as MaxDB
54+
55+
56+
def unescape(string, dbms):
57+
unescaper = {
58+
"Access": Access.unescape,
59+
"Firebird": Firebird.unescape,
60+
"MaxDB": MaxDB.unescape,
61+
"Microsoft SQL Server": MSSQLServer.unescape,
62+
"MySQL": MySQL.unescape,
63+
"Oracle": Oracle.unescape,
64+
"PostgreSQL": PostgreSQL.unescape,
65+
"SQLite": SQLite.unescape,
66+
"Sybase": Sybase.unescape
67+
}
68+
69+
if isinstance(dbms, list):
70+
dbmsunescaper = unescaper[dbms[0]]
71+
else:
72+
dbmsunescaper = unescaper[dbms]
3973

40-
def checkSqlInjection(place, parameter, value, parenthesis):
41-
"""
42-
This function checks if the GET, POST, Cookie, User-Agent
43-
parameters are affected by a SQL injection vulnerability and
44-
identifies the type of SQL injection:
74+
return dbmsunescaper(string)
4575

46-
* Unescaped numeric injection
47-
* Single quoted string injection
48-
* Double quoted string injection
49-
"""
76+
def checkSqlInjection(place, parameter, value):
77+
# Store here the details about boundaries and payload used to
78+
# successfully inject
79+
injection = injectionDict()
5080

51-
logic = conf.logic
52-
randInt = randomInt()
53-
randStr = randomStr()
54-
prefix = ""
55-
suffix = ""
56-
retVal = None
81+
for test in conf.tests:
82+
title = test.title
83+
stype = test.stype
84+
proceed = True
5785

58-
if conf.prefix or conf.suffix:
59-
if conf.prefix:
60-
prefix = conf.prefix
86+
# Parse test's <risk>
87+
if test.risk > conf.risk:
88+
debugMsg = "skipping test '%s' because the risk " % title
89+
debugMsg += "is higher than the provided"
90+
logger.debug(debugMsg)
91+
continue
6192

62-
if conf.suffix:
63-
suffix = conf.suffix
93+
# Parse test's <level>
94+
if test.level > conf.level:
95+
debugMsg = "skipping test '%s' because the level " % title
96+
debugMsg += "is higher than the provided"
97+
logger.debug(debugMsg)
98+
continue
99+
100+
if "details" in test and "dbms" in test.details:
101+
dbms = test.details.dbms
102+
else:
103+
dbms = None
64104

65-
for case in kb.injections.root.case:
66-
conf.matchRatio = None
105+
# Skip current test if it is the same SQL injection type
106+
# already identified by another test
107+
if injection.data and stype in injection.data:
108+
debugMsg = "skipping test '%s' because " % title
109+
debugMsg += "we have already the payload for %s" % PAYLOAD.SQLINJECTION[stype]
110+
logger.debug(debugMsg)
67111

68-
positive = case.test.positive
69-
negative = case.test.negative
112+
continue
113+
114+
# Skip DBMS-specific tests if they do not match the DBMS
115+
# identified
116+
if injection.dbms is not None and injection.dbms != dbms:
117+
debugMsg = "skipping test '%s' because " % title
118+
debugMsg += "the back-end DBMS is %s" % injection.dbms
119+
logger.debug(debugMsg)
70120

71-
if not prefix and not suffix and case.name == "custom":
72121
continue
73122

74-
infoMsg = "testing %s (%s) injection " % (case.desc, logic)
75-
infoMsg += "on %s parameter '%s'" % (place, parameter)
123+
infoMsg = "testing '%s'" % title
76124
logger.info(infoMsg)
77125

78-
payload = agent.payload(place, parameter, value, negative.format % eval(negative.params))
79-
_ = Request.queryPage(payload, place)
126+
# Parse test's <request>
127+
payload = agent.cleanupPayload(test.request.payload)
80128

81-
payload = agent.payload(place, parameter, value, positive.format % eval(positive.params))
82-
trueResult = Request.queryPage(payload, place)
129+
if dbms:
130+
payload = unescape(payload, dbms)
83131

84-
if trueResult:
85-
infoMsg = "confirming %s (%s) injection " % (case.desc, logic)
86-
infoMsg += "on %s parameter '%s'" % (place, parameter)
87-
logger.info(infoMsg)
132+
if "comment" in test.request:
133+
comment = test.request.comment
134+
else:
135+
comment = ""
136+
testPayload = "%s%s" % (payload, comment)
137+
138+
if conf.prefix is not None and conf.suffix is not None:
139+
boundary = advancedDict()
140+
141+
boundary.level = 1
142+
boundary.clause = [ 0 ]
143+
boundary.where = [ 1, 2, 3 ]
144+
# TODO: inspect the conf.prefix and conf.suffix to set
145+
# proper ptype
146+
boundary.ptype = 1
147+
boundary.prefix = conf.prefix
148+
boundary.suffix = conf.suffix
149+
150+
conf.boundaries.insert(0, boundary)
151+
152+
for boundary in conf.boundaries:
153+
# Parse boundary's <level>
154+
if boundary.level > conf.level:
155+
# NOTE: shall we report every single skipped boundary too?
156+
continue
88157

89-
payload = agent.payload(place, parameter, value, negative.format % eval(negative.params))
158+
# Parse test's <clause> and boundary's <clause>
159+
# Skip boundary if it does not match against test's <clause>
160+
clauseMatch = False
90161

91-
randInt = randomInt()
92-
randStr = randomStr()
162+
for clauseTest in test.clause:
163+
if clauseTest in boundary.clause:
164+
clauseMatch = True
165+
break
93166

94-
falseResult = Request.queryPage(payload, place)
167+
if test.clause != [ 0 ] and boundary.clause != [ 0 ] and not clauseMatch:
168+
continue
95169

96-
if not falseResult:
97-
infoMsg = "%s parameter '%s' is %s (%s) injectable " % (place, parameter, case.desc, logic)
98-
infoMsg += "with %d parenthesis" % parenthesis
99-
logger.info(infoMsg)
170+
# Parse test's <where> and boundary's <where>
171+
# Skip boundary if it does not match against test's <where>
172+
whereMatch = False
100173

101-
if conf.beep:
102-
beep()
174+
for where in test.where:
175+
if where in boundary.where:
176+
whereMatch = True
177+
break
103178

104-
retVal = case.name
105-
break
179+
if not whereMatch:
180+
continue
181+
182+
# Parse boundary's <prefix>, <suffix> and <ptype>
183+
prefix = boundary.prefix if boundary.prefix else ""
184+
suffix = boundary.suffix if boundary.suffix else ""
185+
ptype = boundary.ptype
186+
injectable = False
106187

107-
kb.paramMatchRatio[(place, parameter)] = conf.matchRatio
188+
# If the previous injections succeeded, we know which prefix,
189+
# postfix and parameter type to use for further tests, no
190+
# need to cycle through all of the boundaries anymore
191+
condBound = (injection.prefix is not None and injection.suffix is not None)
192+
condBound &= (injection.prefix != prefix or injection.suffix != suffix)
193+
condType = injection.ptype is not None and injection.ptype != ptype
194+
195+
if condBound or condType:
196+
continue
197+
198+
# For each test's <where>
199+
for where in test.where:
200+
# The <where> tag defines where to add our injection
201+
# string to the parameter under assessment.
202+
if where == 1:
203+
origValue = value
204+
elif where == 2:
205+
origValue = "-%s" % value
206+
elif where == 3:
207+
origValue = ""
208+
209+
# Forge payload by prepending with boundary's prefix and
210+
# appending with boundary's suffix the test's
211+
# ' <payload><command> ' string
212+
boundPayload = "%s%s %s %s" % (origValue, prefix, testPayload, suffix)
213+
boundPayload = boundPayload.strip()
214+
boundPayload = agent.cleanupPayload(boundPayload)
215+
reqPayload = agent.payload(place, parameter, value, boundPayload)
216+
217+
# Parse test's <response>
218+
# Check wheather or not the payload was successful
219+
for method, check in test.response.items():
220+
check = agent.cleanupPayload(check)
221+
222+
# In case of boolean-based blind SQL injection
223+
if method == "comparison":
224+
sndPayload = agent.cleanupPayload(test.response.comparison)
225+
226+
if dbms:
227+
sndPayload = unescape(sndPayload, dbms)
228+
229+
if "comment" in test.response:
230+
sndComment = test.response.comment
231+
else:
232+
sndComment = ""
233+
234+
sndPayload = "%s%s" % (sndPayload, sndComment)
235+
boundPayload = "%s%s %s %s" % (origValue, prefix, sndPayload, suffix)
236+
boundPayload = boundPayload.strip()
237+
boundPayload = agent.cleanupPayload(boundPayload)
238+
cmpPayload = agent.payload(place, parameter, value, boundPayload)
239+
240+
# Useful to set conf.matchRatio at first
241+
conf.matchRatio = None
242+
_ = Request.queryPage(cmpPayload, place)
243+
244+
trueResult = Request.queryPage(reqPayload, place)
245+
246+
if trueResult:
247+
falseResult = Request.queryPage(cmpPayload, place)
248+
249+
if not falseResult:
250+
infoMsg = "%s parameter '%s' is '%s' injectable " % (place, parameter, title)
251+
logger.info(infoMsg)
252+
253+
kb.paramMatchRatio[(place, parameter)] = conf.matchRatio
254+
injectable = True
255+
256+
kb.paramMatchRatio[(place, parameter)] = conf.matchRatio
257+
258+
# In case of error-based or UNION query SQL injections
259+
elif method == "grep":
260+
reqBody, _ = Request.queryPage(reqPayload, place, content=True)
261+
match = re.search(check, reqBody, re.DOTALL | re.IGNORECASE)
262+
263+
if not match:
264+
continue
265+
266+
output = match.group('result')
267+
268+
if output:
269+
output = output.replace(ERROR_SPACE, " ").replace(ERROR_EMPTY_CHAR, "")
270+
271+
if output == "1":
272+
infoMsg = "%s parameter '%s' is '%s' injectable " % (place, parameter, title)
273+
logger.info(infoMsg)
274+
275+
injectable = True
276+
277+
# In case of time-based blind or stacked queries SQL injections
278+
elif method == "time":
279+
start = time.time()
280+
_ = Request.queryPage(reqPayload, place)
281+
duration = calculateDeltaSeconds(start)
282+
283+
if duration >= conf.timeSec:
284+
infoMsg = "%s parameter '%s' is '%s' injectable " % (place, parameter, title)
285+
logger.info(infoMsg)
286+
287+
injectable = True
288+
289+
if injectable is True:
290+
injection.place = place
291+
injection.parameter = parameter
292+
injection.ptype = ptype
293+
injection.prefix = prefix
294+
injection.suffix = suffix
295+
296+
injection.data[stype] = (title, where, comment, boundPayload)
297+
298+
if "details" in test:
299+
for detailKey, detailValue in test.details.items():
300+
if detailKey == "dbms" and injection.dbms is None:
301+
injection.dbms = detailValue
302+
elif detailKey == "dbms_version" and injection.dbms_version is None:
303+
injection.dbms_version = detailValue
304+
elif detailKey == "os" and injection.os is None:
305+
injection.os = detailValue
306+
307+
break
108308

109-
return retVal
309+
return injection
110310

111311
def heuristicCheckSqlInjection(place, parameter, value):
112312
if kb.nullConnection:

0 commit comments

Comments
 (0)