12/09/25
Injecciód'ordresmitjançantlarevisiódefonts
Redacció oficial: Banderes a l'aire | ConnectorsCTF 2025
Thanks for sharing!
بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيمِ

I know your browser history
Benvingut de nou a un altre escrit. La redacció oficial del repte "Flags in the air" a Connectors CTF Quals 2025.
Aquest repte combina defectes lògics del codi font amb un toc d'interns de Linux.
Se't dóna un codi font (Baixa'l des d'aquí)
A la ruta
app.py a la ruta /login:Aquest punt final comprova si el nom d'usuari existeix al diccionari
TWO_FA_CODES. Si ho fa, estableix el valor corresponent a None. A continuació, verifica si la contrasenya proporcionada coincideix amb la emmagatzemada al diccionari USERS. Si coincideixen, genera un codi 2FA, l'emmagatzema al diccionari TWO_FA_CODES i genera un testimoni JWT amb un rol "2fa".A la ruta
/verify-2fa:Aquest punt final recupera el codi 2FA de les dades de la sol·licitud i el compara amb el codi emmagatzemat al diccionari
TWO_FA_CODES de l'usuari actual. Si coincideixen, elimina el codi 2FA del diccionari i genera un nou testimoni JWT amb un paper "complet".A
auth.py hi ha funcions JWT estàndard i diccionaris USERS i TWO_FA_CODES:Ens adonem que el diccionari
USERS està buit i no hi ha cap registre en aquest codi.Tornant a la ruta
/login, veiem que no comprova si el nom d'usuari existeix al diccionari USERS abans de comparar les contrasenyes, el que significa que si el nom d'usuari no existeix, USERS.get(username) retornarà None.Llavors, què passa si passem un usuari inexistent amb una contrasenya definida a
None? La condició if USERS.get(username) == password: s'avaluarà a True, cosa que ens permetrà iniciar sessió correctament i passar al pas 2FA.
Aquí he eliminat el camp
password del cos de la sol·licitud, que el converteix en None.Fins ara, hi ha un codi 2FA aleatori generat per a l'usuari
mush i emmagatzemat al diccionari TWO_FA_CODES.Mirant enrere a la ruta
/login, veiem això:Amb cada intent d'inici de sessió amb un nom d'usuari específic, el codi 2FA d'aquest nom d'usuari s'estableix en
None i se'n genera un de nou quan la contrasenya coincideix. Però quan les contrasenyes no coincideixen, no fa res. El codi 2FA continua sent None i el testimoni JWT amb la funció "2fa" encara és vàlid.Així que tot el que hem de fer és iniciar sessió per segona vegada amb el mateix nom d'usuari i qualsevol contrasenya incorrecta (que és qualsevol cosa ja que l'usuari no existeix), i el codi 2FA serà
None.

I hi som!
Tenim accés a la línia d'ordres:

Res especial al punt final corresponent, només una simple execució d'ordres.
A ZZMDX0ZZZ, veiem l'ordre següent:
A partir d'això, és evident que la bandera es fa ressò cada 10 segons a la sortida estàndard. El repte és: com podem capturar-lo realment?
A Linux, sempre que s'escriu text al terminal, passa per un descriptor de fitxer (FD). La sortida estàndard és sempre FD 1. Dins d'un contenidor, podem accedir a aquest flux mitjançant
/proc/1/fd/1, on 1 fa referència al PID del procés principal.No obstant això, com que la bandera només apareix breument cada 10 segons, hem de seguir llegint contínuament per tal d'atrapar-la. Un
cat d'un sol cop no funcionarà de manera fiable. En lloc d'això, podem utilitzar timeout per transmetre la sortida d'una finestra prou llarg per capturar la bandera:Això continua llegint FD 1 durant 15 segons, la qual cosa està garantit per capturar almenys una instància de la bandera que s'imprimeix.

Aquesta era la solució prevista. Alguns jugadors van utilitzar altres mètodes com
cat /proc/1/cmdline que imprimeix la línia d'ordres del procés principal que té la bandera.
Un altre jugador utilitzat pspy, tots condueixen al mateix resultat.
I això és tot per aquest repte. De vegades, les banderes passen davant vostre a stdout. Espero que hagis gaudit de l'escrit i hagi après alguna cosa nova. Fins la propera!
Etiquetes:

