Unul dintre participanții la competiția UIUCTF 2025 a explicat în detaliu cum a reușit să finalizeze o sarcină care necesita executarea codului său pe server, având doar posibilitatea de a modifica conținutul textului comentariului din cod.
Participanții puteau trimite o cerere de rețea către un script Python, care crea un nou script Python cu un nume aleatoriu, adăugând datele primite de la utilizator în textul comentariului, eliminând caracterele „\n” și „\r”, și rula acest script cu comanda „python3 nume.py”. Controlând doar conținutul comentariului, participantul trebuia să extragă o linie din fișierul „/home/ctfuser/flag”. Scriptul era creat cu următorul cod: comment = input(«> «).replace(«\n», «»).replace(«\r», «») code = f»»»print(«hello world!») # Acesta este un comentariu. Iată încă unul: # {comment} print(«Thanks for playing!»)»»»
În loc de „{comment}” erau inserate datele primite de la participant, iar în cele din urmă era rulat următorul cod: print(«hello world!») # Acesta este un comentariu. Iată încă unul: # Datele primite de la participantul competiției print(«Thanks for playing!»)
Sarcina a fost formulată pe baza unei vulnerabilități în parserul CPython, care trata caracterul cu cod zero ca final de linie (vulnerabilitate care, de exemplu, putea fi folosită pentru a ascunde acțiuni rău intenționate în textul comentariului). Problema a fost rezolvată în versiunile CPython 3.12.0 și 3.11.4. În handlerul utilizat în competiție erau eliminate doar caracterele „\n” și „\r”, dar în utilizarea versiunii vulnerabile de CPython, participantul putea folosi caracterul „\0” ca separator. Totuși, acest truc nu a funcționat, deoarece în competiție se utiliza deja versiunea corectată de CPython, estimându-se că în parser pot rămâne erori similare și participanții le vor putea descoperi.
Participantul care a finalizat cu succes sarcina nu a căutat noi vulnerabilități în parser care să permită divizarea stringurilor în părți, ci a folosit o caracteristică a executării în Python a fișierelor în funcție de tipul conținutului. De exemplu, în loc de codul sursă, într-un fișier cu extensia „.py” se poate plasa bytecode precompilat, salvat în fișiere cu extensia „.pyc”, iar un astfel de fișier va fi executat. În competiția discutată, participantul putea controla doar conținutul din mijlocul fișierului, astfel că nu putea adăuga un antet propriu pentru a distorsiona tipul MIME.
Soluția a fost găsită folosind faptul că Python, începând cu ramura 2.6, poate executa conținutul arhivelor ZIP pentru livrarea pachetelor Python în format comprimat. Așa cum se întâmplă și în cazul cache-ului bytecode, existența arhivei ZIP este determinată de conținutul său, nu de extensia fișierului, adică într-un «fișier.py» se poate plasa o arhivă ZIP și, la rularea comenzii «python fișier.py», aceasta va fi procesată ca un pachet Python comprimat. În acest caz, arhivele ZIP în Python nu sunt indexate după antetul de la începutul fișierului, ci după secțiunea EOCD (End of Central Directory Record) de la sfârșitul fișierului. Dacă în arhivă există un fișier «__main__.py», acesta va fi executat automat la rularea directă a arhivei cu comanda «python arhivă».
Sarcina din concurs a fost rezolvată prin generarea unei arhive ZIP similare și înlocuirea acesteia în textul comentariului. Pentru a menține corectitudinea structurii fișierului în condițiile în care la sfârșitul fișierului sursă există apelul ‘print(«Thanks for playing!»)’, s-a folosit prezența unei zone de comentarii în secțiunea EOCD, plasată chiar la sfârșit.

Sursa: opennet.ro
