¿Cómo prevenir XSS con HTML / PHP?

256

¿Cómo evito XSS (scripting entre sitios) usando solo HTML y PHP?

He visto muchas otras publicaciones sobre este tema, pero no he encontrado un artículo que indique de manera clara y concisa cómo prevenir realmente XSS.

Tim Tim
fuente
3
Solo una nota de que esto no resolverá el caso en que desee utilizar la entrada del usuario como un atributo HTML. Por ejemplo, la URL de origen de una imagen. No es un caso común, pero fácil de olvidar.
Michael Mior
@MichaelMior aquí hay una solución para evitar XSS en hrefo srcatributo HTML: stackoverflow.com/questions/19047119/…
baptx
Hay un buen artículo aquí que explica XSS y cómo prevenirlo en diferentes idiomas (incl. PHP).
XCore

Respuestas:

296

Básicamente, debe usar la función htmlspecialchars()siempre que desee enviar algo al navegador que provenga de la entrada del usuario.

La forma correcta de usar esta función es algo como esto:

echo htmlspecialchars($string, ENT_QUOTES, 'UTF-8');

Google Code University también tiene estos videos muy educativos sobre seguridad web:

Alix Axel
fuente
77
@TimTim: para la mayoría de los casos, sí. Sin embargo, cuando necesita permitir la entrada de HTML, las cosas se ponen un poco más complicadas y, si este es el caso, le recomiendo que use algo como htmlpurifier.org
Alix Axel
@Alix Axel, entonces, ¿es su respuesta usar htmlspecialchars o htmlpurifier.org ?
TimTim
3
Si necesita aceptar la entrada HTML, use HTML Purifier, si no lo usa htmlspecialchars().
Alix Axel
99
htmlspecialchars o htmlentities? Consulte aquí stackoverflow.com/questions/46483/…
kiranvj
44
La mayoría de las veces es correcto, pero no es tan simple como eso. Debería considerar poner una cadena no confiable en HTML, Js, Css, y considerar poner HTML no confiable en HTML. Mira esto: owasp.org/index.php/…
hombre de bronce
41

Una de mis referencias favoritas de OWASP es la explicación de Cross-Site Scripting porque, si bien hay una gran cantidad de vectores de ataque XSS, ¡el seguir algunas reglas puede defenderse contra la mayoría de ellas!

Esta es la hoja de trucos de seguridad de PHP

Wahyu Kristianto
fuente
77
Yo también ... Esta es la hoja de trucos de evasión del filtro XSS owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
1
No es exactamente XSS, pero creo que XSS y CSRF se mezclan comúnmente y ambos son realmente peligrosos: owasp.org/index.php/…
Simon
2
Esta página ya no existe
Mazzy
1
@Mazzy última caché web.archive.org/web/20180817180409/owasp.org/index.php/…
Wahyu Kristianto
15

Uno de los pasos más importantes es desinfectar cualquier entrada del usuario antes de que se procese y / o se devuelva al navegador. PHP tiene algunas funciones de " filtro " que pueden usarse.

La forma que suelen tener los ataques XSS es insertar un enlace a algún javascript externo que contenga intenciones maliciosas para el usuario. Lea más sobre esto aquí .

También querrás probar tu sitio. Puedo recomendar el complemento XSS Me de Firefox .

James Kolpack
fuente
¿Qué necesito para asegurarme de desinfectar la entrada exactamente? ¿Hay un carácter / cadena en particular que debo tener en cuenta?
TimTim
27
@TimTim - no. Toda la entrada de usuario debe siempre ser considerado como hostil inherentemente.
zombat
Además, los datos internos (empleados, administradores de sistemas, etc.) podrían ser inseguros. Debe identificar y monitorear (con la fecha de registro y el usuario) los datos que se muestran con interpretación.
Samuel Dauzon
9

En orden de preferencia:

  1. Si está utilizando un motor de plantillas (por ejemplo, Twig, Smarty, Blade), compruebe que ofrece un escape sensible al contexto. Sé por experiencia que Twig sí.{{ var|e('html_attr') }}
  2. Si desea permitir HTML, use HTML Purifier . Incluso si cree que solo acepta Markdown o ReStructuredText, aún desea purificar la salida HTML de estos lenguajes de marcado.
  3. De lo contrario, use htmlentities($var, ENT_QUOTES | ENT_HTML5, $charset)y asegúrese de que el resto de su documento use el mismo juego de caracteres que $charset. En la mayoría de los casos, 'UTF-8'es el conjunto de caracteres deseado.

Además, asegúrese de escapar en la salida, no en la entrada .

Scott Arciszewski
fuente
7

Publicación cruzada de esto como referencia consolidada de la versión beta de SO Documentation que se desconecta.

Problema

La secuencia de comandos entre sitios es la ejecución involuntaria de código remoto por parte de un cliente web. Cualquier aplicación web puede exponerse a XSS si recibe información de un usuario y la envía directamente a una página web. Si la entrada incluye HTML o JavaScript, el código remoto se puede ejecutar cuando el cliente web presenta este contenido.

Por ejemplo, si un lado de terceros contiene un archivo JavaScript:

// http://example.com/runme.js
document.write("I'm running");

Y una aplicación PHP genera directamente una cadena que se le pasa:

<?php
echo '<div>' . $_GET['input'] . '</div>';

Si un parámetro GET no verificado contiene, <script src="http://example.com/runme.js"></script>entonces la salida del script PHP será:

<div><script src="http://example.com/runme.js"></script></div>

Se ejecutará el JavaScript de terceros y el usuario verá "Estoy ejecutando" en la página web.

Solución

Como regla general, nunca confíe en la información proveniente de un cliente. Cada valor GET, POST y cookie puede ser cualquier cosa y, por lo tanto, debe validarse. Al generar cualquiera de estos valores, escape de ellos para que no se evalúen de manera inesperada.

Tenga en cuenta que incluso en las aplicaciones más simples, los datos se pueden mover y será difícil hacer un seguimiento de todas las fuentes. Por lo tanto, es una buena práctica escapar siempre de la salida.

PHP proporciona algunas formas de escapar de la salida según el contexto.

Funciones de filtro

Los PHP Funciones de filtro permiten que los datos de entrada al script php para ser desinfectado o validado en muchos sentidos . Son útiles al guardar o generar la entrada del cliente.

Codificación HTML

htmlspecialcharsconvertirá los "caracteres especiales HTML" en sus codificaciones HTML, lo que significa que no se procesarán como HTML estándar. Para arreglar nuestro ejemplo anterior usando este método:

<?php
echo '<div>' . htmlspecialchars($_GET['input']) . '</div>';
// or
echo '<div>' . filter_input(INPUT_GET, 'input', FILTER_SANITIZE_SPECIAL_CHARS) . '</div>';

Daría salida:

<div>&lt;script src=&quot;http://example.com/runme.js&quot;&gt;&lt;/script&gt;</div>

Todo lo que esté dentro de la <div>etiqueta no será interpretado como una etiqueta JavaScript por el navegador, sino como un simple nodo de texto. El usuario verá con seguridad:

<script src="http://example.com/runme.js"></script>

Codificación de URL

Al generar una URL generada dinámicamente, PHP proporciona la urlencodefunción para generar de manera segura URL válidas. Entonces, por ejemplo, si un usuario puede ingresar datos que se convierten en parte de otro parámetro GET:

<?php
$input = urlencode($_GET['input']);
// or
$input = filter_input(INPUT_GET, 'input', FILTER_SANITIZE_URL);
echo '<a href="http://example.com/page?input="' . $input . '">Link</a>';

Cualquier entrada maliciosa se convertirá en un parámetro de URL codificado.

Uso de bibliotecas externas especializadas o listas OWASP AntiSamy

Algunas veces querrá enviar HTML u otro tipo de entradas de código. Deberá mantener una lista de palabras autorizadas (lista blanca) y no autorizadas (lista negra).

Puede descargar listas estándar disponibles en el sitio web de OWASP AntiSamy . Cada lista es adecuada para un tipo específico de interacción (ebay api, tinyMCE, etc.). Y es de código abierto.

Existen bibliotecas para filtrar HTML y evitar ataques XSS para el caso general y que funcionan al menos tan bien como las listas de AntiSamy con un uso muy fácil. Por ejemplo, tienes HTML Purifier

Matt S
fuente
5

Muchos marcos ayudan a manejar XSS de varias maneras. Al rodar el suyo propio o si hay alguna preocupación de XSS, podemos aprovechar filter_input_array (disponible en PHP 5> = 5.2.0, PHP 7.) Normalmente agregaré este fragmento a mi SessionController, porque todas las llamadas pasan allí antes que cualquier otro controlador interactúa con los datos. De esta manera, todas las entradas del usuario se desinfectan en 1 ubicación central. Si esto se hace al comienzo de un proyecto o antes de que su base de datos se envenene, no debería tener ningún problema al momento de la salida ... detiene la basura, la basura sale.

/* Prevent XSS input */
$_GET   = filter_input_array(INPUT_GET, FILTER_SANITIZE_STRING);
$_POST  = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING);
/* I prefer not to use $_REQUEST...but for those who do: */
$_REQUEST = (array)$_POST + (array)$_GET + (array)$_REQUEST;

Lo anterior eliminará TODAS las etiquetas HTML y script. Si necesita una solución que permita etiquetas seguras, basadas en una lista blanca, consulte HTML Purifier .


Si su base de datos ya está envenenada o si desea tratar con XSS en el momento de la salida, OWASP recomienda crear una función de envoltura personalizada echoy usarla EN TODAS PARTES para obtener valores proporcionados por el usuario:

//xss mitigation functions
function xssafe($data,$encoding='UTF-8')
{
   return htmlspecialchars($data,ENT_QUOTES | ENT_HTML401,$encoding);
}
function xecho($data)
{
   echo xssafe($data);
}
webaholik
fuente
2

También puede configurar algunos encabezados de respuesta HTTP relacionados con XSS a través de header(...)

Protección X-XSS "1; modo = bloque"

para estar seguro, el modo de protección XSS del navegador está habilitado.

Content-Security-Policy "default-src 'self'; ..."

para habilitar la seguridad del contenido del lado del navegador. Consulte este para obtener detalles de la Política de seguridad de contenido (CSP): http://content-security-policy.com/ Especialmente, configurar CSP para bloquear scripts en línea y fuentes de scripts externos es útil contra XSS.

para obtener un conjunto general de encabezados de respuesta HTTP útiles sobre la seguridad de su aplicación web, consulte OWASP: https://www.owasp.org/index.php/List_of_useful_HTTP_headers

Chris
fuente
1
<?php
function xss_clean($data)
{
// Fix &entity\n;
$data = str_replace(array('&amp;','&lt;','&gt;'), array('&amp;amp;','&amp;lt;','&amp;gt;'), $data);
$data = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $data);
$data = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $data);
$data = html_entity_decode($data, ENT_COMPAT, 'UTF-8');

// Remove any attribute starting with "on" or xmlns
$data = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $data);

// Remove javascript: and vbscript: protocols
$data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data);
$data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $data);
$data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $data);

// Only works in IE: <span style="width: expression(alert('Ping!'));"></span>
$data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
$data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
$data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $data);

// Remove namespaced elements (we do not need them)
$data = preg_replace('#</*\w+:\w[^>]*+>#i', '', $data);

do
{
    // Remove really unwanted tags
    $old_data = $data;
    $data = preg_replace('#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $data);
}
while ($old_data !== $data);

// we are done...
return $data;
}
Abdo-Host
fuente
55
No debe usar preg_replacecomo se usa evalen su entrada. owasp.org/index.php/PHP_Security_Cheat_Sheet#Code_Injection
CrabLab
0

Usar htmlspecialcharsen PHP. En HTML intente evitar usar:

element.innerHTML = “…”; element.outerHTML = “…”; document.write(…); document.writeln(…);

donde vares controlado por el usuario .

Obviamente, también intente evitarlo eval(var), si tiene que usar alguno de ellos, intente escapar de JS , escapar de HTML y es posible que tenga que hacer algo más, pero para lo básico esto debería ser suficiente.

Pablo
fuente
0

La mejor manera de proteger su entrada es usar la htmlentitiesfunción. Ejemplo:

htmlentities($target, ENT_QUOTES, 'UTF-8');

Puedes obtener más información aquí .

Marco Concas
fuente