-
Notifications
You must be signed in to change notification settings - Fork 46
/
struts2_hunt_v2.py
189 lines (147 loc) · 6.02 KB
/
struts2_hunt_v2.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2018/01/23 下午2:14
# @Author : Komi
# @File : struts2_hunt.py
# @Project : POC-T
# @Ver: : 0.4
# Referer https://threathunter.org/topic/594a9f0fde1d70c20885ccd5
import time
import re
import sys
import requests
import platform
ERROR_KEYS = ['Struts Problem Report','org.apache.struts2','struts.devMode','struts-tags',
'There is no Action mapped for namespace']
# check suffix :.do,.action
def checkBySuffix(info):
if info['code'] == 404:
return False
html = info['html']
matchs_action = re.findall(r"""(['"]{1})(/?((?:(?!\1|\n|http(s)?://).)+)\.action)(\?(?:(?!\1).)*)?\1""", html,
re.IGNORECASE)
matchs_do = re.findall(r"""(['"]{1})(/?((?:(?!\1|\n|http(s)?://).)+)\.do)(\?(?:(?!\1).)*)?\1""", html,
re.IGNORECASE)
if len(matchs_do)+len(matchs_action)> 0 and (".action" in str(matchs_action) or ".do" in str(matchs_do)):
return True
else:
return False
# check devMode page
def checkDevMode(url):
target_url = url+"/struts/webconsole.html"
info = gethtml(target_url)
if info['code'] == 200 and "Welcome to the OGNL console" in info['html']:
return True
else:
return False
# check Error Messages.
def checActionsErrors(url):
test_tmpurls = []
test_tmpurls.append(url+"/?actionErrors=1111")
test_tmpurls.append(url+"/tmp2017.action")
test_tmpurls.append(url + "/tmp2017.do")
test_tmpurls.append(url + "/system/index!testme.action")
test_tmpurls.append(url + "/system/index!testme.do")
for test_url in test_tmpurls:
info = gethtml(test_url)
for error_message in ERROR_KEYS:
if error_message in info['html'] and info['code'] == 500:
print ("[+] found error_message:",error_message)
return True
return False
# check CheckboxInterceptor.
def checkCheckBox(url):
# url = "https://www.vuln.org/?keyword=aaa&loginname=admin&password=888"
"""
https://www.vuln.org/?__checkbox_keyword=aaa&loginname=admin&password=888
https://www.vuln.org/?keyword=aaa&__checkbox_loginname=admin&password=888
https://www.vuln.org/?keyword=aaa&loginname=admin&__checkbox_password=888
em:
http://wsbs.wgj.sh.gov.cn/shwgj_zwdt/core/web/welcome/index!search.action
"""
for match in re.finditer(r"((\A|[?&])(?P<parameter>[^_]\w*)=)(?P<value>[^&#]+)", url):
info = gethtml(url.replace(match.group('parameter'), "__checkbox_"+match.group('parameter')))
check_key = 'name="{}"'.format(match.group('parameter'))
check_value = 'value="false"'
html = info['html']
matchs_inputTags = re.findall(r"""<\s*input[^>]*>""", html,re.IGNORECASE)
for input_tag in matchs_inputTags:
if check_key in input_tag and check_value in input_tag:
return True
return False
# 给 2 个测试站,没有找到好的实现思路
# 初步想法: 对比三次请求返回的文本大小差异,超时请求况,文本是否保护request_locale来做决策
#
# https://eservices.customs.gov.hk/MSOS/wsrh/001s0?request_locale=en_US
# https://ctc.camds.org/camds/mainpage.action?request_locale=zh_CN
# https://ctc.camds.org/camds/mainpage.action?request_locale=en_US
# http://www.quamnet.com/newsUScontent.action?request_locale=zh_CN&articleId=3436914
def checkl18n(target):
info_orgi = gethtml(target)
time.sleep(0.5)
info_zhCN = gethtml(target+"?"+'request_locale=zh_CN')
time.sleep(0.5)
info_enUS = gethtml(target+"?"+ 'request_locale=en_US')
time.sleep(0.5)
if "request_locale=zh_CN" in info_orgi['html'] and "request_locale=en_US" in info_orgi['html']:
return True
if abs(len(info_zhCN['html']) - len(info_enUS['html'])) > 1024:
return True
return False
def gethtml(url):
try:
headers = {}
headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0'
headers['Accept-Language'] ='en-us;q=0.5,en;q=0.3'
headers['Referer'] = url
u = requests.get(url, timeout=3, headers=headers, allow_redirects=True)
content = u.text
return {"html":content,"code":u.status_code,"url":url}
except Exception as e:
return get_html_phantomJS(url)
# 使用PhantomJS获取网页源码
def get_html_phantomJS(url):
try:
from selenium import webdriver
dr = webdriver.PhantomJS()
dr.get(url)
time.sleep(2)
return {"html": dr.page_source, "code": 200, "url": url}
except Exception as e:
# http://phantomjs.org/
print (e)
return {"html":"", "code":500, "url":url}
def poc(target):
ori_target = target
if not target.lower().startswith('http://') and not target.lower().startswith('https://'):
target = 'http://' + target
p_version = platform.python_version()
if "2.7" in p_version:
from urlparse import urlparse
elif "3.5" in p_version:
from urllib.parse import urlparse
target = urlparse(target).scheme + "://" + urlparse(target).netloc
index_html = gethtml(target)
ori_html = gethtml(ori_target)
if checkDevMode(target):
return "[success] %s is struts2! [checkDevMode]" % target
if checkBySuffix(index_html):
return "[success] %s is struts2! [checkBySuffix]" % target
if checkBySuffix(ori_html):
return "[success] %s is struts2! [checkBySuffix]" % ori_target
if checActionsErrors(target):
return "[success] %s is struts2! [checActionsErrors]" % target
if checkCheckBox(target):
return "[success] %s is struts2! [checkCheckBox]" % target
if checkl18n(target):
return "[success] %s is struts2! [checkl18n]" % target
return False
if __name__ == "__main__":
if len(sys.argv) > 1:
result = poc(sys.argv[1])
if not result:
print("[*] %s is not struts2!" % sys.argv[1])
else:
print(result)
else:
print("\n[*]usag: python poc.py http://www.demo.com")