Uno dei partecipanti alla competizione UIUCTF 2025 ha analizzato in dettaglio come sia riuscito a completare un compito che richiedeva l'esecuzione del proprio codice sul server, avendo solo la possibilità di modificare il contenuto del testo nel commento del codice.
I partecipanti potevano inviare una richiesta di rete a uno script Python, che creava un nuovo script Python con un nome casuale, aggiungendo i dati ricevuti dall'utente al testo del commento, rimuovendo i caratteri «\n» e «\r», e avviava questo script con il comando «python3 nome.py». Controllando solo il contenuto del commento, il partecipante doveva estrarre una stringa dal file «/home/ctfuser/flag». Lo script veniva creato con il seguente codice: comment = input(«> «).replace(«\n», «»).replace(«\r», «») code = f»»»print(«hello world!») # Questo è un commento. Ecco un altro: # {comment} print(«Thanks for playing!»)»»»
Al posto di «{comment}» venivano inseriti i dati ricevuti dal partecipante, e alla fine veniva eseguito il seguente codice: print(«hello world!») # Questo è un commento. Ecco un altro: # Dati ricevuti dal partecipante della competizione print(«Thanks for playing!»)
Il compito è stato formulato ispirandosi a una vulnerabilità nel parser CPython, che trattava il carattere con codice zero come fine della stringa (la vulnerabilità, ad esempio, poteva essere utilizzata per nascondere azioni dannose nel testo del commento). Il problema è stato risolto nelle versioni CPython 3.12.0 e 3.11.4. Nell'elaboratore utilizzato nella competizione venivano tagliati solo i caratteri «\n» e «\r», ma utilizzando una versione vulnerabile di CPython il partecipante poteva usare il carattere «\0» come delimitatore. Tuttavia, questo trucco non ha funzionato perché nel concorso veniva già utilizzata una versione corretta di CPython, con il calcolo che nel parser potessero ancora rimanere alcuni errori simili e i partecipanti potessero identificarli.
Il partecipante che ha completato con successo il compito non ha cercato nuove vulnerabilità nel parser che gli avrebbero permesso di dividere la stringa in parti, ma ha sfruttato la particolarità dell'esecuzione in Python dei file in base al loro contenuto. Ad esempio, invece del codice sorgente in un file con estensione «.py» si può posizionare il bytecode memorizzato in file con estensione «.pyc», e tale file verrà eseguito. Nella competizione considerata, il partecipante poteva controllare solo il contenuto al centro del file, quindi non poteva aggiungere il proprio intestazione per alterare il tipo MIME.
È stato possibile risolvere il compito approfittando del fatto che Python a partire dalla versione 2.6 può eseguire il contenuto degli archivi ZIP per fornire i pacchetti Python in forma compressa. Come nel caso della cache dei bytecode, la presenza di un archivio zip viene determinata dal contenuto e non dall'estensione del file, ovvero si può inserire un archivio zip in "file.py" e, eseguendo il comando "python file.py", verrà elaborato come un pacchetto Python compresso. Gli archivi ZIP in Python vengono indicizzati non secondo l'intestazione all'inizio del file, ma secondo la sezione EOCD (End of Central Directory Record) alla fine del file. Se l'archivio contiene il file "__main__.py", questo viene eseguito automaticamente all'avvio diretto dell'archivio con il comando "python archivio".
Il compito del concorso è stato risolto generando un archivio ZIP simile e inserendolo nel testo del commento. Per mantenere la correttezza della struttura del file, in presenza alla fine del file sorgente della chiamata ‘print(«Thanks for playing!»)’, è stata utilizzata la presenza nella sezione EOCD di un'area commento, posizionata proprio alla fine.

Fonte: opennet.ru
