1 puntos por GN⁺ 4 시간 전 | 1 comentarios | Compartir por WhatsApp
  • El EEF CNA reveló que el 35.8% de los CVE publicados son de consumo no controlado de recursos, y en el ecosistema BEAM una gran parte corresponde al agotamiento repetido de atoms
  • El agotamiento de atoms es una vulnerabilidad de denegación de servicio: los atoms no pasan por recolección de basura, se acumulan en una tabla global y, cuando la tabla se llena, la VM se cae
  • Crear atoms a partir de datos cuyo conjunto posible de valores no está garantizado como finito, como la entrada del usuario, genera riesgo de DoS; el esquema de URI tampoco es una excepción
  • El riesgo existe no solo en llamadas explícitas como binary_to_atom/1 y String.to_atom/1, sino también en la decodificación de claves JSON a atoms y en la generación dinámica basada en interpolación de cadenas
  • El manejo seguro consiste en evitar crear atoms nuevos en tiempo de ejecución y, para valores conocidos, limitarse a tablas de búsqueda explícitas o a la familia to_existing_atom, además de revisarlo con un linter

Vulnerabilidades de denegación de servicio causadas por el agotamiento de atoms

  • Entre los CVE publicados por EEF CNA, 35.8% corresponden a consumo no controlado de recursos, y en el ecosistema BEAM una gran parte proviene de problemas repetidos de agotamiento de atoms {p:36}
  • La distribución actual puede verse en la página de Common Weaknesses de EEF CNA
  • El agotamiento de atoms es una vulnerabilidad de denegación de servicio (DoS)
    • Los atoms no pasan por recolección de basura
    • Se almacenan en una tabla global de atoms
    • Cuando la tabla se llena, la VM se cae
  • Crear atoms a partir de valores no finitos, especialmente de la entrada del usuario, se convierte en un DoS potencial
  • El riesgo no se limita solo a llamadas evidentes
    • En Erlang: binary_to_atom/1, list_to_atom/1
    • En Elixir: String.to_atom/1, List.to_atom/1
  • También existen patrones de riesgo menos visibles
    • Generación dinámica de atoms mediante interpolación en Erlang:
      % Erlang: 보간을 통한 동적 atom 생성
      list_to_atom("field_" ++ UserInput)
      
    • Decodificar claves JSON como atoms en Elixir:
      
      
      
      # Elixir: JSON을 atom 키로 디코딩
      Jason.decode(json, keys: :atoms)
      
    • Generación dinámica de atoms mediante interpolación en Elixir:
      
      
      
      # Elixir: 보간을 통한 동적 atom 생성
      :"field_#{user_input}"
      

Formas seguras de manejo y qué revisar

  • Las vulnerabilidades por agotamiento de atoms no son simple descuido; aparecen con frecuencia en código que asume que la entrada está controlada o es finita
  • El esquema de URI es un ejemplo representativo
    • Puede parecer que solo hay unos pocos esquemas por manejar
    • Si el valor proviene de una entrada externa, ya no se puede garantizar que el conjunto posible siga siendo finito
  • El código que crea atoms a partir de entrada no es seguro, salvo cuando el conjunto de valores posibles es finito, conocido y forzado
  • El enfoque más seguro es no crear atoms nuevos en tiempo de ejecución
  • Si se conocen los valores permitidos, es más seguro usar una tabla de búsqueda explícita
    % Erlang
    case Scheme of
        <<"http">> -> http;
        <<"https">> -> https;
        _ -> error
    end
    
  • Cuando una tabla de búsqueda no es práctica, se deben usar variantes que solo empleen atoms existentes, sin crear nuevos
    • Estas funciones no crean atoms nuevos y lanzan un error
    % Erlang
    binary_to_existing_atom(Value)
    list_to_existing_atom(Value)
    
    
    
    
    # Elixir
    String.to_existing_atom(value)
    List.to_existing_atom(value)
    
  • Los linters ayudan a detectar patrones de riesgo antes de que se conviertan en vulnerabilidades
    • En proyectos de Elixir, se puede considerar activar Credo.Check.Warning.UnsafeToAtom de Credo
    • Esta revisión marca llamadas inseguras a String.to_atom/1, List.to_atom/1, Module.concat/1,2 y Jason.decode/2 con keys: :atoms
    • Esa revisión está desactivada de forma predeterminada
  • Los mantenedores de proyectos Erlang o Elixir deberían buscar código que genere atoms a partir de binarios, cadenas, claves JSON, componentes de URI, encabezados y valores de configuración
  • Esta categoría de vulnerabilidad es una de las más fáciles de corregir antes de que termine convertida en un CVE
  • Hay más lineamientos en la guía para prevenir el agotamiento de atoms del EEF Security Working Group

1 comentarios

 
GN⁺ 4 시간 전
Opiniones en Lobste.rs
  • Suena similar a cómo era la situación en Ruby antes de que Symbol fuera recolectado por basura

  • No entiendo el título. Esto definitivamente parece un footgun

    • Creo que la idea del título es que llamar al agotamiento de atoms “solo un footgun” subestima la gravedad del problema
    • Si no recuerdo mal, no uso Erlang a diario, los atoms no son recolectados por basura
      Si piensas “¿Ruby no tiene symbols parecidos a los atoms de Erlang?”, sí, pero Ruby sí recolecta los symbols con garbage collection
      Además, por defecto la tabla de consulta donde se almacenan los atoms de Erlang solo permite un máximo de 1,048,576
      Si generas atoms dinámicamente a partir de entrada de usuario como la de un formulario, es muy peligroso y el software queda expuesto a ataques de denegación de servicio
    • Lo entendí como que es un problema mayor que un footgun “simple”
      Aun así, por mi experiencia, “footgun” ya es una expresión bastante amplia, así que de cualquier forma la redacción del título suena rara
    • Sí, eso también parece un footgun gigantesco
  • Me sorprende porque suena a que alguna parte fundamental del diseño o de la implementación es mala. Más aún porque es un lenguaje que siempre veía muy elogiado en internet

    • Los atoms de BEAM son esencialmente cadenas internadas y tienen una tabla global de bytes↔enteros
      Agregar conteo de referencias a esa tabla sería costoso y cambiaría las características de escalabilidad de código que ha existido durante décadas
      El número máximo de atoms es por defecto de 1 millón y se define al iniciar la VM
      Es una trampa, pero no es difícil de evitar. Desde hace mucho la recomendación es “no crees atoms a partir de entrada de usuario”
      Por ejemplo, si parseas JSON, normalmente no conviertes las claves a atoms, o solo las conviertes si ya existe ese atom. Así puedes hacer pattern matching con claves atom, esos atoms ya fueron creados al cargar el código y las cláusulas generales pueden recibir cadenas en vez de atoms
    • Hay que tener en cuenta que Erlang era un lenguaje de nicho usado por programadores profesionales en casos especiales
      Elixir es mucho más popular, así que es probable que un desarrollador de Erlang lo sepa, pero uno de Elixir quizás no
  • Personalmente, usar atoms de esa manera ya me parece raro. Porque entiendo los atoms de Erlang más o menos como el tipo enum de C
    Lo veo como una función de conveniencia donde, si escribes una palabra de cierta manera, internamente se vuelve un enum
    El artículo menciona entrada de usuario, pero no entiendo por qué habría un caso de uso donde quisieras crear nuevos tipos enum a partir de entrada de usuario. Parece tener una utilidad muy limitada
    Los comentarios de al lado hablan de parsing, pero idealmente ¿no estarías parseando una estructura de datos ya conocida? Siento que me falta algo

    • Esto se desvía un poco de la pregunta, pero en K, los symbols se internan globalmente y, como en Erlang, puedes matar un proceso de K agotando la tabla de symbols
      En ese lenguaje, hay al menos dos ventajas de tener symbols como un tipo separado más allá de comparaciones rápidas de igualdad. Un symbol es un atom, o sea una unidad atómica, no una secuencia tipo lista de caracteres, así que varios operadores lo tratan de forma distinta, y los symbols están vectorizados y pueden almacenarse de forma compacta en listas de un solo tipo
      En K y Q, es muy deseable representar las columnas de tablas de base de datos como tipos vectorizados. Tienen buena localidad, usan la memoria con más eficiencia y muchos operadores tienen rutas rápidas para ellos. Pero por las restricciones de la tabla de symbols, hay que tener cuidado al usar symbols en columnas de alta cardinalidad
      Si parseas JSON con un esquema conocido, los symbols son excelentes como claves de diccionario y en k2/k3 son prácticamente obligatorios. Pero si es JSON de procedencia desconocida, no deberían venir de entrada de usuario
      En algunos dialectos de K, la longitud de los symbols se limita a propósito para que puedan empaquetarse y moverse como valores de 64 bits. Se sacrifica generalidad, pero así ya no hace falta la tabla de symbols
  • La distinción entre “entrada controlada” y “entrada no controlada” en seguridad me recuerda un poco a cosas como si algo es null o no
    Cuando ves entradas del tipo “webpack-plugin-less-css provoca denegación de servicio si recibe un archivo CSS no confiable”, sí da bastante fatiga de CVE
    Aun así, aquí estaría bien tener un mejor marcado de límites. Por ejemplo, si también pudiera manejar bien reglas de composición sobre qué propiedades de seguridad se conservan después de concatenar cadenas
    Y si tomaste un montón de cosas recibidas por HTTP POST y las trataste todas como SafeString, entonces eso ya es, hasta cierto punto, responsabilidad tuya