13 Years of Service
24%
Code:
>
#!/usr/bin/env python
####################################################################################
#
# sqlinject-finder.py
#
# Author: tdean87
# 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()