漏洞概况

  • 在phpshe 1.1版本中,存在一个变量覆盖漏洞,该漏洞可以导致不花钱生成订单。

环境搭建

  • phpshe 1.1
  • php 5.4.45
  • Windows 7 + Mysql 5.6 + Apache

漏洞利用

安装好phpshe之后,因为要复现的是先登陆后台发布商品。

1

提交你的商品,比如说最近卖到脱销的kn95口罩。

2

然后我们来到商品前台,搜索关键字kn95,就可以看到刚才添加的口罩。

3

加入购物车结算。

4

关键在于填写收货信息的时候,我们将表单的info[user_tel]改为info[order_state],然后填入paid状态,提交订单。

5

接着我们进入会员中心查看订单详情,发现订单状态已经是”已付款”。

6

如果我们正常提交一个订单的时候,返回的状态是这样的,会让你选择支付的方式。

7

登录后台可以看到正常提交订单的状态和修改表单之后提交的订单的状态的不同。

8

漏洞分析

module/index/order.php:

9

漏洞代码位置在第80行$db->pe_insert('order', $_p_info),会将$_p_info数组中的值全都插入订单表中。我们跟进pe_insert()函数查看,定义在include/class/class.db.php文件中:

1
2
3
4
5
6
public function pe_insert($table, $set)
{
//处理设置语句
$sqlset = $this->_doset($set);
return $this->sql_insert("insert into `".dbpre."{$table}` {$sqlset}");
}

mysql中支持两种insert方法,一种是insert values,另一种是insert set,下面是这两种插入数据方式的差别:

insert values:优点:可以批量插入;缺点:单条执行效率低。<适合批量插入>

insert into table(col1,col2,col3) values(‘val1’,’val2’,’val3’);

insert set:优点:执行效率高;缺点:每次只能插入一条数据。<适合单条插入>

insert into table set col1=’val1’,col2=’val2’,col3=’val3’;

pe_insert()函数会先调用$this->_doset()函数将array转化为sql set语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//处理设置语句
protected function _doset($set)
{
if (is_array($set)) {
foreach ($set as $k => $v) {
$set_arr[] = "`{$k}` = '{$v}'";
}
$sqlset = 'set '.implode($set_arr, ' , ');
}
else {
$sqlset = "set {$set}";
}
return $sqlset;
}

$_doset()函数可以把下面的array转化为

10

字符串:

1
2
3
4
5
6
7
8
9
10
11
`user_address` = '河南省郑州市hangzhou' ,
`user_tname` = '半天' ,
`user_phone` = '12345678900' ,
`order_state` = 'paid' ,
`order_text` = '' ,
`order_productmoney` = '20.0' ,
`order_wlmoney` = '0.0' ,
`order_money` = '20.0' ,
`order_atime` = '1580394866' ,
`user_id` = '1' ,
`user_name` = 'lxxxz'

完整的sql查询语句就是:

1
insert into `pe_order` set `user_address` = '河南省郑州市hangzhou' , `user_tname` = '半天' , `user_phone` = '12345678900' , `order_state` = 'paid' , `order_text` = '' , `order_productmoney` = '20.0' , `order_wlmoney` = '0.0' , `order_money` = '20.0' , `order_atime` = '1580394866' , `user_id` = '1' , `user_name` = 'lxxxz'

其中order_state的值是paid,这个值是通过表单info['order_state']提交的,这里就是造成变量覆盖的地方,我们可以用自己提交的order_state来覆盖原来的值,先看一下这个值是怎么传进来的。

phpshe的路由定义在common.php文件中:

11

当前请求是http://192.168.247.134/phpshe/index.php?mod=order&act=add ,所以$act的值就是add

然后要对用户传进来的POST参数进行处理。

12

我们知道从php5.4.0版本起,因为php.ini中去掉了magic_quotes,所以get_magic_quotes_gpc()始终会返回false,直接进入else逻辑。

get_magic_quotea_gpc能获取当前magic_quotes_gpc的配置选项设置,这是在php.ini中的配置选项。如果设置了这个选项,那么php解析器就会自动为POST、GET、COOKIE传过来的数据增加转义字符·\

这个选项主要的目的就是防止发生sql注入之类的数据注入攻击。

get_magic_quotes_gpc 在PHP 5.4.0起将始终返回FALSE

13

下面的EXTR_PREFIX_ALL表示给所有变量名加上前缀。调用extract()函数时会对注册的变量名称加上前缀,比如$_GET['id']会注册变成$_g_id$_POST['info']会变成$_p_info,前缀和数组键名之间会自动加一个下划线。

1
extract(pe_trim($_POST),EXTR_PREFIX_ALL,'_p')

经过extract()函数处理之后,我们可以看到$_p_info已经变成了内部变量,覆盖掉了原来的变量值,order_state="paid"成功传入。

14

这是由extract()函数而引起的变量覆盖。

extract()函数与变量覆盖漏洞

extract()函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。

extract()函数的具体用法参考: http://www.runoob.com/php/func-array-extract.html

语法:extract(array,extract_rules,prefix)

15

bugku中有一道extract()函数使用不当导致的变量覆盖漏洞例题, http://123.206.87.240:9009/1.php :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$flag='xxx';
extract($_GET);
if(isset($shiyan))
{
$content=trim(file_get_contents($flag));
if($shiyan==$content)
{
echo'flag{xxx}';
}
else
{
echo'Oh.no';
}
}
?>

分析代码,先是用extract($_GET)接收GET请求中的数据,并将键名和键值分别转化为变量名和变量值,然后再进行两个if的判断,所以可以使用get提交参数和值,然后用extract()对变量进行覆盖。

题解:

我们已经知道如果我们传入$shiyan$flag,通过extract()函数之后,就可以覆盖掉原来的值,所以我们只要将变量的值设置为空,或者是将$flag的值设置为不存在的文件即可,payload:

1
2
3
4
5
http://123.206.87.240:9009/1.php?shiyan=&flag=1.txt

http://123.206.87.240:9009/1.php?shiyan=&flag=

http://123.206.87.240:9009/1.php?shiyan=

16

官方补丁

官方给出的修复方案其实就是在提交订单之前就要求用户选择付款方式。

patch

在进入订单处理逻辑的时候就要用户选择付款方式。