1#!/usr/bin/env python 2 3# 4# 5# 6 7import datetime 8import sys 9import logging 10import urllib 11import urlparse 12try: 13 import libinjection 14except: 15 pass 16 17from tornado import template 18import tornado.httpserver 19import tornado.ioloop 20import tornado.web 21import tornado.wsgi 22import tornado.escape 23import tornado.options 24 25def breakapart(s): 26 """ attempts to add spaces in a SQLi so it renders nicely on the webpage 27 """ 28 return s.replace(',', ', ').replace('/*',' /*') 29 30# http://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks-in-python 31def chunks(l, n): 32 """ Yield successive n-sized chunks from l. 33 """ 34 for i in xrange(0, len(l), n): 35 yield l[i:i+n] 36 37def breakify(s): 38 output = "" 39 for c in chunks(s, 20): 40 output += c 41 if ' ' not in c: 42 output += ' ' 43 return output 44 45def print_token_string(tok): 46 """ 47 returns the value of token, handling opening and closing quote characters 48 """ 49 out = '' 50 if tok.str_open != '\0': 51 out += tok.str_open 52 out += tok.val 53 if tok.str_close != '\0': 54 out += tok.str_close 55 return out 56 57def print_token(tok): 58 """ 59 prints a token for use in unit testing 60 """ 61 out = '' 62 if tok.type == 's': 63 out += print_token_string(tok) 64 elif tok.type == 'v': 65 vc = tok.count; 66 if vc == 1: 67 out += '@' 68 elif vc == 2: 69 out += '@@' 70 out += print_token_string(tok) 71 else: 72 out += tok.val 73 return (tok.type, out) 74 75def alltokens(val, flags): 76 77 if flags & libinjection.FLAG_QUOTE_SINGLE: 78 contextstr = 'single' 79 elif flags & libinjection.FLAG_QUOTE_DOUBLE: 80 contextstr = 'double' 81 else: 82 contextstr = 'none' 83 84 if flags & libinjection.FLAG_SQL_ANSI: 85 commentstr = 'ansi' 86 elif flags & libinjection.FLAG_SQL_MYSQL: 87 commentstr = 'mysql' 88 else: 89 raise RuntimeException("bad quote context") 90 91 parse = { 92 'comment': commentstr, 93 'quote': contextstr 94 } 95 args = [] 96 sqlstate = libinjection.sqli_state() 97 libinjection.sqli_init(sqlstate, val, flags) 98 count = 0 99 while count < 25: 100 count += 1 101 ok = libinjection.sqli_tokenize(sqlstate) 102 if ok == 0: 103 break 104 args.append(print_token(sqlstate.current)) 105 106 107 parse['tokens'] = args 108 109 args = [] 110 fingerprint = libinjection.sqli_fingerprint(sqlstate, flags) 111 for i in range(len(sqlstate.fingerprint)): 112 args.append(print_token(libinjection.sqli_get_token(sqlstate,i))) 113 parse['folds'] = args 114 parse['sqli'] = bool(libinjection.sqli_blacklist(sqlstate) and libinjection.sqli_not_whitelist(sqlstate)) 115 parse['fingerprint'] = fingerprint 116 # todo add stats 117 118 return parse 119 120class PageHandler(tornado.web.RequestHandler): 121 def get(self, pagename): 122 if pagename == '': 123 pagename = 'home' 124 125 self.add_header('X-Content-Type-Options', 'nosniff') 126 self.add_header('X-XSS-Protection', '0') 127 128 self.render( 129 pagename + '.html', 130 title = pagename.replace('-',' '), 131 ssl_protocol=self.request.headers.get('X-SSL-Protocol', ''), 132 ssl_cipher=self.request.headers.get('X-SSL-Cipher', '') 133 ) 134 135class XssTestHandler(tornado.web.RequestHandler): 136 def get(self): 137 settings = self.application.settings 138 139 ldr = template.Loader(".") 140 141 args = ['', '', '', '', '', '', '', '', '', ''] 142 143 qsl = [ x.split('=', 1) for x in self.request.query.split('&') ] 144 for kv in qsl: 145 print kv 146 try: 147 index = int(kv[0]) 148 val = tornado.escape.url_unescape(kv[1]) 149 print "XXX", index, val 150 args[index] = val 151 except Exception,e: 152 print e 153 154 self.add_header('Cache-Control', 'no-cache, no-store, must-revalidate') 155 self.add_header('Pragma', 'no-cache') 156 self.add_header('Expires', '0') 157 self.add_header('X-Content-Type-Options', 'nosniff') 158 self.add_header('X-XSS-Protection', '0') 159 160 self.write(ldr.load('xsstest.html').generate(args=args)) 161 162class DaysSinceHandler(tornado.web.RequestHandler): 163 def get(self): 164 lastevasion = datetime.date(2013, 9, 12) 165 today = datetime.date.today() 166 daynum = (today - lastevasion).days 167 if daynum < 10: 168 days = "00" + str(daynum) 169 elif daynum < 100: 170 days = "0" + str(daynum) 171 else: 172 days = str(daynum) 173 174 self.render( 175 "days-since-last-bypass.html", 176 title='libinjection: Days Since Last Bypass', 177 days=days, 178 ssl_protocol=self.request.headers.get('X-SSL-Protocol', ''), 179 ssl_cipher=self.request.headers.get('X-SSL-Cipher', '') 180 ) 181 182class NullHandler(tornado.web.RequestHandler): 183 def get(self): 184 arg = self.request.arguments.get('type', []) 185 if len(arg) > 0 and arg[0] == 'tokens': 186 return self.get_tokens() 187 else: 188 return self.get_fingerprints() 189 190 def get_tokens(self): 191 ids = self.request.arguments.get('id', []) 192 193 if len(ids) == 1: 194 formvalue = ids[0] 195 else: 196 formvalue = '' 197 198 val = urllib.unquote(formvalue) 199 parsed = [] 200 parsed.append(alltokens(val, libinjection.FLAG_QUOTE_NONE | libinjection.FLAG_SQL_ANSI)) 201 parsed.append(alltokens(val, libinjection.FLAG_QUOTE_NONE | libinjection.FLAG_SQL_MYSQL)) 202 parsed.append(alltokens(val, libinjection.FLAG_QUOTE_SINGLE | libinjection.FLAG_SQL_ANSI)) 203 parsed.append(alltokens(val, libinjection.FLAG_QUOTE_SINGLE | libinjection.FLAG_SQL_MYSQL)) 204 parsed.append(alltokens(val, libinjection.FLAG_QUOTE_DOUBLE | libinjection.FLAG_SQL_MYSQL)) 205 206 self.add_header('Cache-Control', 'no-cache, no-store, must-revalidate') 207 self.add_header('Pragma', 'no-cache') 208 self.add_header('Expires', '0') 209 self.add_header('X-Content-Type-Options', 'nosniff') 210 self.add_header('X-XSS-Protection', '0') 211 212 self.render("tokens.html", 213 title='libjection sqli token parsing diagnostics', 214 version = libinjection.version(), 215 parsed=parsed, 216 formvalue=val, 217 ssl_protocol=self.request.headers.get('X-SSL-Protocol', ''), 218 ssl_cipher=self.request.headers.get('X-SSL-Cipher', '') 219 ) 220 221 def get_fingerprints(self): 222 #unquote = urllib.unquote 223 #detectsqli = libinjection.detectsqli 224 225 ids = self.request.arguments.get('id', []) 226 if len(ids) == 1: 227 formvalue = ids[0] 228 else: 229 formvalue = '' 230 231 args = [] 232 extra = {} 233 qssqli = False 234 235 sqlstate = libinjection.sqli_state() 236 237 allfp = {} 238 for name,values in self.request.arguments.iteritems(): 239 if name == 'type': 240 continue 241 242 fps = [] 243 244 val = values[0] 245 val = urllib.unquote(val) 246 if len(val) == 0: 247 continue 248 libinjection.sqli_init(sqlstate, val, 0) 249 pat = libinjection.sqli_fingerprint(sqlstate, libinjection.FLAG_QUOTE_NONE | libinjection.FLAG_SQL_ANSI) 250 issqli = bool(libinjection.sqli_blacklist(sqlstate) and libinjection.sqli_not_whitelist(sqlstate)) 251 fps.append(['unquoted', 'ansi', issqli, pat]) 252 253 pat = libinjection.sqli_fingerprint(sqlstate, libinjection.FLAG_QUOTE_NONE | libinjection.FLAG_SQL_MYSQL) 254 issqli = bool(libinjection.sqli_blacklist(sqlstate) and libinjection.sqli_not_whitelist(sqlstate)) 255 fps.append(['unquoted', 'mysql', issqli, pat]) 256 257 pat = libinjection.sqli_fingerprint(sqlstate, libinjection.FLAG_QUOTE_SINGLE | libinjection.FLAG_SQL_ANSI) 258 issqli = bool(libinjection.sqli_blacklist(sqlstate) and libinjection.sqli_not_whitelist(sqlstate)) 259 fps.append(['single', 'ansi', issqli, pat]) 260 261 pat = libinjection.sqli_fingerprint(sqlstate, libinjection.FLAG_QUOTE_SINGLE | libinjection.FLAG_SQL_MYSQL) 262 issqli = bool(libinjection.sqli_blacklist(sqlstate) and libinjection.sqli_not_whitelist(sqlstate)) 263 fps.append(['single', 'mysql', issqli, pat]) 264 265 pat = libinjection.sqli_fingerprint(sqlstate, libinjection.FLAG_QUOTE_DOUBLE | libinjection.FLAG_SQL_MYSQL) 266 issqli = bool(libinjection.sqli_blacklist(sqlstate) and libinjection.sqli_not_whitelist(sqlstate)) 267 fps.append(['double', 'mysql', issqli, pat]) 268 269 allfp[name] = { 270 'value': breakify(breakapart(val)), 271 'fingerprints': fps 272 } 273 274 for name,values in self.request.arguments.iteritems(): 275 if name == 'type': 276 continue 277 for val in values: 278 # do it one more time include cut-n-paste was already url-encoded 279 val = urllib.unquote(val) 280 if len(val) == 0: 281 continue 282 283 # swig returns 1/0, convert to True False 284 libinjection.sqli_init(sqlstate, val, 0) 285 issqli = bool(libinjection.is_sqli(sqlstate)) 286 287 # True if any issqli values are true 288 qssqli = qssqli or issqli 289 val = breakapart(val) 290 291 pat = sqlstate.fingerprint 292 if not issqli: 293 pat = 'see below' 294 args.append([name, val, issqli, pat]) 295 296 self.add_header('Cache-Control', 'no-cache, no-store, must-revalidate') 297 self.add_header('Pragma', 'no-cache') 298 self.add_header('Expires', '0') 299 self.add_header('X-Content-Type-Options', 'nosniff') 300 self.add_header('X-XSS-Protection', '0') 301 302 self.render("form.html", 303 title='libjection sqli diagnostic', 304 version = libinjection.version(), 305 is_sqli=qssqli, 306 args=args, 307 allfp = allfp, 308 formvalue=formvalue, 309 ssl_protocol=self.request.headers.get('X-SSL-Protocol', ''), 310 ssl_cipher=self.request.headers.get('X-SSL-Cipher', '') 311 ) 312 313import os 314settings = { 315 "static_path": os.path.join(os.path.dirname(__file__), "static"), 316 "template_path": os.path.join(os.path.dirname(__file__), "."), 317 "xsrf_cookies": False, 318 "gzip": False 319} 320 321application = tornado.web.Application([ 322 (r"/diagnostics", NullHandler), 323 (r'/xsstest', XssTestHandler), 324 (r'/bootstrap/(.*)', tornado.web.StaticFileHandler, {'path': '/opt/bootstrap' }), 325 (r'/jquery/(.*)', tornado.web.StaticFileHandler, {'path': '/opt/jquery' }), 326 (r'/robots.txt', tornado.web.StaticFileHandler, {'path': os.path.join(os.path.dirname(__file__), "static")}), 327 (r'/favicon.ico', tornado.web.StaticFileHandler, {'path': os.path.join(os.path.dirname(__file__), "static")}), 328 (r"/([a-z-]*)", PageHandler) 329 ], **settings) 330 331 332if __name__ == "__main__": 333 tornado.options.parse_command_line() 334 335 logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(process)d %(message)s") 336 337 application.listen(8888) 338 tornado.ioloop.IOLoop.instance().start() 339 340