B2j Contact is a Joomla extension used for creating customized contact components. It is widely used in Joomla deployment. We came across this extension in the course of a security assessment and decided to take a closer look. We managed to get a copy of the module and installed a local instance to do our testing.
When configuring B2j Contact, one of the parameters defines if file upload is enabled. This allows users to upload attachments, which will be sent with the message in the contact form. This is one of the key feature of B2j Contact form and seems to be enabled in most of the deployments. Upload mechanisms is always something you want to look in details from an attacker point of view.
Direct file access
On our Joomla instance, we started by uploading a file and looked where it ended up on the server.
Surprisingly, the file is uploaded to the published directory at the following location:
When saved on the server, the name of the file is modified perhaps in a tentative to avoid direct access or to avoid erasing files with identical names.
Digging into the code we searched for the function responsible for generating this new file name.
$file = JRequest::getVar('b2jstdupload', NULL, 'files', 'array'); if (!$this->Submitted || !$file || $file['error'] == UPLOAD_ERR_NO_FILE) return true; $upload_directory = JPATH_SITE . "/components/" . $GLOBALS["com_name"] . "/uploads/"; ... $filename = JFile::makeSafe($file['name']); $filename = uniqid() . "-" . $filename; $dest = $upload_directory . $filename;
We can see that the final filename is made of the result of uniqid() appended with the original file name. Looking at the definition of this function, we learn that the result is derived from the PHP function microtime.
As its name explicitly suggests, microtime() function gives the current time in microsecond. This is indeed a predictable value and thus so is uniqid().
Doing some scripting, we checked that it is possible to predict what will be the result of uniqid() based on the current time. The two values are not exactly the same due to the non-concurrence of the two functions:
<? $m = microtime(true); $u = uniqid(); $predicted_u = sprintf("%8x%05x\n",floor($m),($m-floor($m))*1000000); print('predicted:' . $predicted_u); print('uniqid :' . $u); ?>
In other words, it means that if we upload a file named navixia.png, it will be accessible at the following url:
where xxxxxxxxxxxx can be guessed in a reasonable amount of tries related to the difference between the server clock and the current time.
To upload a file and be able to directly access it we can then do the following.
- Upload the file and record the time T1 at which the upload was done.
- Compute the predicted uniqid() result of time T1
- Try to access the file using this ID and the original file name.
- As long as it fails, increase T1 by one and retry.
To be exact, we should also try to decrease T1 in case the server time is behind the current time.
File type validation
When uploading a file, B2j seems to check the MIME type but, surprisingly not the extension. It is thus possible to upload a file with an extension that will be executed by the web server (php, cgi,…)
MIME type are defined by the first bits of the file and it turns out we can modify the content without altering the validity of the MIME type.
The combination of two vulnerabilities is obvious, we can upload a *.php file with an image MIME type that further contains executable code. Find its name and access it through the corresponding URL to execute the code. This allows us to execute command on the server and get a remote shell.
- 2017-01-03: Contacting vendor.
- 2017-01-04: Sending advisory and proof of concept exploit to Codextrous
- 2017-01-05: Vendor confirming vulnerability.
- 2017-01-14: Vendor releasing security patch 2.1.13
- 2017-05-05: Releasing vulnerability
Update B2j Contact to 2.1.13 or later.