Novedades de OWASP Top 10 2021 (II)

Tras comentar en el anterior post de la serie algunos detalles sobre la nueva versión de OWASP Top 10, en esta segunda entrada vamos a profundizar en otra categoría introducida, la A08, fallos de integridad del software y de los datos.

A08: Fallos de integridad del software y de los datos

Esta nueva categoría introducida en la versión de 2021 hace referencia a la falta de verificación de integridad en procesos, como por ejemplo: actualizaciones de software, fuentes CDN (Content Delivery Network), flujos CI/CD, serialización de datos o codificación de estructuras de datos, entre otros.

En esta nueva categoría está incluida también la categoría de la anterior versión de 2017: A08:2017-Serialización insegura; dicha referencia puede ser útil para quien ya la conozca o para ampliar información consultando ediciones anteriores.

En general, se podría estar expuesto al riesgo si se usan componentes para los que no se dispone de una forma de verificar su integridad o esta no se lleva a cabo. Si se realiza la verificación, podríamos estar más seguros de que los componentes que estamos usando son los mismos que los que se tenían en el origen.

A continuación expondremos algunos ejemplos de como podría llevarse a cabo la verificación de integridad para los casos mencionados.

CDN (Content Delivery Network) usa fuentes sin verificar su integridad

Para poder utilizar los recursos de un CDN en nuestra aplicación, deberíamos incorporar la verificación de integridad del recurso accedido.

Resaltar que deberíamos prestar especial atención a las ocasiones en las que no se proporciona la verificación de integridad del recurso a utilizar, y deberíamos buscar una opción en la que se verificase el recurso a utilizar o alguna forma alternativa de hacerlo.

Como ejemplo, supongamos que vamos a utilizar el CSS que proporciona jsDelivr, referido en la web del proyecto Bootstrap:

Dicha URL tiene comprobación de integridad, aunque en la captura no se muestra. Para poder utilizar el recurso en nuestra página, debemos incluir la línea donde aparece el enlace de tipo link, que como hemos dicho, tiene incluido el atributo integrity (especificación W3C) que contiene la información de integridad.

A continuación se muestra el enlace de la captura, que nos servirá de ejemplo:

<link rel=”stylesheet” href=”https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css” integrity=”sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3″ crossorigin=”anonymous”>

Cabe destacar, además del mencionado atributo integrity, el atributo crossorigin. Pasemos a revisar los atributos de los que se compone el ejemplo y veamos qué relevancia tiene cada uno de ellos con respecto a la seguridad:

El primer campo que nos interesa es el de los metadatos de integridad, integrity, el cual contiene tres atributos diferenciados, siendo los dos primeros requeridos (MUST):

  1. Función de dispersión (“alg”). Requerido. El campo representa la función de integridad aplicada. Las funciones de integridad que debe soportar son SHA-256, SHA-384 y SHA-512, aunque no se restringe a estas y puede soportar más.
  2. Resumen (“val”). Requerido. El campo indica el valor del resumen (digest o extracto). Es la salida de la función de integridad.
  3. Opciones (“opt”). Opcional. Es un campo reservado para valores opcionales, que actualmente no existen, aunque en un futuro puede que se incorporen opciones como el tipo MIME. En el documento ya referido (W3C especificación atributo integrity) se expresa como: option-expression  = *VCHAR

Podemos hacer una prueba para obtener el resumen y verificar, por nosotros mismos, la integridad del fichero referenciado en el atributo href del ejemplo, escribiendo el siguiente comando:

$ curl -s “https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css” | openssl dgst -sha384 -binary | base64
1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3

Vemos que el hash obtenido coincide con el que nos proporciona la página web para el recurso CSS que queremos usar. Esta comprobación es la que se debería hacer al obtener el recurso, ya sea el navegador automáticamente o nosotros mismos.

Respecto al atributo crossorigin=”anonymous”, el atributo crossorigin indica la configuración de CORS. Dicho atributo esta presente en algunas etiquetas HTML como  <img>, <video> y en este caso <link>, y se usa para configurar las peticiones de CORS que se cargan. Al asignar su valor a anonymous queremos decir que las solicitudes del elemento tendrán su modo establecido en “cors y además que su modo de credenciales está establecido en “same-origin“.

Establecer el modo “cors” significa que el recurso que se solicita debe comprender el protocolo CORS y participar en el protocolo CORS, y si no es así se produce un error de red.

Asimismo, cuando se configura el modo de credenciales a “same-origin se está diciendo que las credenciales se envían y se reciben si las URLs cumplen la política del mismo origen.

Flujos CI/CD acceso de usuarios no autorizados o no se verifica la integridad

En relación a las políticas de acceso, como norma general, deberíamos seguir el principio de mínimo privilegio, con lo que se debería conceder a los usuarios solo los permisos que necesiten.

Esto se puede hacer con políticas de control de acceso:

  • Basadas en roles: RBAC, Role-Based Access Control. Quizás sea la más conocida, en ellas se utiliza el rol para controlar el acceso. Ejemplos de roles pueden ser “ADMIN” o “USER”.
  • Basadas en atributos: ABAC, Attribute-Based Access Control. Son políticas de control de acceso basadas en atributos. Por ejemplo “Leer fichero” o “Descargar fichero”.

Ambas son utilizadas, aunque desde OWASP se recomienda que las políticas de control de acceso sean basadas en atributos (OWASP Top Ten Proactive Controls 2018 – C7: Enforce Access Controls). Desde Amazon también nos recomiendan que migremos de RBAC a ABAC (Amazon AWS ABAC).

El ejemplo que podemos encontrar en la referencia mencionada “OWASP Top Ten Proactive Controls 2018 – C7: Enforce Access Controls” es el siguiente:

Se propone pasar del código tradicional:

if (user.hasRole(“ADMIN”)) || (user.hasRole(“MANAGER”)) {
     deleteAccount();
}

a un código como el siguiente:

if (user.hasAccess(“DELETE_ACCOUNT”)) {
     deleteAccount();
}

Como vemos, hemos pasado de comprobar si se tiene el rol “ADMIN” o “MANAGER” a ver si se tiene acceso a “DELETE_ACCOUNT”.

OWASP y Amazon AWS hacen referencia a que usar un control de acceso basado en roles no es adecuado si el sistema se piensa escalar o tener múltiples usuarios (tenants, o inquilinos). También indica OWASP que usar roles implica incrustarlos en el código, por lo que no es portable. Amazon por su parte comenta que basarse en roles es algo “tradicional” y que además son definidos de manera externa y no tienen tanta relación con el funcionamiento de su sistema de administración de identidades IAM (Identity and Access Management) como la tiene el uso de ABAC.

Con lo que actualmente la tendencia es a utilizar ABAC en lugar del tradicional RBAC, sobretodo si pensamos desplegar en Amazon AWS.

Actualizaciones estándar y automáticas de software sin verificar integridad

Como ejemplo de auto-actualizaciones sin revisión de integridad, podemos revisar cualquier contenido que hable sobre auto-actualizaciones escrito tiempo atrás, donde se refleja perfectamente lo que en algún momento se hizo y que actualmente deberíamos evitar hacer.

Evitaremos nombrar un ejemplo concreto, aunque podría ser similar a lo siguiente: un usuario investiga sobre el proceso de auto-actualización de una aplicación, y lee que la aplicación de auto-actualización, a grandes rasgos, debería comprobar la versión instalada y compararla con la que hay en el servidor. En el caso de que la del servidor sea más actual, se debería descargar directamente el ejecutable e instalarlo.

En este caso no se comprueba la integridad en ningún momento. También podríamos completar el caso comentando que la aplicación de auto-instalación debería incorporar la comprobación de integridad de los ficheros descargados y verificarla al aplicar la actualización. Además, no deberíamos escribir nosotros mismos el código de auto-actualización para casos generales, si no que deberíamos utilizar herramientas que ya estén construidas y probadas.

Agregar que en el caso en que no haya más opciones que programar una función de auto-actualización nosotros mismos, deberíamos revisar a conciencia la documentación y técnicas actuales que hubiesen al respecto.

Ataque SolarWinds

Pasamos ahora a comentar otro caso, el del ataque SolarWinds, detectado en el año 2021, relacionado con la confianza, en ocasiones ciega, del comportamiento del software firmado.

Pasaremos por alto, en este caso, cómo los atacantes consiguieron acceder inicialmente al código del software legítimo de SolarWinds. Lo que nos interesa resaltar es que los atacantes consiguieron introducir contenido malicioso en el código mencionado y que este código acabó siendo firmado por SolarWinds como legítimo por los procedimientos habituales de la empresa, para ser después distribuido e instalado en clientes de todo el mundo por los canales habituales.

El software instalado parecía legítimo y por tanto, a priori, podría decirse que el comportamiento también debería de serlo, después de todo, el software estaba firmado por SolarWinds.

Pero como hemos comentado, el software instalado contenía código malicioso y fue distribuido mundialmente como software legítimo. El ataque pudo detectarse al observar el comportamiento del software en el cliente final, que era distinto al esperado por el fabricante.

Una situación parecida se da cuando el software procesa y envía información o datos de ‘telemetría’  a terceros, lo que implica que posiblemente se estén enviando trazas de lo que hacemos en nuestra computadora. La situación empeora si además tenemos un sistema sensible o que trabaja con datos sensibles, por lo que deberíamos al menos bloquear el software o incluso no instalarlo. También podríamos bloquear el acceso a los servicios a los que se conecta para enviar esa información si los conocemos, pero podría no ser tan efectivo al tratarse de un tipo de filtrado por lista negra.

Resumiendo, deberíamos revisar siempre el comportamiento del software que instalemos para verificar que es el adecuado para nuestros fines, independientemente de que esté firmado o que provenga de una entidad de confianza.

Otro caso significativo, para el tema que nos ocupa, es el reciente ataque a dispositivos móviles Android que puede leerse en el artículo de Ars Technica “Google Play apps downloaded 300,000 times stole bank credential“. En este se reporta que aplicaciones Android de Google Play, las cuales han pasado los controles de la tienda, después de instaladas, preguntan al usuario si desean instalar características adicionales, que es donde se encuentra el malware.

Las aplicaciones que efectuaban actualizaciones maliciosas eran del tipo: Escáneres QR, escáneres de PDF, entrenamiento fitness y alguna relacionada con las criptomonedas.

La lista de aplicaciones afectadas puede encontrarse en el propio artículo referido, aunque entre otras Two Factor Authenticator, Protection Guard, QR CreatorScanner, Master Scanner Live, QR Scanner 2021, QR Scanner, PDF Document Scanner – Scan to PDF, PDF Document Scanner, PDF Document Scanner Free, CryptoTracker, Gym and Fitness Trainer (2 aplicaciones con el mismo nombre). En el artículo referido se pueden encontrar más datos, incluyendo el nombre exacto del paquete y su SHA-256.

La cuestión, en este caso, es que las características adicionales no provienen de la tienda Google Play, sino de sitios de terceros y por tanto no han pasado las comprobaciones de la tienda desde la que se instaló por primera vez la aplicación. El usuario puede llegar a pensar que la actualización también proviene de una fuente verificada y comprobada, de hecho, para hacer pasar desapercibido el engaño, en el caso de las aplicaciones de fitness, se le dice al usuario que la aplicación necesita descargar “Nuevos ejercicios” y se solicita al usuario que conceda nuevos permisos a la misma para acceder a fotos, media y ficheros del dispositivo.

Por lo que, insistimos, debemos instalar aplicaciones de fuentes fiables y evitar instalar componentes o actualizaciones que no sean de fuentes fiables.

Serialización o codificación en estructuras que el atacante puede ver o modificar y no poseen verificación de integridad

A continuación mostraremos, a modo de ejemplo, cómo puede llevarse a cabo la serialización firmada de un objeto. Tomaremos como ejemplo la clase SignedObject de Java 7.

Para ello, el objeto a firmar necesita ser de una clase serializable. Una vez que lo tenemos identificado, podemos serializarla, por ejemplo en el objeto serializable, dejándolo así preparado para firmarlo con la clave privada, obteniendo como resultado el objeto firmado signedObject:

SignedObject signedObject = new SignedObject(serializable, privateKey, signature);

El receptor podrá verificar la firma, con nuestra clave pública publicKey, en el siguiente paso:

boolean bl = signedObject.verify(publicKey, signature);

Para obtener el objeto original, podría hacerse de la siguiente manera:

signedObject.getObject()

Este procedimiento puede aplicarse a todo tipo de datos que serializemos, en el que estarían incluidas las habituales estructuras JSON.

Por lo que para llevar a cabo una serialización/deserialización segura de datos deberíamos:

  • Al enviar: serializar y firmar
  • Al recibir: realizar el proceso inverso

Con lo que conseguimos que el receptor pueda verificar que obtiene los datos originales, mediante la comprobación de su integridad, consiguiendo con ello transmitirle también confianza en el proceso.

Aquí finaliza el segundo artículo de la serie de blogs sobre OWASP Top 10 2021. Hasta pronto, nos leemos en el siguiente artículo de la serie.