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

Skip to content

Commit c8f943f

Browse files
committed
Now, if the back-end dbms type has been identified by the detection engine, skips the fingerprint phase.
Major code refactoring and commenting to detection engine. Ask user whether or not to proceed to test remaining parameters after an injection point has been identified. Restore beep at SQL injection find. Avoid reuse of same variable in DBMS handler code. Minor adjustment of payloads XML file.
1 parent fcdebbd commit c8f943f

17 files changed

Lines changed: 210 additions & 118 deletions

File tree

lib/controller/checks.py

Lines changed: 106 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,24 @@ def unescape(string, dbms):
6767
"Sybase": Sybase.unescape
6868
}
6969

70-
return unescaper[dbms](string)
70+
if dbms in unescaper:
71+
return unescaper[dbms](string)
72+
else:
73+
return string
74+
75+
def unescapeDbms(payload, injection, dbms):
76+
# If this is a DBMS-specific test (dbms), sqlmap identified the
77+
# DBMS during previous a test (injection.dbms) or the user
78+
# provided a DBMS (conf.dbms), unescape the strings between single
79+
# quotes in the payload
80+
if injection.dbms is not None:
81+
payload = unescape(payload, injection.dbms)
82+
elif dbms is not None:
83+
payload = unescape(payload, dbms)
84+
elif conf.dbms is not None:
85+
payload = unescape(payload, conf.dbms)
86+
87+
return payload
7188

7289
def checkSqlInjection(place, parameter, value):
7390
# Store here the details about boundaries and payload used to
@@ -77,42 +94,46 @@ def checkSqlInjection(place, parameter, value):
7794
for test in conf.tests:
7895
title = test.title
7996
stype = test.stype
80-
proceed = True
8197

98+
# Skip test if the risk is higher than the provided (or default)
99+
# value
82100
# Parse test's <risk>
83101
if test.risk > conf.risk:
84102
debugMsg = "skipping test '%s' because the risk " % title
85103
debugMsg += "is higher than the provided"
86104
logger.debug(debugMsg)
87105
continue
88106

107+
# Skip test if the level is higher than the provided (or default)
108+
# value
89109
# Parse test's <level>
90110
if test.level > conf.level:
91111
debugMsg = "skipping test '%s' because the level " % title
92112
debugMsg += "is higher than the provided"
93113
logger.debug(debugMsg)
94114
continue
95115

96-
if "details" in test and "dbms" in test.details:
97-
dbms = test.details.dbms
98-
else:
99-
dbms = None
100-
101-
# Skip current test if it is the same SQL injection type
102-
# already identified by another test
116+
# Skip test if it is the same SQL injection type already
117+
# identified by another test
103118
if injection.data and stype in injection.data:
104119
debugMsg = "skipping test '%s' because " % title
105-
debugMsg += "we have already the payload for %s" % PAYLOAD.SQLINJECTION[stype]
120+
debugMsg += "the payload for %s has " % PAYLOAD.SQLINJECTION[stype]
121+
debugMsg += "already been identified"
106122
logger.debug(debugMsg)
107-
108123
continue
109124

110-
# Skip DBMS-specific tests if they do not match the DBMS
111-
# identified
125+
# Skip DBMS-specific test if it does not match either the
126+
# previously identified or the user's provided DBMS
127+
if "details" in test and "dbms" in test.details:
128+
dbms = test.details.dbms
129+
else:
130+
dbms = None
131+
112132
if dbms is not None:
113133
if injection.dbms is not None and injection.dbms != dbms:
114134
debugMsg = "skipping test '%s' because " % title
115-
debugMsg += "the back-end DBMS is %s" % injection.dbms
135+
debugMsg += "the back-end DBMS identified is "
136+
debugMsg += "%s" % injection.dbms
116137
logger.debug(debugMsg)
117138

118139
continue
@@ -128,16 +149,10 @@ def checkSqlInjection(place, parameter, value):
128149
logger.info(infoMsg)
129150

130151
# Parse test's <request>
131-
payload = agent.cleanupPayload(test.request.payload)
132-
133-
if dbms:
134-
payload = unescape(payload, dbms)
135-
136-
if "comment" in test.request:
137-
comment = test.request.comment
138-
else:
139-
comment = ""
140-
testPayload = "%s%s" % (payload, comment)
152+
comment = agent.getComment(test.request)
153+
fstPayload = agent.cleanupPayload(test.request.payload)
154+
fstPayload = unescapeDbms(fstPayload, injection, dbms)
155+
fstPayload = "%s%s" % (fstPayload, comment)
141156

142157
if conf.prefix is not None and conf.suffix is not None:
143158
# Create a custom boundary object for user's supplied prefix
@@ -162,17 +177,21 @@ def checkSqlInjection(place, parameter, value):
162177
else:
163178
boundary.ptype = 1
164179

165-
# Prepend user's provided boundaries to all others
180+
# Prepend user's provided boundaries to all others boundaries
166181
conf.boundaries.insert(0, boundary)
167182

168183
for boundary in conf.boundaries:
184+
injectable = False
185+
186+
# Skip boundary if the level is higher than the provided (or
187+
# default) value
169188
# Parse boundary's <level>
170189
if boundary.level > conf.level:
171190
# NOTE: shall we report every single skipped boundary too?
172191
continue
173192

174-
# Parse test's <clause> and boundary's <clause>
175193
# Skip boundary if it does not match against test's <clause>
194+
# Parse test's <clause> and boundary's <clause>
176195
clauseMatch = False
177196

178197
for clauseTest in test.clause:
@@ -183,8 +202,8 @@ def checkSqlInjection(place, parameter, value):
183202
if test.clause != [ 0 ] and boundary.clause != [ 0 ] and not clauseMatch:
184203
continue
185204

186-
# Parse test's <where> and boundary's <where>
187205
# Skip boundary if it does not match against test's <where>
206+
# Parse test's <where> and boundary's <where>
188207
whereMatch = False
189208

190209
for where in test.where:
@@ -199,11 +218,10 @@ def checkSqlInjection(place, parameter, value):
199218
prefix = boundary.prefix if boundary.prefix else ""
200219
suffix = boundary.suffix if boundary.suffix else ""
201220
ptype = boundary.ptype
202-
injectable = False
203221

204222
# If the previous injections succeeded, we know which prefix,
205-
# postfix and parameter type to use for further tests, no
206-
# need to cycle through all of the boundaries anymore
223+
# suffix and parameter type to use for further tests, no
224+
# need to cycle through the boundaries for the following tests
207225
condBound = (injection.prefix is not None and injection.suffix is not None)
208226
condBound &= (injection.prefix != prefix or injection.suffix != suffix)
209227
condType = injection.ptype is not None and injection.ptype != ptype
@@ -213,50 +231,50 @@ def checkSqlInjection(place, parameter, value):
213231

214232
# For each test's <where>
215233
for where in test.where:
216-
# The <where> tag defines where to add our injection
217-
# string to the parameter under assessment.
234+
# Threat the parameter original value according to the
235+
# test's <where> tag
218236
if where == 1:
219237
origValue = value
220238
elif where == 2:
221239
origValue = "-%s" % value
222240
elif where == 3:
223241
origValue = ""
224242

225-
# Forge payload by prepending with boundary's prefix and
226-
# appending with boundary's suffix the test's
227-
# ' <payload><command> ' string
228-
boundPayload = "%s%s %s %s" % (origValue, prefix, testPayload, suffix)
243+
# Forge request payload by prepending with boundary's
244+
# prefix and appending the boundary's suffix to the
245+
# test's ' <payload><comment> ' string
246+
boundPayload = "%s%s %s %s" % (origValue, prefix, fstPayload, suffix)
229247
boundPayload = boundPayload.strip()
230248
boundPayload = agent.cleanupPayload(boundPayload)
231249
reqPayload = agent.payload(place, parameter, value, boundPayload)
232250

251+
# Perform the test's request and check whether or not the
252+
# payload was successful
233253
# Parse test's <response>
234-
# Check wheather or not the payload was successful
235254
for method, check in test.response.items():
236255
check = agent.cleanupPayload(check)
237256

238257
# In case of boolean-based blind SQL injection
239258
if method == "comparison":
240259
sndPayload = agent.cleanupPayload(test.response.comparison)
260+
sndPayload = unescapeDbms(sndPayload, injection, dbms)
261+
sndPayload = "%s%s" % (sndPayload, comment)
241262

242-
if dbms:
243-
sndPayload = unescape(sndPayload, dbms)
244-
245-
if "comment" in test.response:
246-
sndComment = test.response.comment
247-
else:
248-
sndComment = ""
249-
250-
sndPayload = "%s%s" % (sndPayload, sndComment)
263+
# Forge response payload by prepending with
264+
# boundary's prefix and appending the boundary's
265+
# suffix to the test's ' <payload><comment> '
266+
# string
251267
boundPayload = "%s%s %s %s" % (origValue, prefix, sndPayload, suffix)
252268
boundPayload = boundPayload.strip()
253269
boundPayload = agent.cleanupPayload(boundPayload)
254270
cmpPayload = agent.payload(place, parameter, value, boundPayload)
255271

256-
# Useful to set conf.matchRatio at first
272+
# Useful to set conf.matchRatio at first based on
273+
# the False response content
257274
conf.matchRatio = None
258275
_ = Request.queryPage(cmpPayload, place)
259276

277+
# Compare True and False response contents
260278
trueResult = Request.queryPage(reqPayload, place)
261279

262280
if trueResult:
@@ -273,6 +291,8 @@ def checkSqlInjection(place, parameter, value):
273291

274292
# In case of error-based or UNION query SQL injections
275293
elif method == "grep":
294+
# Perform the test's request and grep the response
295+
# body for the test's <grep> regular expression
276296
reqBody, _ = Request.queryPage(reqPayload, place, content=True)
277297
match = re.search(check, reqBody, re.DOTALL | re.IGNORECASE)
278298

@@ -290,8 +310,11 @@ def checkSqlInjection(place, parameter, value):
290310

291311
injectable = True
292312

293-
# In case of time-based blind or stacked queries SQL injections
313+
# In case of time-based blind or stacked queries
314+
# SQL injections
294315
elif method == "time":
316+
# Perform the test's request and check how long
317+
# it takes to get the response back
295318
start = time.time()
296319
_ = Request.queryPage(reqPayload, place)
297320
duration = calculateDeltaSeconds(start)
@@ -302,26 +325,44 @@ def checkSqlInjection(place, parameter, value):
302325

303326
injectable = True
304327

305-
if injectable is True:
306-
injection.place = place
307-
injection.parameter = parameter
308-
injection.ptype = ptype
309-
injection.prefix = prefix
310-
injection.suffix = suffix
311-
312-
injection.data[stype] = (boundPayload, comment)
313-
314-
if "details" in test:
315-
for detailKey, detailValue in test.details.items():
316-
if detailKey == "dbms" and injection.dbms is None:
317-
injection.dbms = detailValue
318-
elif detailKey == "dbms_version" and injection.dbms_version is None:
319-
injection.dbms_version = detailValue
320-
elif detailKey == "os" and injection.os is None:
321-
injection.os = detailValue
328+
# If the injection test was successful feed the injection
329+
# object with the test's details
330+
if injectable is True:
331+
# Feed with the boundaries details only the first time a
332+
# test has been successful
333+
if injection.place is None or injection.parameter is None:
334+
injection.place = place
335+
injection.parameter = parameter
336+
injection.ptype = ptype
337+
injection.prefix = prefix
338+
injection.suffix = suffix
339+
340+
# Feed with test details every time a test is successful
341+
injection.data[stype] = (title, reqPayload, where, comment)
342+
343+
if "details" in test:
344+
for detailKey, detailValue in test.details.items():
345+
if detailKey == "dbms" and injection.dbms is None:
346+
injection.dbms = detailValue
347+
kb.dbms = detailValue
348+
elif detailKey == "dbms_version" and injection.dbms_version is None:
349+
injection.dbms_version = detailValue
350+
kb.dbmsVersion = [ detailValue ]
351+
elif detailKey == "os" and injection.os is None:
352+
injection.os = detailValue
353+
354+
beep()
355+
356+
# There is no need to perform this test for other
357+
# <where> tags
358+
break
322359

360+
if injectable is True:
361+
# There is no need to perform this test with others
362+
# boundaries
323363
break
324364

365+
# Return the injection object
325366
if injection.place is not None and injection.parameter is not None:
326367
return injection
327368
else:

lib/controller/controller.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ def __formatInjection(inj):
109109
for stype, sdata in inj.data.items():
110110
stype = PAYLOAD.SQLINJECTION[stype] if isinstance(stype, int) else stype
111111
data += " Type: %s\n" % stype
112-
data += " Payload: %s\n\n" % sdata[0]
112+
data += " Title: %s\n" % sdata[0]
113+
data += " Payload: %s\n\n" % sdata[1]
113114

114115
return data
115116

@@ -126,13 +127,16 @@ def __showInjections():
126127

127128
def __saveToSessionFile():
128129
for inj in kb.injections:
130+
if inj.place is None or inj.parameter is None:
131+
continue
132+
129133
setInjection(inj)
130134

131135
place = inj.place
132136
parameter = inj.parameter
133137

134138
for stype, sdata in inj.data.items():
135-
payload = sdata[0]
139+
payload = sdata[1]
136140

137141
if stype == 1:
138142
kb.booleanTest = payload
@@ -313,6 +317,8 @@ def start():
313317
parameters.remove(place)
314318
parameters.insert(0, place)
315319

320+
proceed = True
321+
316322
for place in parameters:
317323
# Test User-Agent header only if --level >= 4
318324
condition = (place == "User-Agent" and conf.level < 4)
@@ -325,6 +331,9 @@ def start():
325331
if not conf.paramDict.has_key(place):
326332
continue
327333

334+
if not proceed:
335+
break
336+
328337
paramDict = conf.paramDict[place]
329338
for parameter, value in paramDict.items():
330339
testSqlInj = True
@@ -361,15 +370,22 @@ def start():
361370

362371
injection = checkSqlInjection(place, parameter, value)
363372

364-
if injection:
373+
if injection is not None and injection.place is not None:
365374
kb.injections.append(injection)
375+
376+
msg = "%s parameter '%s' " % (injection.place, injection.parameter)
377+
msg += "is vulnerable. Do you want to keep testing the others? [y/N] "
378+
test = readInput(msg, default="N")
379+
380+
if test[0] in ("n", "N"):
381+
proceed = False
382+
break
366383
else:
367384
warnMsg = "%s parameter '%s' is not " % (place, parameter)
368385
warnMsg += "injectable"
369386
logger.warn(warnMsg)
370387

371-
if (len(kb.injections) == 0 or len(kb.injections) == 1 and kb.injections[0].parameter is None) \
372-
and not kb.injection.place and not kb.injection.parameter:
388+
if len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None):
373389
errMsg = "all parameters are not injectable, try "
374390
errMsg += "a higher --level"
375391
raise sqlmapNotVulnerableException, errMsg

0 commit comments

Comments
 (0)