# Pantera - Web Pen-Test Proxy # # FILENAME : panterautils.py # CODER : Simon Roses Femerling # DATE : 9/23/2004 # LAST UPDATE : 9/04/2006 # ABSTRACT : Python Web Pen-Test Proxy :) # Pantera utils. Heavely base on SpikeProxy. # # - Roses Labs Innovations (RL+I) # Roses Labs # http://www.roseslabs.com # # Copyright (c) 2003-2006 Roses Labs. # # You may not distribute, transmit, repost this software for commercial # purposes without Roses Labs written permission. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, publish, # distribute the Software, and to permit persons to whom the Software # is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # ''' @author: Simon Roses Femerling @license: GNU General Public License 2.0 or later @contact: pantera.proxy@gmail.com @organization: OWASP / Roses Labs ''' # Pantera Utils Imports import os import random import string import cPickle from htmllib import HTMLParser from formatter import DumbWriter, AbstractFormatter from cStringIO import StringIO import re from random import random, choice, uniform import codecs import pantera import panteraLib ############################################################################################# # Defines ############################################################################################# YES = 1 NO = 0 hd = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',] ############################################################################################# # Our Functions ############################################################################################# ############################################################################################# # FUNC : def readuntil # PARAMS : mysocket, mybreakpoint # RETURN : Return data read or "" if none # ABSTRACT : Read until... def readuntil(mysocket,mybreakpoint): ''' Read data until condition @type mysocket: socket @param mysocket: Socket descriptor. @type mybreakpoint: string @param mybreakpoint: string end tag. @return: Return string. ''' data="" length=-len(mybreakpoint) #print "length=%d"%length while data[length:]!=mybreakpoint: newdata=mysocket.recv(1) data+=newdata #print "data=%s"%(data[length:]) return data # EOF: def reauntil ############################################################################################# # FUNC : def urlnormalize # PARAMS : url # RETURN : string # ABSTRACT : Normalize URL def urlnormalize(url): ''' Normalize a URL /cow/../../../bob/bob2.php -> /bob/bob2.php @type url: string @param url: String to mormalize. @return: Return string. ''' #for win32 users f=url.replace("\\","/") if f[-1]=="/": tailslash=1 else: tailslash=0 dot=f.split("/") #while "." in dot: for i in dot: if i == ".": #print dot #dot=dot.remove(".") _=dot.pop(dot.index(".")) while "" in dot: dot.remove("") while ".." in dot: firstdotdot=dot.index("..") #go one directory up if firstdotdot==0: dot.remove(dot[0]) continue #get rid of parent directory dot.remove(dot[firstdotdot-1]) #do this again to get rid of the .. dot.remove(dot[firstdotdot-1]) fin="/".join(dot)+"/"*tailslash if fin=="": fin="/" if fin[0]!="/": fin="/"+fin return fin # EOF: def urlnormalize ############################################################################################# # FUNC : def dmkdir # PARAMS : newdir # RETURN : ... # ABSTRACT : stolen from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/82465 def dmkdir(newdir): ''' works the way a good mkdir should :) - already exists, silently complete - regular file in the way, raise an exception - parent directory(ies) does not exist, make them as well @type newdir: string @param newdir: Create directory with the give name. ''' if os.path.isdir(newdir): pass elif os.path.isfile(newdir): raise OSError("a file with the same name as the desired " \ "dir, '%s', already exists." % newdir) else: head, tail = os.path.split(newdir) if head and not os.path.isdir(head): dmkdir(head) #print "_mkdir %s" % repr(newdir) if tail: if os.path.isdir(newdir): pass try: os.mkdir(newdir) except OSError: pass # EOF: def dmkdir ############################################################################################# # FUNC : def joinallspaces # PARAMS : input # RETURN : string # ABSTRACT : Joins all the spaces in a string def joinallspaces(input): ''' Joins all the spaces in a string. @type input: string @param input: String to join spaces. @return: Return string. ''' inputold="" inputnew=input[:] while inputold!=inputnew: inputold=inputnew[:] inputnew=inputnew.replace(" "," ") return inputnew # EOF: def joinallspaces ############################################################################################# # FUNC : def getrandomnumber # PARAMS : ... # RETURN : Number # ABSTRACT : Get random number def getrandomnumber(): ''' Get a random number. @return: Return number. ''' return random.randrange(1,100000,1) # EOF: def getrandomnumber ############################################################################################# # FUNC : def pathjoin # PARAMS : *paths # RETURN : string # ABSTRACT : Join path def pathjoin(*paths): ''' Join path. @type *paths: list of strings @param *paths: List of string to join. @return: Return string. ''' temp="" for path in paths: #print "Pathjoin "+path if path!="": if path[0]=="/" or path[0]=="\\": #we are windoze compliant! path=path[1:] temp=os.path.join(temp,path) #if the first was an absolute path... if paths[0][0]=="/": temp="/"+temp #add that back return temp # EOF: def pathjoin ############################################################################################# # FUNC : def pathsplit # PARAMS : path # RETURN : list # ABSTRACT : Split path def pathsplit(path): ''' Split path. @type path: string @param path: String to split. @return: Return list. ''' temp=path last="tempval" retList=[] while last!="": temp,last=os.path.split(temp) if last!="": retList=[last]+retList return retList # EOF: pathsplit #following inits are for prettyprint norm = string.maketrans('', '') #builds list of all characters non_alnum = string.translate(norm, norm, string.letters+string.digits) trans_nontext=string.maketrans(non_alnum,'#'*len(non_alnum)) ############################################################################################# # FUNC : def prettyprint # PARAMS : data # RETURN : string # ABSTRACT : Pretty print def prettyprint(data): ''' Pretty Print. @type data: string @param data: String to print. @return: Return string. ''' cleaned=string.translate(data,trans_nontext) return cleaned # EOF: def prettyprint ############################################################################################# # FUNC : def getURLfromFile # PARAMS : file # RETURN : string # ABSTRACT : Open a requestandresponse object in a file and # obtains the url from the header def getURLfromFile(file): ''' Open a requestandresponse object in a file and obtains the url from the header. @type file: path @param file: Path of the file to open. @return: Return string. ''' #load our request and response object infile=open(file,"rb") obj=cPickle.load(infile) infile.close() url=obj.clientheader.URL return url # EOF: def getURLfromFile ############################################################################################# # FUNC : def getDirsFromURL # PARAMS : url # RETURN : list # ABSTRACT : Takes a url like /bob/bob2/bob3/asdf.cgi # and returns [bob,bob2,bob3,asdf.cgi] def getDirsFromURL(url): ''' Takes a url like /bob/bob2/bob3/asdf.cgi and returns [bob,bob2,bob3,asdf.cgi] @type url: string @param url: String to split @return: Return list. ''' dirList=url.split("/") #check for a file at the last one if dirList[-1].count(".")>0: dirList=dirList[:-1] #now combine them up start="/" realDirList=[] for dir in dirList: start+= "%s/" % dir start=start.replace("_directory_","") start=start.replace("///","/") start=start.replace("//","/") realDirList.append(start) return realDirList # EOF: def getDirsFromURL ############################################################################################# # FUNC : def constructRequest # PARAMS : myheader, mybody # RETURN : string # ABSTRACT : Constructs a request given a header and optionally a body def constructRequest(myheader,mybody=None): ''' Constructs a request given a header and optionally a body. @type myheader: class @param myheader: HTTP header class @type myheader: class @param myheader: HTTP body class @return: Return string. ''' #for null value if (mybody==None): mybody = pantera.HTTPBody() #debug if 0: return "GET / HTTP/1.1\r\nHost: www.roseslabs.com\r\nContent-Length: 0\r\n\r\n" request="%s %s%s" % (myheader.verb,myheader.getProxyHeader(),myheader.URL) #if we have arguments if myheader.useRawArguments: if len(myheader.allURLargs) > 0: request+= "?%s" % myheader.allURLargs else: if len(myheader.URLargsDict) > 0: request+="?" request+=joinargs(myheader.URLargsDict,orderlist=myheader.orderlist) request+=" %s\r\n" % myheader.version #ok, the first line is done! #do the rest of the headers that need order #I dunno if any except Host really need ordering, but I do it #to erase any chance of lame bugs later on #plus, python makes it quite easy needOrdered=["Host","User-Agent","Accept","Accept-Language","Accept-Encoding","Accept-Charset","Keep-Alive","Connection","Pragma","Cache-Control"] for avalue in needOrdered: request+=myheader.grabHeader(avalue) #now work on the header pairs we haven't already done for akey in myheader.headerValuesDict.keys(): if akey not in needOrdered: request+=myheader.grabHeader(akey) #ok, headers are all done except for content-length #Content-Length: 0 should always be valid, but it's #not working for some reason on get requests! if mybody.mysize!=0 or myheader.verb!="GET": r = len(mybody.data) if not myheader.surpressContentLength() or r != 0: request+="Content-Length: %s\r\n" % str(len(mybody.data)) #ok, all headers are done, finish with blank line request+="\r\n" #ok, now add body request+="".join(mybody.data) #done! return request # EOF: def constructRequest ############################################################################################# # FUNC : def joinargs # PARAMS : argdict, orderlist # RETURN : string # ABSTRACT : Takes in a dict, returls A=B&C=D,etc def joinargs(argdict,orderlist=[]): ''' Takes in a dict, returls A=B&C=D,etc @type argdict: dict @param argdict: Dict. @type orderlist: list @param orderlist: Follow order. @return: Return string. ''' first=1 result="" donelist=[] for akey in orderlist: donelist.append(akey) if not first: result+="&" first=0 result+= "%s=%s" % (akey,argdict[akey]) for akey in argdict.keys(): if akey in donelist: continue if not first: result+="&" first=0 result+= "%s=%s" % (akey,argdict[akey]) return result # EOF: def joinargs( ############################################################################################# # FUNC : def splitargs # PARAMS : argstring, orderlist # RETURN : dict on succes or none on error # ABSTRACT : Returns a dictionary of a string split like a normal HTTP argument list def splitargs(argstring,orderlist=[]): ''' Returns a dictionary of a string split like a normal HTTP argument list. @type argstring: string @param argstring: String argument. @type orderlist = list @param orderlist: Follow order. @return: Return string. ''' resultDict={} templist=argstring.split("&") for pair in templist: if pair!="": templist2=pair.split("=") if len(templist2)<2: #print "Failed to parse the URL arguments because of #invalid number of equal signs in one argument in: #\""+pair+"\" len="+str(len(templist2)) return None else: #add this argument to the Dict orderlist.append(templist2[0]) resultDict[templist2[0]]="=".join(templist2[1:]) return resultDict # EOF: def splitargs ############################################################################################# # FUNC : def splitstring # PARAMS : astring # RETURN : list # ABSTRACT : Turns a string into a one character list def splitstring(astring): ''' Turns a string into a one character list. @type astring = list @param astring: String to convert into a list. @return: Return string. ''' alist = [ch for ch in astring] return alist # EOF: def splitstring ############################################################################################# # FUNC : def printFormEntry # PARAMS : text, name, value # RETURN : string # ABSTRACT : Print a form entry def printFormEntry(text, name,value, inside_table=0, size=15): ''' Print a form entry. @type text: string @param text: String to display. @type name: string @param name: Name of the input tag. @type inside_table: int @param inside_table: If 0 input tag inside html table, else if 1 input is not in html table. @type size: int @param size: Size of the input tag. @return: Return string. ''' if inside_table==1: result= "
%s" % (text) result+= "\r\n" result+= "
\r\n" return result # EOF: def printFormSelectMenu ############################################################################################# # FUNC : def printFormSelectMenuID # PARAMS : string, string, dict, value # RETURN : string # ABSTRACT : print select menu with ID to use in a form def printFormSelectMenuID(text, name, dict, inside_table=0): ''' Print select menu with ID to use in a form. @type text: string @param text: String to display. @type name: string @param name: Name of the input tag. @type dict: dict @param dict: Dict to use in select tag. @type inside_table: int @param inside_table: If 0 input tag inside html table, else if 1 input is not in html table. @return: Return string. ''' result = "" if inside_table==1: result+= "%s" % text result+= "\r\n" result+= "
\r\n" return result # EOF: def printFormSelectMenuID ############################################################################################# # FUNC : def headerdictcmp # PARAMS : dict1, dict2 # RETURN : Return 1 if they are the same, 0 if not # ABSTRACT : Filters out Date: and whatnot # this is basically to detect if we get different Cookies # this is lame, but it should work ok def headerdictcmp(dict1,dict2): ''' Filters out Date: and whatnot this is basically to detect if we get different Cookies this is lame, but it should work ok @type dict1: dict @param dict1: First dict. @type dict2: dict @param dict2: Second dict. @return: Return 0 if they are different, else 1. ''' for akey in dict1.keys(): if akey=="Date": continue if not dict2.has_key(akey): return 0 for bkey in dict1[akey]: if not bkey in dict2[akey]: return 0 return 1 # EOF: def headerdictcmp ############################################################################################# # FUNC : def genhash # PARAMS : clientheader, clientbody, serverheader, serverbody # RETURN : string # ABSTRACT : Hashes a requestandresponse so we can do matches quickly # returns a string as the hash def genhash(clientheader,clientbody,serverheader,serverbody): ''' Hashes a requestandresponse so we can do matches quickly returns a string as the hash @type clientheader: class @param clientheader: Contains a HTTP header class. @type clientbody: class @param clientbody: Contains a HTTP body class. @type serverheader: class @param serverheader: Contains a HTTP body class. @type serverbody: class @param serverbody: Contains a HTTP body class. @return: Return string hash. ''' #print "in genhash" CH=clientheader.genhash() CB=clientbody.genhash() SH=serverheader.genhash() SB=serverbody.genhash() return CH+CB+SH+SB # EOF: def genhash ############################################################################################# # FUNC : def hashstring # PARAMS : astring # RETURN : string # ABSTRACT : Hashes a string to a number, then returns that number as a string def hashstring(astring): ''' Hashes a string to a number, then returns that number as a string. @type astring: string @param astring: String to hash. @return: Return string. ''' i=0 #print "in hashstring" if astring=="": return "" hashnum=0 l=len(astring) while i