Hola, para dedicarse a la seguridad de manera profesional no solamente basta con saber encontrar un XSS sino también es necesario saber porque se produce y como darle una solución, ya que en muchas ocasiones nos toca hablar con el dueño de la aplicación WEB y nos pide consejos y estos no pueden estar fundados en la ignorancia. Por eso quiero comentarles como aconsejar a alguien sobre como solucionar un XSS y como darle su importancia.
Estructura
Cuando encuentras un XSS es importante saber que el dueño de la aplicación no es el experto en seguridad sino tu mismo, por lo cual el dueño puede que entienda menos de la mitad de lo que le digas, por eso es importante ser muy claro y estructurado al momento de detallar un XSS:
Generalmente en el resumen del hallazgo debes incluir el tipo de vulnerabilidad, normalmente categorizado junto al nivel de riesgo y esto lo puedes obtener a traves de diferentes estándares tales como la CVSS ( https://nvd.nist.gov/vuln-metrics/cvss ) y la CWE ( https://nvd.nist.gov/vuln/categories ) ya que será tu palabra contra el dueño de la aplicación, por lo cual siempre es buena idea apegarse a los estándares.
Recomendaciones
Acá va el problema: el cliente tiene un sitio WEB que cuenta con una vulnerabilidad de tipo XSS Reflejado ( CWE 80 https://cwe.mitre.org/data/definitions/80.html ) y el cliente necesita una recomendación. He escuchado a muchas personas recomendar lo siguiente de manera equivocada:
Pero, ¿Porqué están errados?: Para comenzar, impedir o filtrar el ingreso de caracteres especiales en los campos es una recomendación demasiado genérica y va a depender del tamaño y de la arquitectura de la aplicación. Como podemos observar, el CWE es 80 ( https://cwe.mitre.org/data/definitions/80.html ) y no 79 ( https://cwe.mitre.org/data/definitions/79.html ), hay una gran diferencia ya que el CWE-79 indica que es un problema de caracteres de entrada y el CWE-80 indica que es un problema de salida.
Cuando ingresas contenido HTML en un campo y este se refleja en el código fuente resolutante de la respuesta del sitio WEB (response) entonces tenemos un problema de datos de salida y no de entrada, pero ¿porqué no filtrar los caracteres en la entrada para evitar tener problemas en los datos de salida?: porque no todas las aplicaciones funcionan igual y esto dependerá de su arquitectura, por ejemplo: Supongamos que nuestro sistema tiene una versión WEB y una version movil, si filtro los caracteres especiales en la entrada de datos para convertirlos en entidades HTML para que se guarde en la base de datos entonces cuando la aplicación móvil vaya a solicitar estos datos vendrán con escapes HTML pero las aplicaciones móviles nativas no usan HTML asi que se verán caracteres extraños basura en el contenido. No todo lo que esté en una base de datos significa que se desplegará en una aplicación WEB o móvil o de escritorio, sea nativa o híbrida, por esto es muy importante tener en mente el siguiente concepto:
Ningún dato de entrada debe ser filtrado a menos que sea un filtro de datos por concepto de definición de capa de negocio y datos, o sea, si mi valor a ingresar debe ser siempre un número positivo entonces eso si se puede filtrar, si el nombre debe tener una longitud máxima entonces esto debe ser controlado y filtrado, pero si no hay nada que impida que alguien ingrese un caracter especial en una caja de descripción entonces para que filtrar, por lo contrario, un buen desarrollo debe poder funcionar de manera segura con cualquier tipo de caracter especial, por ejemplo, este foro permite caracteres especiales para el nombre de usuario o la caja del contenido del post y no quiere decir que se deban eliminar todos los caracteres especiales para hacerlo mas seguro.
Si eres un profesional y encontraste un buen XSS pero aconsejas filtrar los campos de entrada te puede hacer quedar como ignorante en el campo del desarrollo de la solución y puede que pierdan la confianza en ti y sin que te lo digan.
¿Porqué no es bueno recomendar que se solucione del lado del WAF creando reglas que impidan los XSS?: Porque un WAF es solamente un agente mitigante pero no un solucionador de problemas, puede que para alguien sea mas dificil llegar al XSS pero el agujero de seguridad con el riesgo aun van a seguir existiendo, los riesgos se reducen pero no se eliminan, por lo cual no se considera una solución sino una mitigación y esto es parte de dos planes de trabajo: La mitigación al corto plazo y la solución al mediano/largo plazo.
¿Cual es la mejor recomendación?
Crear un plan de mitigación al corto plazo sonde se mitigue a traves de diferentes agentes dependiendo los que tenga el dueño de la aplicación WEB (Web Application Firewall), por ejemplo, el WAF para mitigar los ataques WEB, un Firewall para evitar la llegada de paquetes maliciosos a la infraestructura, evitar pivoting y esas cosas.
Crear un plan de solución al mediano/largo plazo que incluya corrección de código fuente del lado del desarrollo y acá entra algo de conocimiento de Arquitectura aplicativa:
En el caso de ser una aplicatión WEB monolítica ( https://es.wikipedia.org/wiki/Aplicaci%C3%B3n_monol%C3%ADtica ) se debe recomendar crear un escapado de datos de salida en el punto vulnerable y ojo, existen varias maneras de hacerlo según sea el caso ya que va a depender en que lugar del código resultante se realiza la impresión de la variable vulnerable con el XSS, pero antes de explicar esto es necesario entender otra cosa: ¿Qué es una secuencia de escape de caracteres?.
Pues bien, digamos que tenemos un String entre comillas dobles así:
¿Que sucede si el valor de esta palabra contiene una comilla doble?
Pues, lo que sucede es que se rompe la cadena de texto ya que las comillas dobles declaran el valor, todo lo que esté dentro es parte del texto, pero cuando le pones una comilla doble lo estás interrumpiendo indicando que se está cerrando la declaración, o sea, que la palabra ya ha terminado, entonces, que sucede con el texto restante, en este caso podrías hacer dos textos o inyectar algo entremedio, por ejemplo usando la frase: Palabra" + algo + "acá
Y bueno, se produce una inyección de código malicioso entre una declaración de tipo texto. Esto mismo sucede en muchos lugares, como verán, no estoy hablando de un lenguaje de programación en particular ya que cada lenguaje utiliza su propia sintaxis y su propia manera de escapar los caracteres.
Entonces, para evitar que suceda una inyección se creó algo universal llamado "Secuencia de escape de caracteres" los cuales se añaden al caracter especial para que este forme parte de la cadena de texto y no que lo rompa, por ejemplo, en javascript es el backslash "\", por ejemplo:
Esto le indica al motor interpretador de javascript que el caracter que sigue después del backslash será parte del texto en ves de interpretarse como un quiebre del string. En batch el caracter de escape es el "^" y el "%%", en bash es el backslash, en SQL es el backslash, en javascript es el backslash y en HTML es la Entidad HTML! ( https://dev.w3.org/html5/html-author/charref ).
Asi que, en nuestro XSS pueden haber dos situaciones muy comunes, que el código esté imprimiendose dentro de una etiqueta javascript o dentro del mismo HTML y en cambos casos se necesitarán usar diferentes tipos de secuencia de escape, por ejemplo:
Donde $valor_escapado será igual al texto de salida escapado, esto quiere decir que cada caracter especial podrá ser reemplazado por valores de escape unicode o hexadecimal (definido por el estandar de javascript ES6), por ejemplo:
Pero, ¿con que función se escapa?: Eso dependerá del lenguaje de programación o del framework que estés utilizando, cada lenguaje o framework tiene distintas maneras de aplicar este filtrado y de ahi la importancia de entender de desarrollo de software, comprender lo que estás analizando, mas allá de ver un simple XSS.
Ahora vamos con el código HTML, su secuencia de escape está definido por el estandar mismo de la W3C: https://dev.w3.org/html5/html-author/charref , por lo cual enocntramos las siguientes equivalencias:
Entonces, si el problema estuviera en código HTML debería ser escapado de la siguiente manera:
Hay personas que aplican un filtro html para todo el código, pero esto no escapa contenido javascript, por ejemplo, en javascrpt puedes inyectar código con un backslash, pero HTML no tiene un escape para backslash porque para HTML ese caracter no es peligroso pero para javascript si, lo mismo sucede cuando intentas romper una secuencia con un salto de línea, por otro lado un tag html en javascript no le pasa nada pero a HTML si. Asi que, cada lenguaje en cada trozo de código debe llevar su propia secuencia de escape de caracteres o podrás terminar abriendo una vulnerabilidad donde antes no lo había.
Pero, en aplicaciones con separación de capas (no monolíoticas) hay que indicar que esto se debe hacer del lado del código que expone la información, o sea la capa de presentación ( https://es.wikipedia.org/wiki/Programaci%C3%B3n_por_capas ), y ojo, no se les vaya a ocurrir decirle a alguien que debe escapar absolutamente todo, ya que va a depender de muchas cosas, por ejemplo, un servicio REST que expone datos a traves de código Json tiene su propia secuencia de escape de caracteres y esto generalmente se maneja de manera automática dependiendo del lenguaje y framework de programación.
Asi que, ahora si estamos medianamente listos para dar una buena recomendación:
Como verán, la recomendación es corta, pero va al grano y con todo el tecnicismo que necesita el dueño de la aplicación, es muy diferente a decir "hay que filtrar los datos de entrada" y ya.
A modo de conocimiento general, en algunas ocasiones, a demás de recomendar solucionar el XSS también se le recomienda sobre migrar de tecnología a una mas segura, por ejemplo, si el desarrollo WEB no utiliza un framework, sino que está hecho a mano con archivos php o jsp tirados en el servidor, entonces se le dice que al largo plazo va a ser mejor migrar a un framework de desarrollo mas seguro como Codeigniter, Laravel o Spring boot ya que estos frameworks manejan de manera automática los problemas de tipo inyección como los XSS o Inyección SQL debido a que nunca programas código a mano, sino que te apoyas en clases y templates, donde las consultas SQL ya no se realizan directamente sino que usas clases y el framework traduce esas clases a consultas SQL parametrizadas y ya no programas variables directamente sobre HTML sino que programas sobre un sistema de plantillas que traduce tu código a código escapado de manera automática como es el caso de Jinja para Django de Python, Thymeleaf para Spring Boot de Java y Razor para MVC de .NET.
Como ya habrán notado, esto no solo aplica para los XSS sino para cualquier tipo de vulnerabilidad de tipo inyección, esto incluye Inyecciones SQL e Inyección de Comandos, Inyección XML, Inyección LDAP y similares.
Recomendaciones adicionales
Cuando el dueño de una aplicación pregunta como solucionar un problema, por lo general no se lo pregunta a la persona de al lado, sino que se lo pregunta al que encontró el problema y estará esperando a que entiendas como ayudarlo pero si no sabes en que lenguaje está hecho o no tienes los fundamentos básicos de desarrollo entonces va a ser dificil dar una recomendación certera, incluso puede que estés dando una recomendación erronea y que en el futuro tengan problemas aun mas graves y les hagas perder el tiempo, me ha tocado ver esos casos y no es muy agradable decirle a una persona que lo que hizo a modo de corrección realmente no sirve y que va a tener que volver a programar sus soluciones.
Espero les pueda ayudar un poco a ampliar el vocabulario se seguridad informática. Saludos.
Estructura
Cuando encuentras un XSS es importante saber que el dueño de la aplicación no es el experto en seguridad sino tu mismo, por lo cual el dueño puede que entienda menos de la mitad de lo que le digas, por eso es importante ser muy claro y estructurado al momento de detallar un XSS:
- Resumen del hallazgo
- Cómo replicar el problema (PoC) (ojo, no te están pidiendo mostrar un alert, sino replicar un impacto real)
- Porqué es un riesgo de seguridad (impacto)
- Cuales son las mejores recomendciones
Generalmente en el resumen del hallazgo debes incluir el tipo de vulnerabilidad, normalmente categorizado junto al nivel de riesgo y esto lo puedes obtener a traves de diferentes estándares tales como la CVSS ( https://nvd.nist.gov/vuln-metrics/cvss ) y la CWE ( https://nvd.nist.gov/vuln/categories ) ya que será tu palabra contra el dueño de la aplicación, por lo cual siempre es buena idea apegarse a los estándares.
Recomendaciones
Acá va el problema: el cliente tiene un sitio WEB que cuenta con una vulnerabilidad de tipo XSS Reflejado ( CWE 80 https://cwe.mitre.org/data/definitions/80.html ) y el cliente necesita una recomendación. He escuchado a muchas personas recomendar lo siguiente de manera equivocada:
- Impedir el ingreso de caracteres especiales en los campos
- Filtrar los caracteres especiales evitando que en el input se ingresen etiquetas HTML
- Crear una regla en el WAF para evitar los XSS
Pero, ¿Porqué están errados?: Para comenzar, impedir o filtrar el ingreso de caracteres especiales en los campos es una recomendación demasiado genérica y va a depender del tamaño y de la arquitectura de la aplicación. Como podemos observar, el CWE es 80 ( https://cwe.mitre.org/data/definitions/80.html ) y no 79 ( https://cwe.mitre.org/data/definitions/79.html ), hay una gran diferencia ya que el CWE-79 indica que es un problema de caracteres de entrada y el CWE-80 indica que es un problema de salida.
Cuando ingresas contenido HTML en un campo y este se refleja en el código fuente resolutante de la respuesta del sitio WEB (response) entonces tenemos un problema de datos de salida y no de entrada, pero ¿porqué no filtrar los caracteres en la entrada para evitar tener problemas en los datos de salida?: porque no todas las aplicaciones funcionan igual y esto dependerá de su arquitectura, por ejemplo: Supongamos que nuestro sistema tiene una versión WEB y una version movil, si filtro los caracteres especiales en la entrada de datos para convertirlos en entidades HTML para que se guarde en la base de datos entonces cuando la aplicación móvil vaya a solicitar estos datos vendrán con escapes HTML pero las aplicaciones móviles nativas no usan HTML asi que se verán caracteres extraños basura en el contenido. No todo lo que esté en una base de datos significa que se desplegará en una aplicación WEB o móvil o de escritorio, sea nativa o híbrida, por esto es muy importante tener en mente el siguiente concepto:
Ningún dato de entrada debe ser filtrado a menos que sea un filtro de datos por concepto de definición de capa de negocio y datos, o sea, si mi valor a ingresar debe ser siempre un número positivo entonces eso si se puede filtrar, si el nombre debe tener una longitud máxima entonces esto debe ser controlado y filtrado, pero si no hay nada que impida que alguien ingrese un caracter especial en una caja de descripción entonces para que filtrar, por lo contrario, un buen desarrollo debe poder funcionar de manera segura con cualquier tipo de caracter especial, por ejemplo, este foro permite caracteres especiales para el nombre de usuario o la caja del contenido del post y no quiere decir que se deban eliminar todos los caracteres especiales para hacerlo mas seguro.
Si eres un profesional y encontraste un buen XSS pero aconsejas filtrar los campos de entrada te puede hacer quedar como ignorante en el campo del desarrollo de la solución y puede que pierdan la confianza en ti y sin que te lo digan.
¿Porqué no es bueno recomendar que se solucione del lado del WAF creando reglas que impidan los XSS?: Porque un WAF es solamente un agente mitigante pero no un solucionador de problemas, puede que para alguien sea mas dificil llegar al XSS pero el agujero de seguridad con el riesgo aun van a seguir existiendo, los riesgos se reducen pero no se eliminan, por lo cual no se considera una solución sino una mitigación y esto es parte de dos planes de trabajo: La mitigación al corto plazo y la solución al mediano/largo plazo.
¿Cual es la mejor recomendación?
Crear un plan de mitigación al corto plazo sonde se mitigue a traves de diferentes agentes dependiendo los que tenga el dueño de la aplicación WEB (Web Application Firewall), por ejemplo, el WAF para mitigar los ataques WEB, un Firewall para evitar la llegada de paquetes maliciosos a la infraestructura, evitar pivoting y esas cosas.
Crear un plan de solución al mediano/largo plazo que incluya corrección de código fuente del lado del desarrollo y acá entra algo de conocimiento de Arquitectura aplicativa:
En el caso de ser una aplicatión WEB monolítica ( https://es.wikipedia.org/wiki/Aplicaci%C3%B3n_monol%C3%ADtica ) se debe recomendar crear un escapado de datos de salida en el punto vulnerable y ojo, existen varias maneras de hacerlo según sea el caso ya que va a depender en que lugar del código resultante se realiza la impresión de la variable vulnerable con el XSS, pero antes de explicar esto es necesario entender otra cosa: ¿Qué es una secuencia de escape de caracteres?.
Pues bien, digamos que tenemos un String entre comillas dobles así:
Código [Seleccionar]
"Palabra acá"
¿Que sucede si el valor de esta palabra contiene una comilla doble?
Código [Seleccionar]
"Palabra " acá"
Pues, lo que sucede es que se rompe la cadena de texto ya que las comillas dobles declaran el valor, todo lo que esté dentro es parte del texto, pero cuando le pones una comilla doble lo estás interrumpiendo indicando que se está cerrando la declaración, o sea, que la palabra ya ha terminado, entonces, que sucede con el texto restante, en este caso podrías hacer dos textos o inyectar algo entremedio, por ejemplo usando la frase: Palabra" + algo + "acá
Código [Seleccionar]
"Palabra" + algo + "acá"
Y bueno, se produce una inyección de código malicioso entre una declaración de tipo texto. Esto mismo sucede en muchos lugares, como verán, no estoy hablando de un lenguaje de programación en particular ya que cada lenguaje utiliza su propia sintaxis y su propia manera de escapar los caracteres.
Entonces, para evitar que suceda una inyección se creó algo universal llamado "Secuencia de escape de caracteres" los cuales se añaden al caracter especial para que este forme parte de la cadena de texto y no que lo rompa, por ejemplo, en javascript es el backslash "\", por ejemplo:
Código [Seleccionar]
"Palabra \" acá"
Esto le indica al motor interpretador de javascript que el caracter que sigue después del backslash será parte del texto en ves de interpretarse como un quiebre del string. En batch el caracter de escape es el "^" y el "%%", en bash es el backslash, en SQL es el backslash, en javascript es el backslash y en HTML es la Entidad HTML! ( https://dev.w3.org/html5/html-author/charref ).
Asi que, en nuestro XSS pueden haber dos situaciones muy comunes, que el código esté imprimiendose dentro de una etiqueta javascript o dentro del mismo HTML y en cambos casos se necesitarán usar diferentes tipos de secuencia de escape, por ejemplo:
Código [Seleccionar]
<script>var x = '$valor_escapado';</script>
Donde $valor_escapado será igual al texto de salida escapado, esto quiere decir que cada caracter especial podrá ser reemplazado por valores de escape unicode o hexadecimal (definido por el estandar de javascript ES6), por ejemplo:
Código [Seleccionar]
"Palabra \x22 ac\xc3\xa1"
Pero, ¿con que función se escapa?: Eso dependerá del lenguaje de programación o del framework que estés utilizando, cada lenguaje o framework tiene distintas maneras de aplicar este filtrado y de ahi la importancia de entender de desarrollo de software, comprender lo que estás analizando, mas allá de ver un simple XSS.
Ahora vamos con el código HTML, su secuencia de escape está definido por el estandar mismo de la W3C: https://dev.w3.org/html5/html-author/charref , por lo cual enocntramos las siguientes equivalencias:
Valor/ | Entidad que lo reemplaza |
< | < |
> | > |
' | ' |
" | " |
Entonces, si el problema estuviera en código HTML debería ser escapado de la siguiente manera:
Código [Seleccionar]
<input type="text" value="Palabra " acá" />
Hay personas que aplican un filtro html para todo el código, pero esto no escapa contenido javascript, por ejemplo, en javascrpt puedes inyectar código con un backslash, pero HTML no tiene un escape para backslash porque para HTML ese caracter no es peligroso pero para javascript si, lo mismo sucede cuando intentas romper una secuencia con un salto de línea, por otro lado un tag html en javascript no le pasa nada pero a HTML si. Asi que, cada lenguaje en cada trozo de código debe llevar su propia secuencia de escape de caracteres o podrás terminar abriendo una vulnerabilidad donde antes no lo había.
Pero, en aplicaciones con separación de capas (no monolíoticas) hay que indicar que esto se debe hacer del lado del código que expone la información, o sea la capa de presentación ( https://es.wikipedia.org/wiki/Programaci%C3%B3n_por_capas ), y ojo, no se les vaya a ocurrir decirle a alguien que debe escapar absolutamente todo, ya que va a depender de muchas cosas, por ejemplo, un servicio REST que expone datos a traves de código Json tiene su propia secuencia de escape de caracteres y esto generalmente se maneja de manera automática dependiendo del lenguaje y framework de programación.
Asi que, ahora si estamos medianamente listos para dar una buena recomendación:
CitarA modo de solución al mediano/largo plazo se recomienda aplicar la secuencia de escape de caracteres utilizando el estandar de Entidades HTML declarado por W3C en capa de presentación, únicamente sobre el parámetro afectado.
Como verán, la recomendación es corta, pero va al grano y con todo el tecnicismo que necesita el dueño de la aplicación, es muy diferente a decir "hay que filtrar los datos de entrada" y ya.
A modo de conocimiento general, en algunas ocasiones, a demás de recomendar solucionar el XSS también se le recomienda sobre migrar de tecnología a una mas segura, por ejemplo, si el desarrollo WEB no utiliza un framework, sino que está hecho a mano con archivos php o jsp tirados en el servidor, entonces se le dice que al largo plazo va a ser mejor migrar a un framework de desarrollo mas seguro como Codeigniter, Laravel o Spring boot ya que estos frameworks manejan de manera automática los problemas de tipo inyección como los XSS o Inyección SQL debido a que nunca programas código a mano, sino que te apoyas en clases y templates, donde las consultas SQL ya no se realizan directamente sino que usas clases y el framework traduce esas clases a consultas SQL parametrizadas y ya no programas variables directamente sobre HTML sino que programas sobre un sistema de plantillas que traduce tu código a código escapado de manera automática como es el caso de Jinja para Django de Python, Thymeleaf para Spring Boot de Java y Razor para MVC de .NET.
Como ya habrán notado, esto no solo aplica para los XSS sino para cualquier tipo de vulnerabilidad de tipo inyección, esto incluye Inyecciones SQL e Inyección de Comandos, Inyección XML, Inyección LDAP y similares.
Recomendaciones adicionales
Cuando el dueño de una aplicación pregunta como solucionar un problema, por lo general no se lo pregunta a la persona de al lado, sino que se lo pregunta al que encontró el problema y estará esperando a que entiendas como ayudarlo pero si no sabes en que lenguaje está hecho o no tienes los fundamentos básicos de desarrollo entonces va a ser dificil dar una recomendación certera, incluso puede que estés dando una recomendación erronea y que en el futuro tengan problemas aun mas graves y les hagas perder el tiempo, me ha tocado ver esos casos y no es muy agradable decirle a una persona que lo que hizo a modo de corrección realmente no sirve y que va a tener que volver a programar sus soluciones.
Espero les pueda ayudar un poco a ampliar el vocabulario se seguridad informática. Saludos.