28 oct 2024

A03:2021 – Injection

Las inyecciones son un tipo de vulnerabilidad consistente en la realización de un envío de datos no confiables mediante una petición o consulta a un intérprete, por parte de un atacante, causando una disrupción en el flujo de ejecución o el comportamiento previsto inicialmente para la aplicación. 

De esta forma, se pueden llegar a realizar acciones que van desde la extracción de información adicional sobre el sistema atacado, hasta el robo de una sesión de usuario o incluso la ejecución remota de comandos y la posterior toma de control del propio servidor web donde se aloja la aplicación.

Las inyecciones se han catalogado como las más críticas y constantes a lo largo de las sucesivas ediciones de los OWASP Top 10, debido a que pueden llegar a derivar en el control absoluto de la máquina objetivo. De hecho, han estado en el número uno de la lista durante todas las ediciones hasta la del 2021, donde bajó a la tercera posición.

Tipos de inyecciones

Como depende en gran medida, del intérprete utilizado, del lenguaje empleado o del sistema de almacenamiento empleado en el backend del servidor, hay también un importante número de inyecciones diferentes que se pueden dar, como son:

Inyecciones SQL (SQLi - SQL injections) 

Permite la manipulación no deseada de los datos existentes en bases de datos no relacionales, modificando o alterando la información de los datos, o bien permitiendo la evasión de los sistemas de autenticación y/o autorizaciones existentes. 

Contrariamente a lo que gente no experta suele opinar, este tipo de inyecciones no son una “cosa de los 90”. Hoy en día se siguen empleando inyecciones de tipo SQL (Structured Query Language), empleados en consultas o modificaciones de bases de datos relacionales. Un ejemplo claro ha sido incluso muchas veces, como se ha demostrado en una de las vulnerabilidades detectadas en el sistema TSA (Transportation Security Administration), que ha permitido ignorar la seguridad de 77 aerolíneas que usaban un sistema determinado para llegar a tener privilegios de administración con una de las inyecciones de este tipo más triviales. 

Inyecciones NoSQL (NoSQLi -NoSQL injections)

Con la aparición de las bases de datos no relacionales, como MongoDB (donde los datos quedan almacenados en BSON, un formato de datos similar a JSON), CouchDB, o Redis, el tipo de inyección cambia para ajustarse a este tipo de información, pero el resultado puede ser igual de devastador. 

Inyecciones de comandos (CI- Command Injections)

En este caso, lo que se “inyectan” son comandos no deseados en el sistema operativo subyacente. 

Inyecciones LDAP (LDAPi - Ligthweight Directory Access Protocol injections)

En este caso, la explotación de la inyección viene dada por la manipulación no deseada de consultas LDAP o Protocolo Ligero de Acceso a Directorios, para acceder o manipular recursos de red o la autenticación de usuarios que empleen este tipo de protocolo en la aplicación web. 

Inyecciones XML (XMLi - XML injection, XXE - XML eXternal Entity injection)

En este caso se manipulan las peticiones XML realizadas al servidor para incluir entidades no controladas, incluso entidades externas cargadas desde la propia máquina atacante (en el caso de XXE), para poder leer ficheros internos o incluso realizar ejecución remota de comandos. 

Inyecciones XPath

En este caso se abusará de la manipulación de consultas XPath (lenguaje XML Path), para el acceso o modificación de datos, o atributos en documentos XML. 

Uso de secuencias de comandos en sitios cruzados (XSS - CrossSite Scripting)

En este tipo de inyecciones, inicialmente se consideran inyecciones en el lado “cliente”, por afectar principalmente a la relación entra las aplicaciones web y lo que acontece en el navegador de los usuarios. 

Pero este hecho, que inicialmente podría considerarse como no especialmente relevante, no lo es tal, si se considera que una XSS de carácter reflejado puede llevar a ataques de robo de identidad o robo de sesiones de usuario o, en el caso de los XSS almacenados, llevar a comprometer una infraestructura completa y no solo el defacement o desfiguración del aplicativo web. 

Además, en combinación con otro tipo de ataques (el mencionado SQLi, u otros como SSRF-Server-Side Request Forgery, CSRF o XSRF-Cross-Site Request Forgery…) pueden ser especialmente peligrosos. 

Destacan por la utilización de JavaScript (o lenguajes de scripting similares empleados en el cliente, como vbscript, aunque menos frecuente hoy en día) y HTML (HTMLi o HTML Injection). 

Inyecciones de Plantillas del lado servidor (SSTI - Server-Side Template Injection)

Con la aparición de nuevos frameworks en servidores web como PHP, Spring en Java, Flask/Django en Python, Express (Node.js), a los backends tradicionales, surgieron ciertos motores populares de procesamiento de plantillas en el servidor web, como Jinja o Jinja2 (Python), Twig (PHP), Pug/Jade (Node.js), EJS (JavaScript), Freemarker (Java), etc. 

Y con ellos, nuevos tipos de inyección abusando de estas plantillas existentes, donde, mediante su adecuada manipulación e inyección de comandos específicos, un atacante podría llegar a interactuar con todo el entorno del servidor, incluidos archivos o servicios existentes, o incluso el propio sistema operativo. 

ORM Injection (Object-Relational Mapping Injection)

Si bien el uso de una capa de abstracción empleando Orientación a Objetos es muy deseable para evitar precisamente tipos de inyección SQL o NoSQL, evitando la construcción y manipulación directa de cadenas SQL, una mala implementación de ORM puede llevar al descubrimiento de información de la base de datos subyacente, mediante el empleo de consultas dinámicas manipulando la lógica de este tipo de objetos, de forma análoga a como se haría con un SQLi. 

Se han descubierto en este sentido varias formas de inyección en ORMs populares como SQLAlchemy (para Python) o Hibernate (para Java). 

Inyecciones de código (Code Injection)

El abuso de este tipo de inyecciones depende exclusivamente del tipo de lenguaje empleado para la ejecución dinámica de código en la aplicación web. Por ejemplo, PHP (con funciones como eval()) o Python. 

Inyecciones de formato de cadena (Format String Injection)

Aquí la inyección vendría dada en las funciones de formato de cadena empleadas para la manipulación de datos de entrada de usuario, que permitirían la divulgación de información no deseada en el lado servidor, o la ejecución incluso de código arbitrario. 

Inyecciones de expresiones regulares (Regex Injection)

La explotación de este tipo de inyecciones viene generalmente dada por la laxitud en la implementación de ciertas expresiones regulares empleadas en el sistema, permitiendo su manipulación para realizar ataques de obtención de información adicional, evasión de filtros, o incluso ataques de Denegación de Servicio (DoS).

Inyecciones de cabecera HTTP (HTTP Header Injection)

La inyección en este caso viene dada en la propia cabecera HTTP de las peticiones enviadas al servidor, permitiendo otro tipo de ataques como el envenenamiento de caché (Cache Poisoning) o la manipulación de cookies empleadas en la sesión de la aplicación. 

De hecho, es habitual emplear otro tipo de inyecciones mencionadas dentro también de las cabeceras. 

¿Por qué ocurren las inyecciones?

La mayoría de las inyecciones ocurren por un motivo común, independientemente del lenguaje empleado en el backend, de la infraestructura desplegada o de la configuración del servidor, que motivarán la elección del uso de unos tipos de inyección u otras. 

El principal motivo es la falta de controles para la validación de los datos de entrada del usuario, así como también en la salida (o el procesamiento entre capas), de la información hacia el servidor.

No se debe olvidar que las peticiones iniciales pueden ser interceptadas tras pasar los filtros en el lado cliente, y manipuladas para afectar igualmente al servidor, siempre y cuando no se hayan establecido controles y validaciones especialmente en este lado.

Tampoco se deben concebir este tipo de ataques como ataques que afectan únicamente a aplicaciones web, siendo muchas de estas inyecciones descritas posibles (especialmente SQLi) en arquitecturas cliente-servidor no web (incluso donde el cliente y el servidor se encuentran en la misma máquina).

Medidas de mitigación

Como recomendaciones principales, se dan las siguientes:

  • Se debe establecer una política de confianza cero en todo lo que respecta a los inputs que una aplicación puede recibir, estableciendo una sanitización adecuada, de tal forma que se escapen caracteres no deseados (evitando en la medida de lo posible el uso de listas negras y sustituyéndolas por las funcionalidades de seguridad en cuanto a escape de caracteres dependientes de las tecnologías empleadas). 
  • Se recomienda adicionalmente, el empleo de consultas parametrizadas en el caso de acceso a servidores de bases de datos, siguiendo las recomendaciones de seguridad de cada proveedor de bases de datos, así como de la tecnología empleada. 
  • Se deben realizar las debidas abstracciones de bases de datos para evitar la construcción directa de consultas SQL mediante un uso correcto de ORM (Object-Relational Mapping). 
  • Se deben implementar, con independencia de lo anteriormente citado, un correcto control de acceso, limitando los permisos en el servidor, tanto del servicio web en ejecución, como en el acceso a la base de datos, o a los directorios específicos donde se está ejecutando la aplicación, de tal manera que se limite severamente el acceso a otras áreas del servidor. 

De esta manera, mediante un establecimiento del “principio de mínimo privilegio”, se restringirán al máximo los pasos posteriores de un posible atacante, en caso de haber obtenido éxito en la explotación de cualquiera de estos ataques. 

  • Recurrir a capas de protección adicionales, como son la ejecución de un WAF (Web Application Firewall), con unas reglas bien definidas para parar este tipo de ataques, o bloquear posibles automatizaciones en intentos de inyección. 

Eso sí, sin confiar única y ciegamente en este tipo de sistemas, que también pueden ser evadidos. 

Roberto Trigo, Analista de Ciberseguridad en Zerolynx.

21 oct 2024

A02:2021 - Cryptographic failures - Colisiones Hash


¿Qué son los fallos criptográficos?

Un fallo criptográfico ocurre cuando la protección del dato no es adecuada, independientemente de si el fallo sucede por un uso incorrecto de un algoritmo criptográfico, un fallo en la implementación del mismo, o por el uso de prácticas inseguras. Las vulnerabilidades que se dan pueden afectar a la confidencialidad e integridad de la información de una aplicación.

Los fallos criptográficos son un riesgo de seguridad ya que permiten a un posible actor malintencionado, descifrar, manipular o acceder a información que, por diseño, no debería de tener acceso.

Se puede entender la criptografía moderna como el uso de algoritmos robustos, consecuentemente considerados como seguros. Entre ellos destacan AES; RSA o SHA-256, a pesar de ello, su correcta implementación es crucial. Una incorrecta generación de claves, el uso de algoritmos obsoletos, tales como SHA1 o MD5; o la puesta en producción de generadores de números predecibles, pueden resultar en ataques contra la criptografía de la aplicación.


Uso de algoritmos obsoletos

Uno de los fallos criptográficos más comunes es el uso funciones de hash y algoritmos de cifrado obsoletos, anteriormente referenciados, SHA1 y MD5. A pesar de que fueron estándares en su época (1995 y 1992, respectivamente), hoy en día no son considerados seguros debido a los avances en las técnicas de ataque, como la búsqueda de colisiones de hash. Estos ataques permiten que un posible actor malicioso genere dos entradas diferentes que produzcan el mismo hash, lo que podría suponer una suplantación de información.

También se deben tener en cuenta los algoritmos de cifrado simétrico, entre los cuáles también existen algunos considerados como obsoletos, DES o 3DES, los cuales no proporcionan una robustez adecuada a la época en la que nos encontramos, dado que con el tiempo y los recursos suficientes se puede llegar a descifrar los datos.


Historia: MD5 y SHA1

Los algoritmos MD5 (Message Digest 5) y SHA1 (Secure Hash Algorithm 1) son funciones hash que se usaron ampliamente para verificar la integridad de los datos. Sin embargo, con el tiempo se descubrieron vulnerabilidades de alto impacto en ambos algoritmos, permitiendo a actores maliciosos llevar a cabo ataques de colisiones (entre otros), resultando en un fallo de seguridad. Como resultado, tanto MD5 como SHA1 se consideran obsoletos y no son recomendados para su uso en funcionalidad que requiera de alta seguridad.


MD5 - Message Digest 5

Algoritmo diseñado en 1991, genera un hash de 128 bits a partir de un conjunto de datos de cualquier tamaño. Originalmente utilizado para verificar la integridad de archivos e imágenes, y hashes de contraseñas.

  • Ataques de colisión: en 2004 los investigadores Xiaoyun Wang, Dengguo Feng, Xuejia Lai y Hongbo Yu; demostraron que MD5 es vulnerable a colisiones.
    • Xie, T., Liu, F., & Feng, D. (2013). Fast collision attack on MD5. Cryptology ePrint Archive.
  • Ataques de preimagen: aunque menos comunes, los ataques de preimagen son teóricamente posibles en MD5, en ellos un actor malicioso podría intentar reconstruir el mensaje original a partir del hash.
    • Sasaki, Y., & Aoki, K. (2009). Finding preimages in full MD5 faster than exhaustive search. In Advances in Cryptology-EUROCRYPT 2009: 28th Annual International Conference on the Theory and Applications of Cryptographic Techniques, Cologne, Germany, April 26-30, 2009. Proceedings 28 (pp. 134-152). Springer Berlin Heidelberg.


SHA1 - Secure Hash Algorithm 1

SHA1, desarrollado por la NSA en 1993, genera un hash de 160 bits. Se estandarizó su uso, encontrándose en protocolos como SSL/TLS y PGP. En los últimos años se ha demostrado como es vulnerable a ataques de colisión.

En 2017, Google en asociación con la Universidad de Ámsterdam, demostrator que se podrían generar colisiones en SHA1, publicando https://shattered.io/ para libre consulta pública.


Colisiones Hash


Funciones Hash

Una función hash es un algoritmo que toma una entrada de longitud variable y la convierte en una salida variable (hash o digest). Las funciones hash están diseñadas para ser unidireccionales, no siendo posible recuperar la entrada a partir de la salida, y resistentes a colisiones. Sin embargo, en determinados algoritmos ya ha quedado demostrado como esa resiliencia ante colisiones, no siempre es perfecta.


Pero, ¿qué son las colisiones de hash?

Una colisión de hash ocurre cuando dos entradas diferentes producen la misma salida o digest. Esta situación desmonta una de las propiedades esenciales de las funciones hash, la unicidad del digest.


La seguridad de sistemas como las firmas digitales, la integridad de archivos, y los certificados SSL/TLS, puede ser comprometida mediante colisiones de hash. Si un actor malicioso logra modificar el contenido de un mensaje sin que se altere su hash, puede lograr que las modificaciones pasen inadvertidas y se considere legítimo el mensaje.


Ejemplo de colisión en MD5

Para el ejemplo de colisión, se utilizan dos cadenas de texto, las cuales se diferencian en un byte. Créditos a Marc Stevens por su publicación.

  • Cadena1:

TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak 

  • Cadena 2:

TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak


TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak

|

TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak

Para poder demostrarlo, se realizar una pequeña prueba de concepto en C:

#include <stdio.h>
#include <openssl/md5.h>
#include <string.h>
int main() {
    char *input1 = "TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak";
    char *input2 = "TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak";
    unsigned char hash1[MD5_DIGEST_LENGTH];
    unsigned char hash2[MD5_DIGEST_LENGTH];
    MD5((unsigned char*)input1, strlen(input1), hash1);
    MD5((unsigned char*)input2, strlen(input2), hash2);
    for(int i = 0; i < MD5_DIGEST_LENGTH; i++) printf("%02x", hash1[i]);
    printf("\n");
    for(int i = 0; i < MD5_DIGEST_LENGTH; i++) printf("%02x", hash2[i]);
    printf("\n");
    printf(memcmp(hash1, hash2, MD5_DIGEST_LENGTH) == 0 ? "¡Colisión encontrada!\n" : "No hay colisión\n");
    return 0;
}


Y se obtiene una colisión de hash, dos cadenas alfanuméricas diferentes resultan en el mismo hash MD5. Esto llevado a un sistema de acceso en el cuál las credenciales se encuentran almacenadas mediante hash MD5, podría suponer un acceso no autorizado con una credencial distinta a la original.


¿Cómo se encuentran las colisiones de hash? → Chosen Prefix Collision

Identificar una colisión de hash es como encontrar una aguja en un pajar, la cantidad de recursos computacionales que requiere para encontrar dos cadenas (por ejemplo) arbitrarias las cuales resulten en el mismo hash, es descomunal.

Por ello se han desarrollado técnicas como Chosen Prefix Collision (CPC), donde se puede seleccionar dos mensajes con prefijos específicos y crearles variaciones que resulten en una colisión del hash.

Marco teórico


El objetivo del ataque es encontrar dos mensajes M1 y M2, cada uno con prefijos conocidos y controlados P1 y P2, que resulten en el mismo digest. Se puede expresar como:


Donde:

  • H es la función hash (MD5 o SHA1).
  • P1 y P2 son los prefijos elegidos.
  • M1 y M2 son las partes variables a identificar.
  • ∣∣ denota concatenación.

El riesgo que supone este ataque en particular es elevado dado que los prefijos pueden ser parte de un archivo o mensaje con una estructura conocida (como un certificado digital), lo que podría permitir manipular datos de manera estratégica con el fin de lograr la colisión sin que se modifiquen elementos visibles o críticos.


Proceso del Ataque

Considerando la estructura de una función hash que se divide en bloques:


Donde Mi representa los bloques de tamaño fijo en la entrada. El cálculo de la función hash, H, se realiza de manera iterativa sobre estos bloques, donde cada bloque modifica un estado intermedio Si.


Durante el ataque CPC los prefijos P1 y P2 se procesarán primero, generando un estado intermedio para el cálculo del hash.


Para lograr el objetivo de encontrar dos bloques M1 y M2, que, concatenados con los prefijos P1 y P2 produzcan el mismo valor final de hash, se los bloques M1 y M2 deben ajustarse de manera que las diferencias entre S1 y S2 se cancelen en los estados intermedios posteriores.

Al menos un bloque Mi debe compensar la diferencia entre los estados intermedios.

Donde:

  • ⊕ representa la operación XOR.
  • ΔS es la diferencia introducida en los estados intermedios para equilibrar la disparidad entre S1 y S2.

Simplificando, si se asume que se logra forzar una colisión de estados intermedios, al final de los bloques elegidos se obtendría:



Resultando en el ajuste de M1 y M2 de manera que, a pesar de tener prefijos diferentes P1 y P2, se produce el mismo estado intermedio final S1.

Alcanzar la igualdad pasa por introducir diferencias calculadas en los bloques de mensaje M1 y M2, aplicando diferencias en los bits del bloque de mensaje que contrarresten las diferencias en los estados intermedios de la función hash. Estas diferencias se calcularán de manera que su propagación a través de la función hash conlleve al mismo estado final.

Suponiendo que se introducen diferencias en el bloque de mensaje M1, como ΔM de tal manera que:


Con el objetivo de que el estado intermedio después de aplicar el bloque M1⊕ΔM sea igual al estado intermedio después de aplicar el bloque M2.


Conclusión

El ataque Chosen Prefix Collision explota la naturaleza interactiva de las funciones hash. Mediante la manipulación de bloques de datos y la aplicación de diferencias calculadas, se puede lograr que coincidan los estados intermedios, provocando la colisión en la salida del hash final.

Este ataque es una prueba de las debilidades fundamentales de funciones hash como MD5 y SHA1, principalmente ante ataques avanzados que manipulan los datos de entrada de manera controlada.



Autor: Daniel Puente