From 456909432c372543fee454b8c378430b863bab4f Mon Sep 17 00:00:00 2001 From: Xavier Mendez Date: Wed, 22 Oct 2014 23:27:53 +0100 Subject: [PATCH] Importing old wfuzz-1.4d from google code --- README | 38 ++++- reqresp.py | 442 +++++++++++++++++++++++++++++------------------------ wfuzz.py | 375 ++++++++++++++++++++++++++++++--------------- 3 files changed, 525 insertions(+), 330 deletions(-) diff --git a/README b/README index 5acbdbb2..fc01071b 100755 --- a/README +++ b/README @@ -1,5 +1,5 @@ ************************************* -* Wfuzz 1.4c - The web bruteforcer * +* Wfuzz 1.4d - The web bruteforcer * * Coded by: * * Christian Martorella * * - cmartorella@edge-security.com * @@ -33,30 +33,41 @@ The tool is based on dictionaries or ranges, then you choose where you want to b Examples: - - wfuzz.py -c -z file -f wordlists/commons.txt --hc 404 --html http://www.mysite.com/FUZZ 2> results.html + - wfuzz.py -c -z file -f wordlists/commons.txt --hc 404 --html http://www.mysite.com/FUZZ 2> results.html This will bruteforce the site http://www.mysyte.com/FUZZ in search of resources i (directories, scripts, files,etc), it will hide from the output the return code 404 (for easy reading results), it will use the dictionary commons.txt for the bruteforce , and also will output the results to the results.html file (with a cool format to work). - - - wfuzz.py -c -z range -r 1-100 --hc 404 http://www.mysite.com/list.asp?id=FUZZ + - wfuzz.py -c -z range -r 1-100 --hc 404 http://www.mysite.com/list.asp?id=FUZZ In this example instead of using a file as dictionary, it will use a range from 1-100, and will bruteforce the parameter "id". - - wfuzz.py -c -z file -f wordlists/commons.txt --hc 404 --html -d "id=1&catalogue=FUZZ" + - wfuzz.py -c -z file -f wordlists/commons.txt --hc 404 --html -d "id=1&catalogue=FUZZ" http://www.mysite.com/check.asp 2> results.html Here you can see the use of POST data, with the option "-d". - wfuzz.py -c -z file -f wordlists/commons.txt --hc 404 -R 2 http://www.mysite.com/FUZZ Example of path discovery, using a recursive level of 2 paths. + - wfuzz.py -z file -f wordlists/http_methods.txt -X http://testphp.vulnweb.com/ + HTTP method scanning example + + - wfuzz.py -z file -f wordlists/http_methods.txt,wordlists/commons.txt -X http://testphp.vulnweb.com/FUZ2Z/ + HTTP method scanning example in several paths + + - wfuzz.py -c -z file -f wordlists/methods.txt --hc 404 -v --follow http://www.mysite.com/FUZZ + Bruteforce following HTTP redirects + + - wfuzz.py -c -z file -f wordlists/commons.txt --hc 404 -I http://www.mysite.com/FUZZ + Bruteforce using HEAD HTTP method + Platforms: ---------- wfuzz was tested on Linux, Os X and Windows. -On windows the colored output, it doesn't work, we are working towards fixing this problem. +On windows the colored output doesn't work, we are working towards fixing this problem. Dependencies: @@ -70,11 +81,24 @@ Thanks: Shouts goes to: Trompeti an all the S21sec Team. (www.s21sec.com) -Special thanks to DarkRaver for the tool Dirb, part of wfuzz is based on the functionallity of dirb. (www.open-labs.org) and most of the wordlist are from his tool. +Special thanks to DarkRaver for the tool Dirb, part of wfuzz is based on the functionallity of dirb. (www.open-labs.org) and most of the wordlist are from his tool. Andres Andreu, all Injection payloads are taken from wsFuzzer (www.neurofuzz.com) Stay tunned for the GUI it rocks.. +Changelog 1.4d: +============== +-Using _ in encoders names +-Added HEAD method scanning +-Added magictree support +-Fuzzing in HTTP methods +-Hide responses by regex +-Bash auto completion script (modify and then copy wfuzz_bash_completion into /etc/bash_completion.d) +-Verbose output including server header and redirect location +-Added follow HTTP redirects option (this functionality was already provided by reqresp) +-Fixed HTML output, thanks to Christophe De La Fuente +-Fixed terminal colour, thanks to opensource@till.name + Changelog 1.4c: ============== -Fixed Headers parsing, thanks to Osama diff --git a/reqresp.py b/reqresp.py index dded29ac..e966079f 100644 --- a/reqresp.py +++ b/reqresp.py @@ -10,7 +10,9 @@ import string import re import threading +import copy from time import localtime, strftime +from datetime import date from xml.dom.minidom import Document @@ -23,6 +25,127 @@ Semaphore_Mutex=threading.BoundedSemaphore(value=mutex) REQLOG=False +class Variable: + def __init__(self,name,value="",extraInfo=""): + self.name=name + self.value=value + self.initValue=value + self.extraInfo=extraInfo + + def restore(self): + self.value=self.initValue + + def change(self,newval): + self.initValue=self.value=newval + + def update(self,val): + self.value=val + + def append(self,val): + self.value+=val + + def __str__(self): + return "[ %s : %s ]" % (self.name,self.value) + +class VariablesSet: + def __init__(self): + self.variables=[] + self.boundary=None + + def names(self): + dicc=[] + for i in self.variables: + dicc.append(i.name) + + return dicc + + def existsVar(self,name): + return name in self.names() + + def addVariable(self,name,value="",extraInfo=""): + self.variables.append(Variable(name,value,extraInfo)) + + + def getVariable(self,name): + dicc=[] + for i in self.variables: + if i.name==name: + dicc.append(i) + + if len(dicc)>1: + raise Exception, "Variable exists more than one time!!! :D" % (name) + + if not dicc: + var=Variable(name) + self.variables.append(var) + return var + + return dicc[0] + + + def urlEncoded(self): + return "&".join(["=".join([i.name,i.value]) for i in self.variables]) + + def parseUrlEncoded(self,cad): + dicc=[] + + for i in cad.split("&"): + if i: + list=i.split("=",1) + if len (list)==1: + dicc.append(Variable(list[0],"")) + elif len (list)==2: + dicc.append(Variable(list[0],list[1])) + + self.variables=dicc + + def multipartEncoded(self): + if not self.boundary: + self.boundary="---------------------------D33PB1T0R3QR3SP0B0UND4RY2203" + pd="" + pos=0 + for i in self.variables: + pd+="--"+self.boundary+"\r\n" + pd+="%s\r\n\r\n%s\r\n" % ("\r\n".join(i.extraInfo),i.value) + pd+="--"+self.boundary+"--\r\n" + return pd + + def parseMultipart(self,cad,boundary): + self.boundary=boundary + dicc=[] + tp=TextParser() + tp.setSource("string",cad) + + while True: + headers=[] + if not tp.readUntil("name=\"([^\"]+)\""): + break + var=tp[0][0] + headers.append(tp.lastFull_line.strip()) + while True: + tp.readLine() + if tp.search("^([^:]+): (.*)$"): + headers.append(tp.lastFull_line.strip()) + else: + break + + value="" + while True: + tp.readLine() + if not tp.search(boundary): + value+=tp.lastFull_line + else: + break + + if value[-2:]=="\r\n": + value=value[:-2] + + + dicc.append(Variable(var,value,headers)) + + self.variables=dicc + + class Request: @@ -38,17 +161,16 @@ def __init__ (self): # self.pathWithVariables # /index.php?a=b&c=d # self.urlWithoutVariables=None # http://www.google.es/index.php # self.completeUrl="" # http://www.google.es/index.php?a=b + # self.finalUrl="" # Url despues de hacer el FollowLocation # self.postdata="" # Datos por POST, toto el string ################ self.ContentType="application/x-www-form-urlencoded" # None es normal encoding - self.boundary="---------------------------D33PB1T0R3QR3SP0B0UND4RY2203" self.multiPOSThead={} - self.__variablesGET={} - self.__GETorder=[] + self.__variablesGET=VariablesSet() + self.__variablesPOST=VariablesSet() - self.__variablesPOST={} self.__headers={} # diccionario, por ejemplo headers["Cookie"] self.response=None # Apunta a la response que produce dicha request @@ -70,8 +192,10 @@ def __init__ (self): self.__proxy=None self.__timeout=None self.__totaltimeout=None + self.__finalurl="" self.followLocation=False + self.follow_url="" def __str__(self): str="[ URL: %s" % (self.completeUrl) @@ -82,6 +206,9 @@ def __str__(self): str+=" ]" return str + def getHost(self): + return self.__host + def getXML(self,obj): r=obj.createElement("request") r.setAttribute("method",self.method) @@ -98,87 +225,56 @@ def getXML(self,obj): r.appendChild(ck) return r - + def __getattr__ (self,name): if name=="urlWithoutVariables": return urlunparse((self.schema,self.__host,self.__path,'','','')) elif name=="pathWithVariables": - return urlunparse(('','',self.__path,'',self.getVARIABLESstring(self.__variablesGET,self.__GETorder),'')) + return urlunparse(('','',self.__path,'',self.__variablesGET.urlEncoded(),'')) elif name=="completeUrl": - return urlunparse((self.schema,self.__host,self.__path,self.__params,self.getVARIABLESstring(self.__variablesGET,self.__GETorder),'')) + return urlunparse((self.schema,self.__host,self.__path,self.__params,self.__variablesGET.urlEncoded(),'')) + elif name=="finalUrl": + if self.__finalurl: + return self.__finalurl + return self.completeUrl elif name=="urlWithoutPath": return "%s://%s" % (self.schema,self.__headers["Host"]) elif name=="path": return self.__path elif name=="postdata": if self.ContentType=="application/x-www-form-urlencoded": - return self.getVARIABLESstring(self.__variablesPOST,None) + return self.__variablesPOST.urlEncoded() elif self.ContentType=="multipart/form-data": - pd="" - for i,j in self.__variablesPOST.items(): - pd+="--"+self.boundary+"\r\n" - pd+="%s\r\n\r\n%s\r\n" % ("\r\n".join(self.multiPOSThead[i]),j) - pd+="--"+self.boundary+"--\r\n" - return pd + return self.__variablesPOST.multipartEncoded() else: return self.__uknPostData else: raise AttributeError - def getVARIABLESstring(self,vars,sort): - str=[] - - if not sort: - for i,j in vars.items(): - str.append(i+"="+j) - else: - for i in sort: - str.append(i+"="+vars[i]) - - return "&".join(str) - - - def readUrlEncodedVariables(self,str): - dicc=[] - - for i in str.split("&"): - if i: - list=i.split("=",1) - if len (list)==1: - dicc.append([list[0],""]) - elif len (list)==2: - dicc.append([list[0],list[1]]) - - - - return dicc - def setUrl (self, urltmp): - - self.__variablesGET={} - + self.__variablesGET=VariablesSet() self.schema,self.__host,self.__path,self.__params,variables,f=urlparse(urltmp) + self.__headers["Host"]=self.__host - self.__headers["Host"]=self.__host.lstrip() - - if len(variables)>0: - dicc=self.readUrlEncodedVariables(variables) - [self.addVariableGET(i,j) for i,j in dicc] + if variables: + self.__variablesGET.parseUrlEncoded(variables) - +############### PROXY ################################## def setProxy (self,prox): self.__proxy=prox +############### FOLLOW LOCATION ######################## def setFollowLocation(self,value): self.followLocation=value - +############## TIMEOUTS ################################ def setConnTimeout (self,time): self.__timeout=time def setTotalTimeout (self,time): self.__totaltimeout=time + ############## Autenticacion ########################### def setAuth (self,method,string): self.__authMethod=method @@ -189,64 +285,53 @@ def getAuth (self): ############## TRATAMIENTO VARIABLES GET & POST ######################### - def variablesGET(self): - return self.__variablesGET.keys() + def existsGETVar(self,key): + return self.__variablesGET.existsVar(key) - def variablesPOST(self): - return self.__variablesPOST.keys() + def existPOSTVar(self): + return self.__variablesPOST.existsVar(key) - def addVariablePOST (self,key,value): - self.method="POST" - self.__variablesPOST[key]=value - self.__headers["Content-Length"]=str(len(self.postdata)) - def addVariableGET (self,key,value): - if not key in self.__variablesGET: - self.__GETorder.append(key) - self.__variablesGET[key]=value - - def getVariableGET (self,key): - if self.__variablesGET.has_key(str(key)): - return self.__variablesGET[str(key)] - else: - return None - - def getVariablePOST (self,key): - if self.__variablesPOST.has_key(str(key)): - return self.__variablesPOST[str(key)] - else: - return None - - def setPostData (self,pd): - self.__variablesPOST={} + def setVariablePOST (self,key,value): self.method="POST" - self.parsePOSTDATA(pd) + v=self.__variablesPOST.getVariable(key) + v.update(value) +# self.__headers["Content-Length"]=str(len(self.postdata)) - def addPostdata (self,str): - self.method="POST" - self.postdata=self.postdata+str - variables=str.split("&") - for i in variables: - tmp=i.split("=",1) - if len(tmp)==2: - self.addVariablePOST(tmp[0],tmp[1]) - else: - self.addVariablePOST(tmp[0],'') + def setVariableGET (self,key,value): + v=self.__variablesGET.getVariable(key) + v.update(value) + def getGETVars(self): + return self.__variablesGET.variables + def getPOSTVars(self): + return self.__variablesPOST.variables + def setPostData (self,pd,boundary=None): + self.__variablesPOST=VariablesSet() + self.method="POST" + if self.ContentType=="application/x-www-form-urlencoded": + self.__variablesPOST.parseUrlEncoded(pd) + elif self.ContentType=="multipart/form-data": + self.__variablesPOST.parseMultipart(pd,boundary) + else: + self.__uknPostData=pd ############################################################################ def addHeader (self,key,value): k=string.capwords(key,"-") - if k!="Accept-Encoding": - self.__headers[k]=value.strip() - + if k.lower() not in ["accept-encoding","content-length","if-modified-since","if-none-match"]: + self.__headers[k]=value + + def delHeader (self,key): + k=string.capwords(key,"-") + del self.__headers[k] def __getitem__ (self,key): k=string.capwords(key,"-") - if self.__headers.has_key(k): + if k in self.__headers: return self.__headers[k] else: return "" @@ -256,20 +341,39 @@ def __getHeaders (self): for i,j in self.__headers.items(): list+=["%s: %s" % (i,j)] return list - - def getHeaders (self): - list=[] - for i,j in self.__headers.items(): - list+=["%s: %s" % (i,j)] - return list + def head(self): + conn=pycurl.Curl() + conn.setopt(pycurl.SSL_VERIFYPEER,False) + conn.setopt(pycurl.SSL_VERIFYHOST,1) + conn.setopt(pycurl.URL,self.completeUrl) + + conn.setopt(pycurl.HEADER, True) # estas dos lineas son las que importan + conn.setopt(pycurl.NOBODY, True) # para hacer un pedido HEAD + conn.setopt(pycurl.WRITEFUNCTION, self.header_callback) + conn.perform() + + rp=Response() + rp.parseResponse(self.__performHead) + self.response=rp + + def createPath(self,newpath): + '''Creates new url from a location header || Hecho para el followLocation=true''' + if "http" in newpath[:4].lower(): + return newpath + + parts=urlparse(self.completeUrl) + if "/" != newpath[0]: + newpath="/".join(parts[2].split("/")[:-1])+"/"+newpath + + return urlunparse([parts[0],parts[1],newpath,'','','']) def perform(self): global REQLOG if REQLOG: Semaphore_Mutex.acquire() - f=open("/tmp/REQLOG","a") + f=open("/tmp/REQLOG-%d-%d" % (date.today().day,date.today().month) ,"a") f.write( strftime("\r\n\r\n############################ %a, %d %b %Y %H:%M:%S\r\n", localtime())) f.write(self.getAll()) f.close() @@ -304,8 +408,6 @@ def perform(self): conn.setopt(pycurl.WRITEFUNCTION, self.body_callback) conn.setopt(pycurl.HEADERFUNCTION, self.header_callback) - #conn.setopt(pycurl.DEBUGFUNCTION, self.sent_header_callback) - #conn.setopt(pycurl.VERBOSE, 1) if self.__proxy!=None: conn.setopt(pycurl.PROXY,self.__proxy) @@ -315,6 +417,11 @@ def perform(self): conn.setopt(pycurl.HTTPHEADER,self.__getHeaders()) if self.method=="POST": conn.setopt(pycurl.POSTFIELDS,self.postdata) + + conn.setopt(pycurl.CUSTOMREQUEST, self.method) + if self.method == "HEAD": + conn.setopt(pycurl.NOBODY, True) + conn.perform() rp=Response() @@ -331,18 +438,23 @@ def perform(self): if self.followLocation: if self.response.getLocation(): - a=Request() - url=urlparse(self.response.getLocation()) - if not url[0] or not url[1]: - sc=url[0] - h=url[1] - if not sc: - sc=self.schema - if not h: - h=self.__host - a.setUrl(urlunparse((sc,h)+url[2:])) - else: - a.setUrl(self.response.getLocation()) + #a=Request() + a=copy.deepcopy(self) + newurl=self.createPath(self.response.getLocation()) + a.setUrl(newurl) + #url=urlparse(self.response.getLocation()) + #if not url[0] or not url[1]: + # sc=url[0] + # h=url[1] + # if not sc: + # sc=self.schema + # if not h: + # h=self.__host + # a.setUrl(urlunparse((sc,h)+url[2:])) + # self.__finalurl=urlunparse((sc,h)+url[2:]) + #else: + # a.setUrl(self.response.getLocation()) + # self.__finalurl=self.response.getLocation() a.setProxy(self.__proxy) ck="" @@ -358,19 +470,15 @@ def perform(self): if ck: self.addHeader("Cookie",ck) - a.perform() + self.follow_url = a.completeUrl self.response=a.response ######### ESTE conjunto de funciones no es necesario para el uso habitual de la clase - def getPostData (self): - return self.postdata - def getAll (self): - "Devuelve el texto de la request completa (lo que escrbirias por telnet" pd=self.postdata string=str(self.method)+" "+str(self.pathWithVariables)+" "+str(self.protocol)+"\n" for i,j in self.__headers.items(): @@ -381,16 +489,6 @@ def getAll (self): ########################################################################## - def sent_header_callback(self,type,data): - if type==pycurl.INFOTYPE_HEADER_OUT: - tp=TextParser() - tp.setSource("string",data) - - while (tp.readUntil("^([^:]+): (.*)$")): - self.addHeader(tp[0][0],tp[0][1]) - - - def header_callback(self,data): self.__performHead+=data @@ -409,25 +507,21 @@ def parseRequest (self,rawRequest,prot="http"): tp=TextParser() tp.setSource("string",rawRequest) - self.__variablesPOST={} + self.__variablesPOST=VariablesSet() self.__headers={} # diccionario, por ejemplo headers["Cookie"] - tp.readLine() try: - tp.search("(\w+) (.*) (HTTP\S*)") + tp.search("^(\w+) (.*) (HTTP\S*)$") self.method=tp[0][0] self.protocol=tp[0][2] except Exception,a: print rawRequest raise a - pathTMP=tp[0][1] + pathTMP=tp[0][1].replace(" ","%20") pathTMP=('','')+urlparse(pathTMP)[2:] pathTMP=urlunparse(pathTMP) - # print pathTMP - # pathTMP=pathTMP.replace("//","/") - self.time=strftime("%H:%M:%S", gmtime()) while True: tp.readLine() @@ -444,71 +538,15 @@ def parseRequest (self,rawRequest,prot="http"): while tp.readLine(): pd+=tp.lastFull_line - + boundary=None if "Content-Type" in self.__headers: values=self.__headers["Content-Type"].split(";") - if values[0].strip().lower()=="application/x-www-form-urlencoded": - self.ContentType=values[0] - elif values[0].strip().lower()=="multipart/form-data": - self.ContentType=values[0] - self.boundary=values[1].split("=")[1].strip() - - self.parsePOSTDATA(pd) - - - def parsePOSTDATA(self,pd): - - if self.ContentType=="application/x-www-form-urlencoded": - dicc=self.readUrlEncodedVariables(pd) - [self.addVariablePOST(i,j) for i,j in dicc] - - elif self.ContentType=="multipart/form-data": - self.multiPOSThead={} - dicc={} - tp=TextParser() - tp.setSource("string",pd) - # print self.boundary - # print tp.readUntil("%s$" % (self.boundary)) - - while True: - headers=[] - if not tp.readUntil("name=\"([^\"]+)\""): - break - var=tp[0][0] - headers.append(tp.lastFull_line.strip()) - while True: - tp.readLine() - if tp.search("^([^:]+): (.*)$"): - headers.append(tp.lastFull_line.strip()) - else: - break - - value="" - while True: - tp.readLine() - if not tp.search(self.boundary): - value+=tp.lastFull_line - else: - break - - if value[-2:]=="\r\n": - value=value[:-2] - - - dicc[var]=value - self.multiPOSThead[var]=headers - - if tp.search(self.boundary+"--"): - break + self.ContentType=values[0].strip().lower() + if self.ContentType=="multipart/form-data": + boundary=values[1].split("=")[1].strip() - - self.__variablesPOST.update(dicc) -# print pd -# print dicc -# print self.__variablesPOST + self.setPostData(pd,boundary) - else: - self.__uknPostData=pd class Response: @@ -573,11 +611,15 @@ def getHeaders (self): def getContent (self): return self.__content - def getAll (self): + def getTextHeaders(self): string=str(self.protocol)+" "+str(self.code)+" "+str(self.message)+"\r\n" for i,j in self.__headers: string+=i+": "+j+"\r\n" - string+="\r\n"+self.getContent() + + return string + + def getAll (self): + string=self.getTextHeaders()+"\r\n"+self.getContent() return string def Substitute(self,src,dst): diff --git a/wfuzz.py b/wfuzz.py index e03b2a5d..cac14f2b 100644 --- a/wfuzz.py +++ b/wfuzz.py @@ -15,17 +15,20 @@ from dictio import dictionary import re import hashlib +import socket + +from xml.dom import minidom +from urlparse import urlunparse ENCODERS={} encs=dir(encoders) for i in encs: try: if i[:8]=='encoder_': - ENCODERS[getattr(encoders,i).text.lower()]=i + ENCODERS[getattr(encoders,i).text.lower().replace(" ","_")]=i except: pass -# Generate_fuzz evolution class requestGenerator: def __init__(self,reqresp,varsSet,dictio,dictio2=None,proxy=None): @@ -172,12 +175,16 @@ def generate_request(self,req,payload1,payload2="",variable=""): return copycat else: + if fuzzmethods: + req.method="FUZZ" + rawReq=req.getAll() schema=req.schema method,userpass=req.getAuth() if rawReq.count('FUZZ'): a=Request() + a.followLocation = self.request.followLocation res=rawReq.replace("FUZZ",payload1) if rawReq.count('FUZ2Z'): res=res.replace("FUZ2Z",payload2) @@ -198,6 +205,8 @@ def generate_request(self,req,payload1,payload2="",variable=""): if method != 'None': a.setAuth(method,userpass) a.setProxy(self.proxy) + if fuzzmethods: + a.method = payload1 return a elif method and (userpass.count('FUZZ') ): @@ -219,6 +228,7 @@ class FuzzResult: def __init__(self,request,saveMemory=True): global OS + global node_service ####################################### self.len=0 @@ -281,31 +291,37 @@ def __init__(self,request,saveMemory=True): self.cookie=request.response.getCookie() if request.response.has_header('Location'): self.location=request.response['Location'] + elif request.followLocation and request.follow_url: + self.location="(*) %s" % request.follow_url else: self.location="" + + if request.response.has_header("Server"): + self.server = request.response["Server"] + else: + self.server = "" + m=hashlib.md5() m.update(request.response.getContent()) self.md5=m.hexdigest() - if __name__=="__main__": - - - if str(self.code) in hidecodes or str(self.lines) in hidelines or str(self.words) in hidewords or str(self.len) in hidechars: + if str(self.code) in hidecodes or str(self.lines) in hidelines or str(self.words) in hidewords or str(self.len) in hidechars \ + or (hideregex and hideregex.search(request.response.getContent())): fl="" else: fl="\r\n" - nreq+=1 - - imprimeResult (nreq,self.code,self.lines,self.words,request.description[-50:],fl,self.len) - del request - if html: - if str(self.code) in hidecodes or str(self.lines) in hidelines or str(self.words) in hidewords or str(self.len) in hidechars: - return - imprimeResultHtml (nreq,self.code,self.lines,self.words,request,self.len) + if html: + self.imprimeResultHtml(nreq,request) + elif magictree: + self.imprimeResultMagicTree(request) + + nreq+=1 + self.imprimeResult(nreq,request.description[-50:],fl) + del request def __getitem__ (self,key): for i,j in self.respHeaders: @@ -321,6 +337,100 @@ def has_header (self,key): return False + def imprimeResult(self, nreq,fuzzs,finalLine): + global printMutex + + printMutex.acquire() + + limpialinea() + sys.stdout.write ("%05d: C=" % (nreq) ) + + cc="" + wc=8 + if self.code>=400 and self.code<500: + if color: + cc="\x1b[31m" + wc=12 + elif self.code>=300 and self.code<400: + if color: + cc="\x1b[36m" + wc=11 + elif self.code>=200 and self.code<300: + if color: + cc="\x1b[32m" + wc=10 + else: + if color: + cc="\x1b[35m" + wc=1 + if OS!='nt': + sys.stdout.write (cc) + else: + WConio.textcolor(wc) + + + sys.stdout.write ("%03d" % (self.code)) + + if color: + if OS!='nt': + sys.stdout.write ("\x1b[37m") + else: + WConio.textcolor(8) + + if verbose: + sys.stdout.write (" %4d L\t %5d W\t %5d Ch %20.20s %51.51s \"%s\"%s" % (self.lines,self.words,self.len,self.server[:17],self.location[:48],fuzzs,finalLine)) + else: + sys.stdout.write (" %4d L\t %5d W\t %5d Ch\t \"%s\"%s" %(self.lines,self.words,self.len,fuzzs,finalLine)) + + sys.stdout.flush() + + + printMutex.release() + + def imprimeResultMagicTree(self, request): + def create_xml_element(parent, caption, text): + # Create a element + doc = minidom.Document() + el = doc.createElement(caption) + parent.appendChild(el) + + # Give the element some text + ptext = doc.createTextNode(text) + + el.appendChild(ptext) + return el + + node_url = create_xml_element(node_service, "url", str(request.completeUrl)) + + if self.server: + create_xml_element(node_url, "HTTPServer", self.server) + + if self.code == 301 or self.code == 302 and self.location: + create_xml_element(node_url, "RedirectLocation", self.location) + + create_xml_element(node_url, "ResponseCode", str(self.code)) + create_xml_element(node_url, "source", "WFuzz") + + def imprimeResultHtml(self, nreq,req): + + htmlc="" + if self.code>=400 and self.code<500: + htmlc="" + elif self.code>=300 and self.code<400: + htmlc="" + elif self.code>=200 and self.code<300: + htmlc="" + + if req.method.lower()=="get": + sys.stderr.write ("\r\n%05d%s%d%4dL%5dW%s\r\n" %(nreq,htmlc,self.code,self.lines,self.words,req.completeUrl,req.completeUrl)) + else: + inputs="" + postvars=req.variablesPOST() + for i in postvars: + inputs+="" % (i,req.getVariablePOST(i)) + + sys.stderr.write ("\r\n%05d\r\n%s%d\r\n%4dL\r\n%5dW\r\n
%s
%s
\r\n\r\n" %(nreq,htmlc,self.code,self.lines,self.words,req.description,req.completeUrl,inputs)) + ##################################################################################################### ##################################################################################################### @@ -420,52 +530,6 @@ def delete(self): printMutex=threading.BoundedSemaphore(value=mutex) -def imprimeResult (nreq,code,lines,words,fuzzs,finalLine,len): - global printMutex - - printMutex.acquire() - - limpialinea() - sys.stdout.write ("%05d: C=" % (nreq) ) - - cc="" - wc=8 - if code>=400 and code<500: - if color: - cc="\x1b[31m" - wc=12 - elif code>=300 and code<400: - if color: - cc="\x1b[36m" - wc=11 - elif code>=200 and code<300: - if color: - cc="\x1b[32m" - wc=10 - else: - if color: - cc="\x1b[35m" - wc=1 - if OS!='nt': - sys.stdout.write (cc) - else: - WConio.textcolor(wc) - - - sys.stdout.write ("%03d" % (code)) - - if OS!='nt': - sys.stdout.write ("\x1b[37m") - else: - WConio.textcolor(8) - - sys.stdout.write (" %4d L\t %5d W\t %5d Ch\t \"%s\"%s" %(lines,words,len,fuzzs,finalLine)) - - sys.stdout.flush() - - - printMutex.release() - def limpialinea(): sys.stdout.write ("\r") if OS!='nt': @@ -473,28 +537,6 @@ def limpialinea(): else: WConio.clreol() -def imprimeResultHtml (nreq,code,lines,words,req): - - htmlc="" - if code>=400 and code<500: - htmlc="" - elif code>=300 and code<400: - htmlc="" - elif code>=200 and code<300: - htmlc="" - - if req.method.lower()=="get": - sys.stderr.write ("\r\n%05d%s%d%4dL%5dW%s\r\n" %(nreq,htmlc,code,lines,words,req.completeUrl,req.completeUrl)) - else: - inputs="" - postvars=req.variablesPOST() - for i in postvars: - inputs+="" % (i,req.getVariablePOST(i)) - - sys.stderr.write ("\r\n%05d\r\n%s%d\r\n%4dL\r\n%5dW\r\n
%s
%s
\r\n\r\n" %(nreq,htmlc,code,lines,words,req.description,req.completeUrl,inputs)) - - - def select_encoding(typ): typ=typ.lower() @@ -508,13 +550,17 @@ def select_encoding(typ): if __name__=="__main__": color=False + verbose=False + fuzzmethods=False hidecodes=[] hidewords=[] hidelines=[] hidechars=[] + hideregex=None ths=20 postdata=False html=False + magictree=False postdata_data="" nreq=0 @@ -522,50 +568,57 @@ def select_encoding(typ): current_depth=0 banner=''' -************************************* -* Wfuzz 1.4c - The Web Bruteforcer * -* Coded by: * -* Christian Martorella * -* - cmartorella@edge-security.com * -* Carlos del ojo * -* - deepbit@gmail.com * -************************************* +******************************************************** +* Wfuzz 1.4d - The Web Bruteforcer * +* Coded by: * +* Christian Martorella (cmartorella@edge-security.com) * +* Carlos del ojo (deepbit@gmail.com) * +* Project contributor: * +* Xavier Mendez aka Javi (xmendez@edge-security.com) * +******************************************************** ''' - usage=''' -Usage: %s [options] \r\n + usage='''Usage: %s [options] \r\n Options: --c : Output with colors --x addr : use Proxy (ip:port) --d postdata : Use post data (ex: "id=FUZZ&catalogue=1") --H headers : Use headers (ex:"Host:www.mysite.com,Cookie:id=1312321&user=FUZZ") --z payload type : Specify type of payload (file,range,hexa-range,hexa-rand) --r N1-N2 : Specify range limits --f path : Specify file path (comma sepparated, if multiple FUZZ vars) --t N : Specify the number of threads (20 default) --e encoding : Encoding for payload (-e help for a list of encodings) --b cookie : Specify a cookie for the requests --R depth : Recursive path discovery --V alltype : All parameters bruteforcing (allvars and allpost). No need for FUZZ keyword. - ---basic auth : in format "user:pass" or "FUZZ:FUZZ" ---ntlm auth : in format "domain\user:pass" or "domain\FUZ2Z:FUZZ" ---digest auth : in format "user:pass" or "FUZZ:FUZZ" - ---hc N[,N]+ : Hide resposnes with the specified[s] code ---hl N[,N]+ : Hide responses with the specified[s] number of lines ---hw N[,N]+ : Hide responses with the specified[s] number of words ---hh N[,N]+ : Hide responses with the specified[s] number of chars ---html : Output in HTML format by stderr \r\n +-c : Output with colors +-v : Verbose information + +-x addr : use Proxy (ip:port) +-t N : Specify the number of threads (20 default) + +-e encoding : Encoding for payload (-e help for a list of encodings) +-R depth : Recursive path discovery +-I : Use HTTP HEAD instead of GET method (No HTML body responses). +--follow : Follow redirections + +-z payload type : Specify type of payload (file,range,hexa-range,hexa-rand) +-r N1-N2 : Specify range limits +-f path : Specify file path (comma sepparated, if multiple FUZZ vars) +-V alltype : All parameters bruteforcing (allvars and allpost). No need for FUZZ keyword. + +-X : Payload within HTTP methods (ex: "FUZZ HTTP/1.0"). No need for FUZZ keyword. +-b cookie : Specify a cookie for the requests +-d postdata : Use post data (ex: "id=FUZZ&catalogue=1") +-H headers : Use headers (ex:"Host:www.mysite.com,Cookie:id=1312321&user=FUZZ") + +--basic/ntlm/digest auth : in format "user:pass" or "FUZZ:FUZZ" or "domain\FUZ2Z:FUZZ" + +--hc/hl/hw/hh N[,N]+ : Hide resposnes with the specified[s] code/lines/words/chars +--hs regex : Hide responses with the specified regex within the response + +--html/magictree : Output in HTML/Magictree format by stderr Keyword: FUZZ,FUZ2Z wherever you put these words wfuzz will replace them by the payload selected. -Examples in the README. +Example: - wfuzz.py -c -z file -f commons.txt --hc 404 --html http://www.site.com/FUZZ 2> res.html + - wfuzz.py -c -z file -f users.txt,pass.txt --hc 404 --html http://www.site.com/log.asp?user=FUZZ&pass=FUZ2Z 2> res.html + + More examples in the README. ''' % (sys.argv[0]) try: - opts, args = getopt.getopt(sys.argv[1:], "cx:b:e:R:d:z:r:f:t:w:V:H:",['hc=','hh=','hl=','hw=','ntlm=','basic=','digest=','html']) + opts, args = getopt.getopt(sys.argv[1:], "IXvcx:b:e:R:d:z:r:f:t:w:V:H:",['hc=','hh=','hl=','hw=','hs=','ntlm=','basic=','digest=','html','magictree','follow']) optsd=dict(opts) if "-e" in optsd: if optsd["-e"] == "help": @@ -583,9 +636,17 @@ def select_encoding(typ): print usage sys.exit(-1) + if "-X" in optsd: + fuzzmethods=True + + if "-v" in optsd: + verbose=True + if "-c" in optsd: color=True + if "--magictree" in optsd: + magictree=True if "--html" in optsd: html=True if "--hc" in optsd: @@ -596,6 +657,8 @@ def select_encoding(typ): hidelines=optsd["--hl"].split(",") if "--hh" in optsd: hidechars=optsd["--hh"].split(",") + if "--hs" in optsd: + hideregex=re.compile(optsd["--hs"],re.MULTILINE|re.DOTALL) payloadtype=optsd ["-z"] d2=None @@ -652,6 +715,9 @@ def select_encoding(typ): a=Request() a.setUrl(url) + if "-I" in optsd: + a.method="HEAD" + if "--basic" in optsd: a.setAuth("basic",optsd["--basic"]) @@ -662,8 +728,10 @@ def select_encoding(typ): a.setAuth("ntlm",optsd["--ntlm"]) if "-d" in optsd: - a.addPostdata(optsd["-d"]) - print "test" + a.setPostData(optsd["-d"]) + + if "--follow" in optsd: + a.followLocation = True if "-b" in optsd: a.addHeader("Cookie",optsd["-b"]) @@ -694,6 +762,55 @@ def select_encoding(typ): if html: sys.stderr.write("

Fuzzing %s

\r\n\r\n\r\n" % (url) ) + elif magictree: + def create_xml_element(parent, caption, text): + # Create a element + doc = minidom.Document() + el = doc.createElement(caption) + parent.appendChild(el) + + # Give the element some text + ptext = doc.createTextNode(text) + + el.appendChild(ptext) + return el + doc = minidom.Document() + + # + node_mt = doc.createElement("magictree") + node_mt.setAttribute("class", "MtBranchObject") + + # + node_td = doc.createElement("testdata") + node_td.setAttribute("class", "MtBranchObject") + node_mt.appendChild(node_td) + + #209.85.146.105 + host = a["Host"] + if host.find(":") > 0: + host, port = host.split(":") + else: + port = 80 + if a.schema.lower() == "https": + port = 443 + + try: + resolving = socket.gethostbyname(host) + node_h = create_xml_element(node_td, "host", str(resolving)) + except socket.gaierror: + node_h = create_xml_element(node_td, "host", str(host)) + + #tcp + node_ipr = create_xml_element(node_h, "ipproto", "tcp") + + #80openhttp + node_port = create_xml_element(node_ipr, "port", str(port)) + create_xml_element(node_port, "state", "open") + if a.schema.lower() == "https": + node_port = create_xml_element(node_port, "tunnel", "ssl") + + node_service = create_xml_element(node_port, "service", "http") + fz=Fuzzer(rh,hidecodes,ths) print banner @@ -701,9 +818,14 @@ def select_encoding(typ): print "Payload type: " + payloadtype + "\n" print "Total requests: " + str(rh.count()) - print "==================================================================" - print "ID Response Lines Word Chars Request " - print "==================================================================\r\n" + if verbose: + print "=========================================================================================================================================" + print "ID Response Lines Word Chars Server Redirect Request " + print "=========================================================================================================================================\r\n" + else: + print "==================================================================" + print "ID Response Lines Word Chars Request " + print "==================================================================\r\n" fz.Launch() try: while True: @@ -724,10 +846,13 @@ def select_encoding(typ): i.req.setUrl(i.req.completeUrl+"FUZZ") rhtemp=requestGenerator(i.req,"None",d1,None,proxy) rh2.append(rhtemp) + if i.code==200 and i.req.followLocation and i.req.follow_url and i.req.follow_url[-1]=='/': + i.req.setUrl(i.req.follow_url+"FUZZ") + rhtemp=requestGenerator(i.req,"None",d1,None,proxy) + rh2.append(rhtemp) elif i.code>=300 and i.code<400: if i.has_header("Location") and i["Location"][-1]=='/': i.req.setUrl(i["Location"]+"FUZZ") - print i.req rhtemp=requestGenerator(i.req,"None",d1,None,proxy) rh2.append(rhtemp) elif i.code==401: @@ -751,6 +876,9 @@ def select_encoding(typ): if html: sys.stderr.write("
#requestCode#lines#wordsUrl
Wfuzz by EdgeSecurity
\r\n") + elif magictree: + sys.stderr.write(node_mt.toxml()) + sys.exit(0) @@ -763,7 +891,8 @@ def select_encoding(typ): if html: sys.stderr.write("
Wfuzz by EdgeSecurity
\r\n") - + elif magictree: + sys.stderr.write(node_mt.toxml()) limpialinea() sys.stdout.write("\r\n")