paint-brush
Análisis de vulnerabilidades comunes introducidas por la IA generativa de códigopor@natanjfrog
4,968 lecturas
4,968 lecturas

Análisis de vulnerabilidades comunes introducidas por la IA generativa de código

por Natan Nehorai11m2024/02/04
Read on Terminal Reader

Demasiado Largo; Para Leer

No se puede confiar ciegamente en el código generado automáticamente y aún así requiere una revisión de seguridad para evitar la introducción de vulnerabilidades de software.
featured image - Análisis de vulnerabilidades comunes introducidas por la IA generativa de código
Natan Nehorai HackerNoon profile picture
0-item
1-item

Las herramientas de inteligencia artificial como Bard, ChatGPT y Bing Chat son los grandes nombres actuales en la categoría de modelos de lenguaje grande (LLM) que está en aumento.


Los LLM están capacitados en vastos conjuntos de datos para poder comunicarse utilizando el lenguaje humano cotidiano como mensaje de chat. Dada la flexibilidad y el potencial de los LLM, las empresas los están integrando en muchos flujos de trabajo dentro de la industria tecnológica para hacernos la vida mejor y más fácil. Una de las principales integraciones del flujo de trabajo de IA es un complemento de código de autocompletar, que brinda la capacidad de prever y generar patrones de código coincidentes para codificar de manera más rápida y eficiente.


En el mercado actual de herramientas generadoras de códigos de IA, hay muchos jugadores, incluidos GitHub Copilot , Amazon CodeWhisperer , Google Cloud Code (Duet AI) , Blackbox AI Code Generation y más. Esta publicación de blog describe ejemplos de problemas de seguridad comunes que enfrentan los desarrolladores al utilizar dichas herramientas.


El objetivo de este artículo es crear conciencia y enfatizar que no se puede confiar ciegamente en el código generado automáticamente y que aún requiere una revisión de seguridad para evitar la introducción de vulnerabilidades de software.


Nota: algunos proveedores sugieren que sus herramientas de autocompletar pueden contener fragmentos de código inseguros.


Muchos artículos ya han enfatizado que las herramientas de autocompletar generan vulnerabilidades conocidas como IDOR , inyección SQL y XSS. Esta publicación resaltará otros tipos de vulnerabilidad que dependen más del contexto del código, donde existe una alta probabilidad de que la función de autocompletar de IA introduzca errores en su código.


Herramientas de generación de código de seguridad e inteligencia artificial Los modelos de inteligencia artificial entrenados para código generalmente se entrenan en grandes bases de código con la misión principal de producir código funcional.


Muchos problemas de seguridad tienen una forma de mitigación conocida y definida sin comprometer el rendimiento (por ejemplo, las inyecciones de SQL podrían evitarse al no concatenar las entradas/parámetros del usuario directamente en la consulta) y, por lo tanto, pueden erradicarse ya que no hay razón para escribir el código de forma insegura.


Sin embargo, muchos problemas de seguridad dependen del contexto, lo que podría ser perfectamente seguro cuando se implementa como funcionalidad interna, mientras que cuando se enfrenta a entradas externas de un cliente, se convierte en una vulnerabilidad y, por lo tanto, es difícil de distinguir a través del conjunto de datos de aprendizaje de la IA.


En tales vulnerabilidades, el uso específico del código generalmente depende de otras partes del código y del propósito general de la aplicación.


A continuación, se muestran algunos casos de uso comunes y ejemplos que probamos, que muestran algunos de estos tipos de vulnerabilidades:

  1. Caso de uso: obtención de archivos a partir de la entrada del usuario: lectura de archivos arbitrarios
  2. Caso de uso: Comparación de fichas secretas: malabarismo de tipos/coerción
  3. Caso de uso: Mecanismo de contraseña olvidada: colisiones de asignación de casos Unicode
  4. Caso de uso: generación de un archivo de configuración: malas prácticas de seguridad
  5. Caso de uso: objetos de configuración: inconsistencias que conducen a una deserialización insegura


Después de ver cómo las herramientas de generación de código de IA manejan los casos anteriores, intentamos probar su capacidad para crear filtros de seguridad adecuados y otros mecanismos de mitigación.


A continuación se muestran algunos casos de uso comunes y ejemplos que probamos solicitando deliberadamente código seguro:

  1. Caso de uso de solicitud de código seguro: leer archivos y evitar el cruce de directorios
  2. Solicitud de código seguro - Carga de archivos - Lista de extensiones restringidas


  1. Caso de uso: obtención de archivos a partir de la entrada del usuario: lectura de archivos arbitrarios

Muchas aplicaciones incluyen código que devuelve un archivo al usuario en función de un nombre de archivo. El uso del recorrido de directorio para leer archivos arbitrarios en el servidor es un método común que utilizan los delincuentes para obtener información no autorizada.


Puede parecer obvio desinfectar la entrada de nombre de archivo/ruta de archivo proveniente del usuario antes de recuperar el archivo, pero asumimos que es una tarea difícil para un modelo de IA entrenado en bases de código genéricas; esto se debe a que algunos de los conjuntos de datos no necesitan utilizan rutas desinfectadas como parte de su funcionalidad (incluso puede degradar el rendimiento o dañar la funcionalidad) o no se ven perjudicadas por rutas no desinfectadas, ya que están destinadas a ser solo para uso interno.


Sugerencia de herramienta de IA (Google Cloud Code - Duet AI): Intentemos generar una función básica de recuperación de archivos a partir de la entrada del usuario, utilizando Google Cloud Code - Duet AI con autocompletar. Dejaremos que Duet AI complete automáticamente nuestro código, según nuestra función y nombres de ruta.



Como podemos ver en gris, la sugerencia de autocompletar es utilizar la función “ send_file ” de Flask, que es la función básica de manejo de archivos de Flask.


Pero incluso la documentación de Flask dice " Nunca pase las rutas de archivo proporcionadas por un usuario ", lo que significa que al insertar la entrada del usuario directamente en la función "send_file", el usuario tendrá la capacidad de leer cualquier archivo en el sistema de archivos, por ejemplo, utilizando caracteres transversales de directorio. como “../” en el nombre del archivo.


Implementación adecuada:


Reemplazar la función "send_file" con la función segura "send_from_directory" de Flask mitigará el riesgo de cruce del directorio. Sólo permitirá recuperar archivos de un directorio codificado específico ("exportaciones" en este caso).


  1. Caso de uso: Comparación de fichas secretas: malabarismo de tipos/coerción

En algunos lenguajes de programación, como PHP y NodeJS, es posible hacer comparaciones entre dos tipos diferentes de variables y el programa realizará una conversión de tipo detrás de escena para completar la acción.


Las comparaciones flexibles no verifican el tipo de variable detrás de escena y, por lo tanto, son más propensas a ataques de malabarismo de tipos. Sin embargo, el uso de comparaciones vagas no se considera una práctica de codificación insegura en sí misma; depende del contexto del código. Y nuevamente, a los modelos de IA les resulta difícil diferenciar los casos.


Sugerencia de herramienta de inteligencia artificial (Amazon CodeWhisperer): el ejemplo más común de malabarismo con tipos de PHP son las comparaciones flexibles de tokens/hashes secretos donde la entrada del usuario que se lee a través de una solicitud JSON (que permite el control del tipo de entrada) alcanza una comparación flexible (" " ) en lugar de uno estricto (“ =”).


Al parecer, CodeWhisperer genera condiciones de comparación flexibles en sugerencias de autocompletar:

La comparación flexible de secret_token permite a un adversario eludir la comparación $data[“secret_token”] == “secret_token”. Al enviar un objeto JSON con el valor de "secret_token" como Verdadero (booleano), el resultado de la comparación flexible también es Verdadero (incluso en versiones más nuevas de PHP).



Incluso cuando "insinuaba" a la herramienta (mediante un comentario) que escribiera el cheque de forma "segura", no generó un fragmento de código que contuviera una comparación estricta.

Implementación adecuada:

Sólo cuando especificamos una comparación estricta (“===”) estamos protegidos contra ataques de malabarismo de tipos.


  1. Caso de uso: Mecanismo de contraseña olvidada: colisiones de asignación de casos Unicode

Las vulnerabilidades en el mecanismo "Olvidé mi contraseña" son muy comunes en las aplicaciones SaaS y fueron la causa principal detrás de CVE como CVE-2017-8295 (Wordpress), CVE-2019-19844 (Django), y CVE-2023-7028 (GitLab).


Específicamente, una vulnerabilidad de colisión de mapeo de casos Unicode ocurre cuando la entrada del usuario está en mayúsculas o minúsculas en una expresión de comparación mientras su valor original también se usa en el código. Algunos caracteres diferentes darán como resultado el mismo código de carácter cuando se transformen. Por ejemplo, “ß” y “SS” se transformarán en “0x00DF” cuando estén en mayúsculas.


Estos casos se suelen utilizar para eludir/engañar algunos controles de seguridad engañando el control de comparación y luego utilizando el valor original que es diferente al esperado. Estos casos son bastante difíciles de entender, especialmente cuando son posibles muchas implementaciones.


Sugerencia de herramienta de IA (copilot de GitHub): un mecanismo que es particularmente susceptible a este tipo de vulnerabilidad es el mecanismo de contraseña olvidada . Es común normalizar la entrada del usuario en mayúsculas o minúsculas (en direcciones de correo electrónico o nombres de dominio) al realizar la verificación para evitar discrepancias no deseadas.


Como ejemplo, observar el código generado por Copilot para la función de contraseña olvidada (a continuación) muestra que es vulnerable a este problema.


El código de Copilot primero busca la dirección de correo electrónico en minúscula:

Olvidé mi contraseña: sugerencia para enviar un correo electrónico en minúsculas en una consulta


Luego, al seguir con el resto del proceso sugiere lo siguiente:

Olvidé mi contraseña: sugerencia para utilizar la dirección de correo electrónico original (no en minúsculas) en send_email


Como podemos ver, la sugerencia de autocompletar genera una contraseña aleatoria y la envía a la dirección de correo electrónico del usuario. Sin embargo, inicialmente utiliza una versión en minúsculas del correo electrónico del usuario para recuperar la cuenta de usuario y luego continúa usando el correo electrónico del usuario original "tal cual" (sin una conversión a minúsculas) como destino del correo electrónico de recuperación.


Por ejemplo, digamos que el atacante posee la bandeja de entrada de "[email protected]" (la "K" es un carácter Unicode del signo Kelvin ) y utiliza este correo electrónico para el mecanismo "Olvidé mi contraseña". Los correos electrónicos "[email protected]" y "[email protected]" no son equivalentes "tal cual", pero después de la conversión en minúsculas serán:


El uso de la dirección de correo electrónico "[email protected]" obtendrá la información de la cuenta de "[email protected]", pero el restablecimiento de la contraseña se enviará a la bandeja de entrada del atacante ("[email protected]"), lo que permitirá tomar el control de " [email protected]” cuenta.


Implementación adecuada: la dirección de correo electrónico utilizada para recuperar la cuenta de usuario debe ser exactamente igual a la dirección de correo electrónico de recuperación (es decir, el parámetro send_email también usará email.lower()).


Además, por precaución, se recomienda utilizar el valor que ya estaba almacenado en el servidor en lugar del proporcionado por el usuario.


  1. Caso de uso: generación de un archivo de configuración: malas prácticas de seguridad

Otro tema que podría confundir a las herramientas de autocompletar es la escritura de archivos de configuración, especialmente para infraestructuras de nube complicadas.


La razón principal es que, de hecho, existen directrices de mejores prácticas de seguridad, pero no todo el mundo las sigue y, en algunos casos, es necesario ir en contra de ellas. Estos ejemplos de código podrían llegar al conjunto de datos de entrenamiento de IA. Como resultado, algunos resultados de autocompletar violarán las pautas de seguridad recomendadas.


En el siguiente ejemplo, crearemos un archivo de configuración para un Kubernetes Pod , la unidad de ejecución más pequeña de Kubernetes.


Sugerencia de herramienta AI (generación de código AI de Blackbox): en este ejemplo, comenzamos a crear un archivo de configuración de Pod.

La sugerencia crea la sección de capacidades y agrega una capacidad del kernel de Linux (SYS_ADMIN) al contenedor que es esencialmente equivalente a ejecutar el pod como usuario root.


Implementación adecuada: ¿Es mejor entonces simplemente omitir el contexto de seguridad? No, ya que muchas otras capacidades se agregarán de forma predeterminada, violando el principio de privilegio mínimo .


De acuerdo con las mejores prácticas de seguridad de Docker , la configuración más segura es eliminar primero todas las capacidades del kernel de Linux y solo después agregar las necesarias para su contenedor.


Para solucionar los problemas mencionados anteriormente, la sección de capacidades comienza eliminando todas las capacidades y luego agregará gradualmente las necesarias para nuestro contenedor.


  1. Caso de uso: objetos de configuración: inconsistencias que conducen a una deserialización insegura

    Muchas vulnerabilidades requieren definir un objeto de configuración, que decide cómo la aplicación manejará el componente requerido.


El ejemplo que elegimos es la biblioteca de deserialización JSON de Newtonsoft en C#, que se puede usar de forma segura o insegura según la configuración del objeto JsonSerializerSettings y, específicamente, TypeNameHandling .


Mientras probábamos el código de deserialización, notamos que la definición del objeto de configuración es bastante aleatoria y cambios sutiles pueden llevar a generar un conjunto de configuración diferente.


Sugerencias de herramientas de IA (Amazon CodeWhisperer): los siguientes ejemplos muestran un comportamiento inconsistente que se basa únicamente en los nombres de los métodos utilizados por el desarrollador:


Podemos ver dos configuraciones diferentes que dan como resultado una deserialización JSON insegura debido a que la propiedad TypeNameHandling es "Auto" y "ALL". Estas propiedades permiten que el documento JSON defina la clase deserializada, lo que permite a los atacantes deserializar clases arbitrarias. Esto se puede aprovechar fácilmente para la ejecución remota de código, por ejemplo, deseriaizando la clase "System.Diagnostics.Process". La única diferencia en el código original del desarrollador son los nombres de los métodos.


Implementación adecuada:

De forma predeterminada, se crea una instancia de un objeto JsonSerializerSettings con “TypeNameHandling = TypeNameHandling.None”, que se considera seguro. Por lo tanto, utilizamos el constructor predeterminado de JsonSerializerSettings que dará como resultado un valor TypeNameHandling seguro.


  1. Caso de uso de solicitud de código seguro: leer archivos: evitar el cruce de directorios De manera similar al primer caso de uso, donde examinamos el código vulnerable producido por Copilot para recuperar archivos, aquí intentamos solicitar una versión segura de un mecanismo de lectura de archivos.


Sugerencia de herramientas de IA (copilot de GitHub):

Podemos ver que la verificación de seguridad es realmente ingenua, ya que solo evita las secuencias de punto, punto y barra que son necesarias para realizar intentos de recorrido de directorio. El parámetro de ruta puede ser una ruta absoluta, que apunta a cualquier ruta deseada en el sistema (por ejemplo, /etc/passwd), lo que anula el propósito de la verificación de seguridad.


Implementación adecuada:

Aquí, la función Secure_file_read obtiene un parámetro de nombre de archivo (relativo) junto con un directorio (safe_dir) donde debe residir el nombre de archivo (y del cual no se debe escapar). Crea una ruta absoluta uniendo safe_dir y filename y procede a obtener su forma canonicalizada llamando a realpath. Luego obtiene la forma canonizada de la carpeta safe_dir. Luego, el contenido del archivo se devuelve solo si la ruta canonicalizada comienza con el directorio seguro canonicalizado.


  1. Solicitud de código seguro - Carga de archivos - Lista de extensiones restringidas Es un caso de uso común aceptar solo tipos de archivos específicos para cargar según su extensión.


Sugerencia de herramienta de IA (generación de código AI de Blackbox): en el siguiente ejemplo, le pedimos al autocompletado de IA que generara una función encargada de filtrar extensiones de archivos peligrosas.

El código Python sugerido toma el nombre del archivo, separa la extensión y la compara con una lista de extensiones peligrosas.


A primera vista, este fragmento de código parece seguro. Sin embargo, las convenciones de nombres de archivos de Windows prohíben los nombres de archivos que terminan con un punto y, cuando se proporciona un nombre de archivo que termina con un punto (“.”) durante la creación del archivo, el punto se descartará. Es decir, que el filtro generado podría omitirse en Windows al enviar un archivo con una extensión maliciosa seguida de un punto (por ejemplo, “ejemplo.exe”).


Implementación adecuada: por lo general, el mejor enfoque es utilizar una lista blanca , que verifica la extensión del archivo solo con las extensiones permitidas. Sin embargo, dado que las herramientas adoptaron un enfoque de lista negra (que es necesario en algunos casos), describiremos una implementación de lista negra más segura:


En el siguiente fragmento, eliminamos todo el control que tenía el usuario sobre la entrada eliminando la extensión de todos los caracteres no alfanuméricos y concatenándola en un nombre de archivo recién generado.


En pocas palabras: utilícelo con precaución. Los coprogramadores automáticos son excelentes para sugerir ideas, completar automáticamente el código y acelerar el proceso de desarrollo de software. Esperamos que estas herramientas sigan evolucionando a medida que pasa el tiempo, pero por ahora, como se demuestra con los casos de uso en esta publicación, las soluciones de IA de autocompletar tienen muchos desafíos que superar hasta que podamos tranquilizarnos y confiar en sus resultados sin volver a verificar. insectos.


Una posible forma de reducir la probabilidad de introducir vulnerabilidades es solicitar deliberadamente a la herramienta de generación de código de IA que genere código seguro. Aunque esto puede resultar útil en algunos casos de uso, no es infalible: alguien con conocimientos de seguridad de software debe revisar manualmente el resultado "aparentemente seguro".


Recomendaciones del equipo de investigación de seguridad de JFrog Para ayudar a proteger su software, recomendamos revisar manualmente el código producido por herramientas generativas de IA, así como incorporar soluciones de seguridad como Static Application Security Testing (SAST), que pueden descubrir vulnerabilidades de manera confiable a medida que avanza. Como se ve en el ejemplo anterior, las herramientas SAST pueden alertar y detectar la colisión de mapeo de casos Unicode descrita en el caso de uso n.° 3. Este enfoque proactivo es esencial para garantizar la seguridad de su software.


Manténgase actualizado con JFrog Security Research Los hallazgos y la investigación del equipo de investigación de seguridad juegan un papel importante en la mejora de las capacidades de seguridad del software de aplicaciones de la plataforma JFrog.


Siga los últimos descubrimientos y actualizaciones técnicas del equipo de investigación de seguridad de JFrog en nuestro sitio web de investigación y en X @JFrogSecurity .