Latte es sinónimo de seguridad
Latte es el único sistema de plantillas para PHP con una protección eficaz contra la vulnerabilidad crítica Cross-site Scripting (XSS). Y esto es gracias al llamado escape sensible al contexto. Le contaremos,
- cuál es el principio de la vulnerabilidad XSS y por qué es tan peligrosa
- por qué Latte es tan eficaz en la defensa contra XSS
- cómo se puede crear fácilmente un agujero de seguridad en plantillas Twig, Blade y similares
Cross-site Scripting (XSS)
Cross-site Scripting (abreviado XSS) es una de las vulnerabilidades más comunes de los sitios web y, al mismo tiempo, muy peligrosa. Permite a un atacante insertar un script malicioso (llamado malware) en la página de otra persona, que se ejecuta en el navegador de un usuario desprevenido.
¿Qué puede hacer un script así? Por ejemplo, puede enviar al atacante cualquier contenido de la página atacada, incluidos los datos sensibles mostrados después de iniciar sesión. Puede modificar la página o realizar otras peticiones en nombre del usuario. Si se tratara, por ejemplo, de un webmail, podría leer mensajes sensibles, modificar el contenido mostrado o reconfigurar la configuración, por ejemplo, activar el reenvío de copias de todos los mensajes a la dirección del atacante para obtener acceso también a futuros emails.
Por eso XSS figura en los primeros puestos de los rankings de las vulnerabilidades más peligrosas. Si aparece una vulnerabilidad en un sitio web, es necesario eliminarla lo antes posible para evitar su abuso.
¿Cómo surge la vulnerabilidad?
El error surge en el lugar donde se genera la página web y se imprimen las variables. Imagine que está creando una página con búsqueda, y al principio habrá un párrafo con la expresión buscada en la forma:
echo '<p>Resultados de la búsqueda para <em>' . $search . '</em></p>';
Un atacante puede escribir en el campo de búsqueda y, por extensión, en la variable $search
cualquier cadena,
incluido código HTML como <script>alert("Hacked!")</script>
. Dado que la salida no está saneada de
ninguna manera, se convierte en parte de la página mostrada:
<p>Resultados de la búsqueda para <em><script>alert("Hacked!")</script></em></p>
El navegador, en lugar de imprimir la cadena buscada, ejecuta JavaScript. Y así el atacante toma el control de la página.
Puede objetar que al insertar código en la variable, JavaScript se ejecuta, pero solo en el navegador del atacante. ¿Cómo llega a la víctima? Desde esta perspectiva, distinguimos varios tipos de XSS. En nuestro ejemplo con la búsqueda, hablamos de reflected XSS. Aquí también es necesario guiar a la víctima para que haga clic en un enlace que contendrá el código malicioso en el parámetro:
https://example.com/?search=<script>alert("Hacked!")</script>
Aunque guiar al usuario al enlace requiere cierta ingeniería social, no es nada complicado. Los usuarios hacen clic en los
enlaces, ya sea en emails o en redes sociales, sin pensarlo mucho. Y que haya algo sospechoso en la dirección se puede
enmascarar usando un acortador de URL, el usuario entonces solo ve bit.ly/xxx
.
Sin embargo, existe una segunda forma de ataque mucho más peligrosa conocida como stored XSS o persistent XSS, donde el atacante logra guardar el código malicioso en el servidor para que se inserte automáticamente en algunas páginas.
Un ejemplo son las páginas donde los usuarios escriben comentarios. Un atacante envía una publicación que contiene código y este se guarda en el servidor. Si las páginas no están suficientemente aseguradas, se ejecutará en el navegador de cada visitante.
Podría parecer que el núcleo del ataque consiste en introducir la cadena <script>
en la página. En
realidad, hay muchas formas de
insertar JavaScript. Mostraremos, por ejemplo, la inserción mediante un atributo HTML. Supongamos una galería de fotos donde
se pueden insertar descripciones a las imágenes, que se imprimen en el atributo alt
:
echo '<img src="' . $imageFile . '" alt="' . $imageAlt . '">';
Al atacante le basta con insertar como descripción una cadena hábilmente construida Ahora forma parte de la página un atributo Cualquier intento de detectar ataques mediante una blacklist, como bloquear la cadena Principalmente se trata de reemplazar todos los caracteres con un significado especial por otras secuencias correspondientes,
lo que se llama coloquialmente escape (el primer carácter de la secuencia se llama carácter de escape, de ahí el
nombre). Por ejemplo, en el texto HTML, el carácter Es muy importante distinguir el contexto en el que imprimimos los datos. Porque en diferentes contextos, las cadenas se
sanean de manera diferente. En diferentes contextos, diferentes caracteres tienen un significado especial. Por ejemplo, el escape
difiere en el texto HTML, en los atributos HTML, dentro de algunos elementos especiales, etc. Lo discutiremos en detalle en un
momento. El saneamiento es mejor realizarlo directamente al imprimir la cadena en la página, asegurando así que realmente se realice y
se realice exactamente una vez. Lo mejor es si el saneamiento lo realiza automáticamente el propio sistema de plantillas.
Porque si el saneamiento no se realiza automáticamente, el programador puede olvidarlo. Y una omisión significa que el sitio web
es vulnerable. Sin embargo, XSS no solo afecta a la impresión de datos en las plantillas, sino también a otras partes de la aplicación que
deben manejar correctamente los datos no confiables. Por ejemplo, es necesario que el JavaScript en su aplicación no use
La defensa ideal en 3 puntos: ¿Qué se entiende exactamente por la palabra contexto? Es un lugar en el documento con sus propias reglas para el saneamiento
de los datos impresos. Depende del tipo de documento (HTML, XML, CSS, JavaScript, texto plano, …) y puede diferir en sus partes
específicas. Por ejemplo, en un documento HTML hay muchos lugares (contextos) donde se aplican reglas muy diferentes. Quizás se
sorprenda de cuántos hay. Aquí tenemos los primeros cuatro: El contexto predeterminado y básico de una página HTML es el texto HTML. ¿Qué reglas se aplican aquí? Los caracteres
El segundo contexto más común es el valor de un atributo HTML. Se diferencia del texto en que aquí tienen un significado
especial las comillas Quizás le sorprenda, pero se aplican reglas especiales dentro de los elementos Es interesante dentro de los comentarios HTML. Aquí, el escape no se realiza utilizando entidades HTML. De hecho, ninguna
especificación indica cómo se debe escapar en los comentarios. Solo es necesario seguir unas reglas curiosas y evitar ciertas combinaciones de
caracteres en ellos. Los contextos también pueden anidarse, lo que ocurre cuando insertamos JavaScript o CSS en HTML. Esto se puede hacer de dos
maneras diferentes, con un elemento y con un atributo: Dos caminos y dos formas diferentes de escapar datos. Dentro del elemento Por el contrario, en los atributos Y, por supuesto, dentro del JavaScript o CSS anidado se aplican las reglas de escape de estos lenguajes. Por lo tanto, una
cadena en un atributo, por ejemplo Uff… Como puede ver, HTML es un documento muy complejo donde se anidan contextos, y sin ser consciente de dónde exactamente
estoy imprimiendo los datos (es decir, en qué contexto), no se puede decir cómo hacerlo correctamente. Tomemos la cadena Si la imprime en texto HTML, en este caso particular no es necesario realizar ningún reemplazo, porque la cadena no contiene
ningún carácter con significado especial. La situación cambia si la imprime dentro de un atributo HTML delimitado por comillas
simples. En ese caso, es necesario escapar las comillas a entidades HTML: Esto fue simple. Una situación mucho más interesante surge al anidar contextos, por ejemplo, si la cadena forma parte de
JavaScript. Primero, la imprimimos en el propio JavaScript. Es decir, la envolvemos en comillas y, al mismo tiempo, escapamos las comillas
contenidas en ella con el carácter También podemos añadir una llamada a alguna función para que el código haga algo: Si insertamos este código en un documento HTML usando Sin embargo, si quisiéramos insertarlo en un atributo HTML, aún debemos escapar las comillas a entidades HTML: Pero el contexto anidado no tiene por qué ser solo JS o CSS. Comúnmente también es una URL. Los parámetros en una URL se
escapan convirtiendo los caracteres con significado especial en secuencias que comienzan con Y cuando imprimimos esta cadena en un atributo, aún aplicamos el escape según este contexto y reemplazamos Si ha leído hasta aquí, felicidades, ha sido exhaustivo. Ahora tiene una buena idea de qué son los contextos y el escape. Y
no tiene que preocuparse de que sea complicado. Latte hace esto por usted automáticamente. Hemos mostrado cómo escapar correctamente en un documento HTML y cuán fundamental es el conocimiento del contexto, es decir,
el lugar donde imprimimos los datos. En otras palabras, cómo funciona el escape sensible al contexto. Aunque es un requisito
previo necesario para una defensa funcional contra XSS, Latte es el único sistema de plantillas para PHP que puede
hacer esto. ¿Cómo es posible, cuando todos los sistemas hoy en día afirman tener escape automático? El escape automático sin
conocimiento del contexto es un poco bullshit, que crea una falsa impresión de seguridad. Los sistemas de plantillas, como Twig, Laravel Blade y otros, no ven ninguna estructura HTML en la plantilla. Por lo tanto,
tampoco ven los contextos. En comparación con Latte, son ciegos e ingenuos. Solo procesan sus propias etiquetas, todo lo demás
es para ellos un flujo de caracteres irrelevante: Los sistemas ingenuos solo convierten mecánicamente los caracteres Latte ve la plantilla igual que usted. Entiende HTML, XML, reconoce etiquetas, atributos, etc. Y gracias a esto, distingue los
contextos individuales y sanea los datos según ellos. Ofrece así una protección realmente eficaz contra la vulnerabilidad
crítica Cross-site Scripting. A la izquierda ve la plantilla en Latte, a la derecha está el código HTML generado. La variable ¡No es genial! Latte realiza el escape sensible al contexto automáticamente, por lo que el programador: Estos ni siquiera son todos los contextos que Latte distingue al imprimir y para los cuales adapta el saneamiento de datos.
Ahora repasaremos otros casos interesantes. En varios ejemplos prácticos, mostraremos cuán importante es la distinción de contextos y por qué los sistemas de
plantillas ingenuos no proporcionan una protección suficiente contra XSS, a diferencia de Latte. Como representante de un sistema
ingenuo, usaremos Twig en las demostraciones, pero lo mismo se aplica a otros sistemas. Intentaremos inyectar código malicioso en la página mediante un atributo HTML, como mostramos anteriormente. Supongamos una plantilla en Twig que renderiza una
imagen: Observe que no hay comillas alrededor de los valores de los atributos. El codificador podría haberlas olvidado, lo que
simplemente sucede. Por ejemplo, en React, el código se escribe así, sin comillas, y un codificador que alterna lenguajes puede
olvidarlas fácilmente. Un atacante inserta como descripción de la imagen una cadena hábilmente construida ¡Y se ha creado un agujero de seguridad! Ahora forma parte de la página un atributo Ahora veamos cómo Latte maneja la misma plantilla: Latte ve la plantilla igual que usted. A diferencia de Twig, entiende HTML y sabe que la variable se imprime como el valor de
un atributo que no está entre comillas. Por eso las añade. Cuando un atacante inserta la misma descripción, el código
resultante se verá así: Latte previno exitosamente el XSS. Gracias al escape sensible al contexto, es posible usar variables PHP de forma completamente nativa dentro de JavaScript. Si la variable Latte comprueba automáticamente si la variable utilizada en los atributos Imprime: La comprobación se puede desactivar con el filtro nocheck. Latte no es una protección completamente completa contra XSS para toda la aplicación. No nos gustaría que dejara de pensar
en la seguridad al usar Latte. El objetivo de Latte es asegurar que un atacante no pueda modificar la estructura de la página,
falsificar elementos o atributos HTML. Pero no controla la corrección del contenido de los datos impresos. Ni la corrección del
comportamiento de JavaScript. Esto ya está fuera de la competencia del sistema de plantillas. La verificación de la corrección
de los datos, especialmente los insertados por el usuario y, por lo tanto, no confiables, es una tarea importante del
programador." language-latte">
<img src="photo0145.webp" alt=""
onload
falsificado. El navegador ejecuta el código contenido en él
inmediatamente después de descargar la imagen. ¡Hackeado!¿Cómo defenderse de XSS?
<script>
, etc., es
insuficiente. La base de una defensa funcional es la sanitización consistente de todos los datos impresos dentro de la
página.<
tiene un significado especial, que si no debe interpretarse
como el inicio de una etiqueta, debemos reemplazarlo por una secuencia visualmente correspondiente, la llamada entidad HTML
<
. Y el navegador imprime el signo menor que.innerHTML
en conexión con ellos, sino solo innerText
o textContent
. Se debe prestar
especial atención a las funciones que evalúan cadenas como JavaScript, que es eval()
, pero también
setTimeout()
, o el uso de la función setAttribute()
con atributos de evento como onload
,
etc. Pero esto ya está fuera del área cubierta por las plantillas.
Escape sensible al contexto
<p>#texto</p>
<img src="#atributo">
<textarea>#rawtext</textarea>
<!-- #comentario -->
<
y &
tienen un significado especial, ya que representan el inicio de una etiqueta o entidad,
por lo que debemos escaparlos reemplazándolos por una entidad HTML (<
por <
&
por &
)."
o '
, que delimitan el atributo. Deben escribirse como una entidad para que no se
entiendan como el final del atributo. Por el contrario, en el atributo se puede usar de forma segura el carácter
<
, porque aquí no tiene ningún significado especial, aquí no puede entenderse como el inicio de una etiqueta
o comentario. Pero cuidado, en HTML también se pueden escribir valores de atributos sin comillas, en cuyo caso toda una serie de
caracteres tienen un significado especial, por lo que se trata de otro contexto independiente.<textarea>
y
<title>
, donde el carácter <
no necesita (pero puede) escaparse si no va seguido de
/
. Pero esto es más bien una curiosidad.<script>#js-elemento</script>
<img
<style>#css-elemento</style>
<p style="#css-atributo"></p>
<script>
y
<style>
, al igual que en el caso de los comentarios HTML, no se realiza el escape mediante entidades HTML. Al
imprimir datos dentro de estos elementos, es necesario seguir una única regla: el texto no debe contener la secuencia
</script
resp. </style
.style
y on***
se escapa mediante entidades HTML.onload
, se escapa primero según las reglas de JS y luego según las reglas del
atributo HTML.¿Quiere un ejemplo?
Rock'n'Roll
.<div title='Rock'n'Roll'></div>
\
:'Rock\'n\'Roll'
alert('Rock\'n\'Roll');
<script>
, no es necesario modificar nada más, porque
no contiene la secuencia prohibida </script
:<script> alert('Rock\'n\'Roll'); </script>
<div
%
. Ejemplo:https://example.org/?a=Jazz&b=Rock%27n%27Roll
&
por &
:<a href="https://example.org/?a=Jazz&b=Rock%27n%27Roll">
Latte vs sistemas ingenuos
░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░
░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░
- en texto: <span>{{ foo }}</span>
- en etiqueta: <span {{ foo }} ></span>
- en atributo: <span title='{{ foo }}'></span>
- en atributo sin comillas: <span title={{ foo }}></span>
- en atributo que contiene URL: <a href="{{ foo }}"></a>
- en atributo que contiene JavaScript: <img foo }}">
- en atributo que contiene CSS: <span style="{{ foo }}"></span>
- en JavaScript: <script>var = {{ foo }}</script>
- en CSS: <style>body { content: {{ foo }}; }</style>
- en comentario: <!-- {{ foo }} -->
< > & ' "
en entidades HTML, lo cual
es un método de escape válido en la mayoría de los casos de uso, pero no siempre. Por lo tanto, no pueden detectar ni prevenir
el origen de varios agujeros de seguridad, como mostraremos a continuación.░░░░░░░░░░░<span>{$foo}</span>
░░░░░░░░░░<span {$foo} ></span>
░░░░░░░░░░░░░░<span title='{$foo}'></span>
░░░░░░░░░░░░░░░░░░░░░░░░░░░<span title={$foo}></span>
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░<a href="{$foo}"></a>
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░<img
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░<span style="{$foo}"></span>
░░░░░░░░░░░░░░░░░<script>░░░░░░{$foo}</script>
░░░░░░░░░<style>░░░░░░░░░░░░░░░░{$foo}░░░</style>
░░░░░░░░░░░░░░░<!--░{$foo}░-->
- en texto: <span>{$foo}</span>
- en etiqueta: <span {$foo} ></span>
- en atributo: <span title='{$foo}'></span>
- en atributo sin comillas: <span title={$foo}></span>
- en atributo que contiene URL: <a href="{$foo}"></a>
- en atributo que contiene JavaScript: <img
- en atributo que contiene CSS: <span style="{$foo}"></span>
- en JavaScript: <script>var = {$foo}</script>
- en CSS: <style>body { content: {$foo}; }</style>
- en comentario: <!-- {$foo} -->
Demostración en vivo
$text
se
imprime varias veces aquí y cada vez en un contexto ligeramente diferente. Y, por lo tanto, también escapada de forma
ligeramente diferente. Puede editar el código de la plantilla usted mismo, por ejemplo, cambiar el contenido de la variable, etc.
Pruébelo:
Cómo hackear sistemas ingenuos
Vulnerabilidad por atributo
<img src={{ imageFile }} alt={{ imageAlt }}>
foo 'Hacked!')
.
Ya sabemos que Twig no puede saber si la variable se imprime en el flujo de texto HTML, dentro de un atributo, comentario HTML,
etc., en resumen, no distingue contextos. Y solo convierte mecánicamente los caracteres < > & ' "
en
entidades HTML. Así que el código resultante se verá así:<img src=photo0145.webp alt=foo
onload
falsificado y el navegador lo ejecuta inmediatamente después
de descargar la imagen.<img src={$imageFile} alt={$imageAlt}>
<img src="photo0145.webp" alt="foo
Impresión de variable en JavaScript
<p
<script>var movie = {$movie};</script>
$movie
contiene la cadena 'Amarcord & 8 1/2'
, se generará la siguiente salida.
Observe que dentro de HTML se usa un escape diferente al que se usa dentro de JavaScript y aún otro diferente en el atributo
onclick
:<p & 8 1\/2")">Amarcord & 8 1/2</p>
<script>var movie = "Amarcord & 8 1\/2";</script>
Comprobación de enlaces
src
o href
contiene una
URL web (es decir, protocolo HTTP) y evita la impresión de enlaces que puedan suponer un riesgo de seguridad.{var $link = 'javascript:attack()'}
<a href={$link}>haz clic</a>
<a href="">haz clic</a>
Límites de Latte