Seguridad PHP - Cross Site Scripting (XSS)
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.
Un ataque XSS consiste en enviar un script malicioso 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.
Los scripts se ocultan entre solicitudes legítimas como la recepción de datos de un formulario web 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. En general en los ataques XSS son usadas etiquetas como SCRIPT, OBJECT, APPLET, EMBED y FORM.
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.
Protección XSS
La protección debe darse tanto al momento de ingresar los datos como al momento de mostrarlos en el browser, no debe haber confusión al momento de pensar que utilizando prepared statements de PDO evitaremos ataques XSS pues estas sentencias preparadas están más orientadas a evitar SQL Injection, así pues, para ingresar datos desde un formulario(i.e.) hacia una base de datos(i.e.) podemos seguir estos pasos:
Validar los datos que se reciben y “sanearlos”
Como regla de oro debemos validar cualquier dato que vaya a ser ingresado a la base de datos, mucho más si esta información viene de parte del usuario ya que solo podemos permitir la entrada esperada. Y por supuesto no confiar únicamente en la posible validación en el lado cliente del sistema, para este fin se pueden utilizar expresiones regulares, Type Casting o bien FILTER_VAR en su opción FILTER_SANITIZE
Prevenir XSS con strip_tags
Lo primero para prevenir estos ataques es limpiar cualquier etiqueta html ingresada por el usuario, para ello podemos utilizar la función strip_tags, la cual elimina etiquetas html. Esto es una forma básica de prevenir un ataque XSS, pero existen versiones más elaboradas de códigos de ataque que pueden no ser limpiadas por esta función. Otro problema de esta solución es que limpia todas las etiquetas html, pero en algunas ocasiones existe la necesidad de permitir algunas etiquetas (Por ejemplo: p, strong, em) para lo cual necesitamos una solución más elaborada.
Prevenir XSS con FILTER_VAR
PHP nos presenta un conjunto de funciones que nos permitirán la validación y saneamiento de datos de una forma sencilla. La funcion filter_var (PHP >=5.2) nos permite filtrar una variable según el filtro especificado, a continuación un ejemplo que valida que el valor no este vacio, luego hace un filtrado de datos para por ultimo verificar que sea del tipo esperado, en caso de pasar las validaciones envía un correo con los datos suministrados, en caso de no pasar las validaciones muestra un mensaje de error
<?php
$errors=””;
if (isset($_POST['Submit'])) {
if ($_POST['name'] != "") {
$_POST['name'] = filter_var($_POST['name'], FILTER_SANITIZE_STRING);
if ($_POST['name'] == "") {
$errors .= 'Please enter a valid name.
';
}
} else {
$errors .= 'Please enter your name.
';
}
if ($_POST['email'] != "") {
$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors .= "$email is NOT a valid email address.
";
}
} else {
$errors .= 'Please enter your email address.
';
}
if ($_POST['homepage'] != "") {
$homepage = filter_var($_POST['homepage'], FILTER_SANITIZE_URL);
if (!filter_var($homepage, FILTER_VALIDATE_URL)) {
$errors .= "$homepage is NOT a valid URL.
";
}
} else {
$errors .= 'Please enter your home page.
';
}
if ($_POST['message'] != "") {
$_POST['message'] = filter_var($_POST['message'], FILTER_SANITIZE_STRING);
if ($_POST['message'] == "") {
$errors .= 'Please enter a message to send.
';
}
} else {
$errors .= 'Please enter a message to send.
';
}
if (!$errors) {
$mail_to = 'me@somewhere.com';
$subject = 'New Mail from Form Submission';
$message = 'From: ' . $_POST['name'] . "\n";
$message .= 'Email: ' . $_POST['email'] . "\n";
$message .= 'Homepage: ' . $_POST['homepage'] . "\n";
$message .= "Message:\n" . $_POST['message'] . "\n\n";
mail($to, $subject, $message);
echo "Thank you for your email!
";
} else {
echo '' . $errors . '
';
}
}
?>
Función para no permitir entidades HTML
Es bastante simple pero al menos a mi me ha funcionado
<?php
function limpiar_tags($tags){
$tags = strip_tags($tags);
$tags = stripslashes($tags);
$tags = htmlentities($tags);
return $tags;
}
?>
Igualmente te dejo esta otra función que me ha funcionado
<?php
function SanitizeInputXSS($dirty_input) {
return htmlspecialchars(rawurldecode(trim($dirty_input)), ENT_QUOTES,'UTF-8');
}
$name = SanitizeInputXSS($_POST['name']);
?>
O si quieres reemplazar los caracteres por tu cuenta, créate un array con los caracteres a reemplazar y los caracteres que serán reemplazados
<?php
function PreventXSS($String)
{
$Find = array('&', '<', '>', '/');
$Replace = array('& ;', '< ;', '> ;', '/ ;');
$NewString = str_replace($Find, $Replace, $String);
return $NewString;
}
?>
Permitir entidades HTML
Las validaciones anteriores sanean los datos de usuario de código HTML y es funcional para la mayoría de datos que provienen de un formulario, pero que pasa cuando por la lógica del negocio queremos ingresar las etiquetas HTML a la base de datos? 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() o bien con FILTER_VAR esto no será posible.
Prevenir XSS con PHP Input Filter
PHP Input Filter, es una clase escrita en PHP que permite filtrar código malicioso ingresado en los formularios para prevenir ataques XSS de manera sencilla, tiene la cualidad de no limpiar determinadas etiquetas o atributos.
Para utilizar esta clase, descargamos los archivos desde el web oficial e incluimos el archivo class.inputfilter.php al inicio de nuestro PHP. Luego se debe crear una instancia de la clase InputFilter, entonces podemos filtrar los datos con el método process de la siguiente forma:
<?php
require_once("class.inputfilter.php");
$ifilter = new InputFilter();
$nombre = $ifilter->process($_POST['nombre']);
?>
También se pueden filtrar todos los campos enviados por el formulario, por ejemplo si el formulario es enviado por el método POST, podríamos utilizar el siguiente código para filtrar todos los campos para luego utilizarlos sin problemas.
<?php
require_once("class.inputfilter.php");
$ifilter = new InputFilter();
$_POST = $ifilter->process($_POST);
?>
Permitir etiquetas con PHP Input Filter
Para evitar que algunas etiquetas sean filtradas podemos pasarlas como un array como parámetro al momento de crear la instancia de la clase. Por ejemplo para permitir las etiquetas strong yem y tendríamos:
<?php
require_once("class.inputfilter.php");
$ifilter = new InputFilter(array('em', 'strong'));
$_POST = $ifilter->process($_POST);
?>
Permitir atributos con PHP Input Filter
También existe la posibilidad de desear que algunos atributos sean permitidos en el ingreso de los datos por parte del usuario. Por ejemplo podríamos permitir que los visitantes puedan ingresar enlaces para lo cual tendríamos que permitir el atributo href que contiene la ruta de destino.
<?php
require_once("class.inputfilter.php");
$ifilter = new InputFilter(array('a'), array('href'));
$comentario = $ifilter->process($_POST['comentario']);
?>
Prevenir XSS con HTMLPurifier
Esta librería es un gran aliado para la seguridad que nos permite analizar una entrada HTML, BBCode o Markdown y eliminar aquellas partes que contengan código malicioso. Además, nos asegura que el HTML de entrada cumple con el estándar declarado en el doctype. Instalarla es sencillo (Requiere PHP 5 y los test se han realizado para versiones >= 5.0.5)
Utilizando Prepared Statements
Por ultimo recuerda que si bien hemos trabajado en evitar ataques XSS aún queda pendiente el ataque bajo SQL Injection por lo que al ingresar a la base de datos lo recomendable es utilizar PDO con sus prepared statements o bien mysqli, asi pues, la lógica es: recibir los datos y validar que existe información, luego hacer un saneamiento de esos datos para su posterior validación.
Mostrando información
Generalmente la función htmlspecialchars() es suficiente para filtrar la salida de contenido que se muestre en el navegador. Si usamos algún tipo de codificación especial diferente a ISO-8859-1 o UTF-8, entonces usaremos la función htmlentities() para dicha tarea.
Para mostrar la información al usuario siempre buscando prevenir XSS se puede utilizar esta función, siempre y cuando estés programando en una versión que aún no ha depreciado el uso de magic_quotes esto es, en versiones de PHP menor a la 5.3.0 ya que esta característica ha sido declarada OBSOLETA desde PHP 5.3.0 y ELIMINADA a partir de PHP 5.4.0.
<?php
function sanitize($html){
if(get_magic_quotes_gpc()){
$html = stripslashes($html);
}
$html = mb_convert_encoding($html, 'UTF-8', 'UTF-8');
$html = htmlentities($html, ENT_QUOTES, 'UTF-8');
return $html;
}
?>
Esta función puede servirte para versiones más recientes
<?php
function bbkoda($text) {
$text = htmlspecialchars($text, ENT_QUOTES,'UTF-8');
// $text = htmlspecialchars($text, ENT_QUOTES);
$text = nl2br($text);
$hitta = array(
"'\[b](.*?)\[/b]'is",
"'\[i](.*?)\[/i]'is"
);
$byt = array(
"\\1",
"\\1"
);
$text = preg_replace($hitta, $byt, $text);
return $text;
}
?>
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.
Por último, una aplicación que puede sernos de gran utilidad es XSSploit nos permitirá localizar las vulnerabilidades de nuestra web antes de publicarla y evitar muchos problemas en el futuro, la web esta en http://www.scrt.ch/en/attack/downloads/xssploit