#!/usr/bin/env python #################################################################################### # # sqlinject-finder.py # # Author: XGrats # Date : 12/02/2010 # Description: Simple python script that parses through a pcap and looks at the # GET and POST request data for suspicious and possible SQL injects. # #################################################################################### import dpkt, re, urllib, sys, getopt tab = False #removes inline comments that can sometimes be used for obfuscating the sql def removeComments(val): while True: index = val.find("/*") index2 = val.find("*/") if index != -1 and index2 != -1: #looks like there is some type of SQL obfuscation, let's remove the comments remove = val[index:index2+2] val = val.replace(remove, "") else: break return val #checks for common sql injection tactics using all the variables from post or get data def analyzeRequest(vals, sIP, page, frameno): var = vals[0] #the variable, i.e. in id=1, the var is id val = vals[1] #the value, i.e. in id=1, the val is 1 val = val.decode('ascii') #not sure if this is really doing anything, but we need to deal with non ascii characters for analysis val = urllib.unquote(val) #removes url encodings like %20 for space, etc val = val.replace("+", " ") #sometimes in urls, instead of a space you can have a + . So, we want to remove those for analysis #print val display = [False, sIP, page, var, val] ##### Look for obfuscation techniques ###### index = val.find("/*") if index != -1: display[0] = True display.append("Might be attempting to obfuscate a SQL statement with a comment") val = removeComments(val) ##### Look for commenting out the end of a MSSQL statement ###### index = val.rfind("--") if index != -1: display[0] = True display.append("Might be attempting to end a SQL statement by commenting out the remaining statement") ##### Look for commenting out the end of a MySQL statement ##### index = val.rfind("#") if index != -1: display[0] = True display.append("Might be attempting to end a SQL statement by commenting out the remaining statement") ##### Look for common SQL syntax in the values of a param ##### sqlvals = ("cast(", "declare ", "select ", "union ", "varchar", "set(", "create ", " or ", " NULL,", " concat(") for sql in sqlvals: index = val.lower().find(sql) if index != -1: display[0] = True display.append("Possible use of SQL syntax in variable") break if display[0] == True: if tab: line = str(display[1]) + "\t" + str(display[2]) + "\t" + str(display[3]) + "=" + str(display[4]) + "\t" + str(frameno) for i in range(len(display)-5): line = line + "\t" + str(display[i+5]) print line else: print "Source : " + str(display[1]) print "Page : " + str(display[2]) print "Value : " + str(display[3]) + "=" + str(display[4]) print "Frame : " + str(frameno) for i in range(len(display)-5): print "Reason : " + str(display[i+5]) print "" def octetIP(sIP): ip = "" for s in sIP: ip = ip + str(ord(s)) + "." return ip[:-1] #reads the pcap file and parses out get and post requests for analysis def parsepcap(filename): try: f = open(filename, 'rb') except: print "Error reading file. Please make sure the file exists" sys.exit() try: pcap = dpkt.pcap.Reader(f) except: print "Error reading file. Please make sure the file is a valid pcap file." sys.exit() sIP="" page="" frameno = 1 for ts, buf in pcap: eth = dpkt.ethernet.Ethernet(buf) ip = eth.data #make sure we are dealing with ip (2048) and tcp (proto=6) if eth.type ==2048 and ip.p == 6: tcp = ip.data #assuming http is running on port 80 if tcp.dport == 80 and len(tcp.data) > 0: index = 1 getvals = "" try: http = dpkt.http.Request(tcp.data) url = http.uri #deal with post data if http.method == "POST": getvals=http.body index = url.find("?") if index != -1: page = url[:index] else: page = url #deal with GET data elif http.method == "GET": index = url.rfind("?") if index != -1: getvals = url[index+1:] page = url[:index] except: data = tcp.data index = str(data).find("POST") if index == 0: url = str(data).split(" ") page = url[1] #POST is usually always the second value in the POST index = str(data).count("\n") #need to look into this method a little more, basically, we want to get POST data out of other streams if index == 0: index = str(data).find("=") if index != -1: getvals = str(data) #split up each variable and its cooresponding value if getvals != "": getvals = getvals.split("&") for val in getvals: i = val.find("=") val = (val[:i], val[i+1:]) sIP = octetIP(ip.src) analyzeRequest(val, sIP, page, frameno) frameno += 1 f.close() #usage stuff def usage(): print "" print "This tool parses through a pcap file and looks for potential SQL injection attempts." print "" print "usage: sqlinject-finder.py -f filename [-t]" print "Options and arguments (and corresponding environment variables):" print "-f, --filename : valid pcap file" print "-t, --tab : prints output in tab delimited format" print "-h, --help : shows this screen" print "" print "Example: #python sqlinject-finder.py -f capture.pcap" print " #python sqlinject-finder.py -f capture.pcap -t > capture.tsv" print "" def main(): try: opts, args = getopt.getopt(sys.argv[1:], "f:th", ["filename=", "tab", "help"]) except getopt.GetoptError, err: print str(err) usage() sys.exit(2) filename = "" for o, a in opts: if o in ("-f", "--filename"): filename = a elif o in ("-t", "--tab"): global tab tab = True elif o in ("-h", "--help"): usage() sys.exit() else: usage() sys.exit() if (filename == ""): print "please specify a filename" sys.exit() if tab: print "Source\tPage\tValue\tFrame\tReason(s)" parsepcap(filename) if __name__ == "__main__": main()