基于框架开发的程序的代码审计思路总结

0x00 前言:

随着时代的变更,使用框架开发的网站越来越多了。导致代码越来越复杂,这篇文章主要就是说明了使用框架开发的站点应该从那方面入手进行审计.

0x01:

首先了解下MVC模式,从网上找到一篇图片:

01.png

以及百度到的定义:

MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式。这种模式用于应用程序的分层开发。
Model(模型) - 模型代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑,在数据变化时更新控制器。
View(视图) - 视图代表模型包含的数据的可视化。
Controller(控制器) - 控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开。

简单的来说就是模型是核心函数,视图是把网页渲染到你面前,控制器是控制用户逻辑的地方。所以在审计类似这种框架代码时,只需要查看controller下的代码。关于其他的模板渲染也有可能会导致代码执行,但是发生情况很少。

0x02:

我们以ThinkPhp框架举例子,首先捋一下思路。在审计ThinkPhp时我一般会关注三个点:

  1. ThinkPhp自带的函数
  2. ThinkPhp自带的漏洞
  3. 程序员开发的扩展

接下来我们会用到不同的例子来解释上面所说到的点。

ThinkPhp自带的函数:

以Where和I函数举例子,其中I函数默认会将html转换为实体字符,并在表达式后增加一个空格。

Code:

    public function test(){
        $id = I("get.id");
        var_dump($id);
    }

输出:

02.png
03.png

由上可以很明显的看出过滤了HTML和在表达式后加入了空格.

然后可以看下where函数:

Code:

    public function test(){
        $id = $_GET['sql'];
        $map = array("admin_id"=>"$id");
        M('admin')->where($map)->find();
        echo "<br>";
        echo D('eyoucms')->getLastSql();
    }

输出1 数字:
04.png
输出2 字符:
005.png

从上面可以发现thinkPHP的where函数可以识别数据类型,来达到自动化预处理的作用。

Code2:

    public function test(){
        $id = $_GET['sql'];
        M('admin')->where("admin_id",$id)->find();
        echo "<br>";
        echo D('eyoucms')->getLastSql();
    }

输出1 数字:
06.png
输出2 字符:
07.png

同样的效果,下面我们使用字符串传入:

Code3:

    public function test(){
        $id = $_GET['sql'];
        M('admin')->where("admin_id={$id}")->find();
        echo "<br>";
        echo D('eyoucms')->getLastSql();
    }

输出1 数字:
08.png
输出2 字符:
09.png
输出3 单引号:
20.png

根据以上结论得到在where使用字符串查询的时候可以直接进行注入

案例:

有点CMS /App/Lib/Action/Member/CustomerAction.class.php:

    function saveModify(){
        header("Content-Type:text/html; charset=utf-8");
        $this->_checkPost( $_POST );
        unset( $_POST['InviterID'], $_POST['IsEnable']);
        $m = D('Admin/Member');
        $inviterID = $m->where("MemberID={$_POST['MemberID']}")->getField('InviterID');
        //检查当前MemberID是否自己的客户
        if( $inviterID == session('MemberID')){
            if( $m->create() ){
                if($m->save() === false){
                    $this->ajaxReturn(null, '修改失败!' , 0);
                }else{
                    $this->ajaxReturn(null, '修改成功!' , 1);
                }
            }else{
                $this->ajaxReturn(null, $m->getError() , 0);
            }
        }else{
            $this->ajaxReturn(null, '数据异常' , 0);
        }
    }

漏洞点:

    $inviterID = $m->where("MemberID={$_POST['MemberID']}")->getField('InviterID');

thinkPHP自带的漏洞:

thinkPHP中自带的漏洞,最出名的可能就是where函数处理表达式可以传数组进行绕过的点了:

核心漏洞代码如下:

    protected function parseWhereItem($key,$val) {
        $whereStr = '';
        if(is_array($val)) {
            if(is_string($val[0])) {
                $exp    =    strtolower($val[0]);
                if(preg_match('/^(eq|neq|gt|egt|lt|elt)$/',$exp)) { // 比较运算
                    $whereStr .= $key.' '.$this->exp[$exp].' '.$this->parseValue($val[1]);
                }elseif(preg_match('/^(notlike|like)$/',$exp)){// 模糊查找
                    if(is_array($val[1])) {
                        $likeLogic  =   isset($val[2])?strtoupper($val[2]):'OR';
                        if(in_array($likeLogic,array('AND','OR','XOR'))){
                            $like       =   array();
                            foreach ($val[1] as $item){
                                $like[] = $key.' '.$this->exp[$exp].' '.$this->parseValue($item);
                            }
                            $whereStr .= '('.implode(' '.$likeLogic.' ',$like).')';                          
                        }
                    }else{
                        $whereStr .= $key.' '.$this->exp[$exp].' '.$this->parseValue($val[1]);
                    }
                }elseif('bind' == $exp ){ // 使用表达式
                    $whereStr .= $key.' = :'.$val[1];
                }elseif('exp' == $exp ){ // 使用表达式
                    $whereStr .= $key.' '.$val[1];
                }elseif(preg_match('/^(notin|not in|in)$/',$exp)){ // IN 运算
                    if(isset($val[2]) && 'exp'==$val[2]) {
                        $whereStr .= $key.' '.$this->exp[$exp].' '.$val[1];
                    }else{
                        if(is_string($val[1])) {
                             $val[1] =  explode(',',$val[1]);
                        }
                        $zone      =   implode(',',$this->parseValue($val[1]));
                        $whereStr .= $key.' '.$this->exp[$exp].' ('.$zone.')';
                    }
                }elseif(preg_match('/^(notbetween|not between|between)$/',$exp)){ // BETWEEN运算
                    $data = is_string($val[1])? explode(',',$val[1]):$val[1];
                    $whereStr .=  $key.' '.$this->exp[$exp].' '.$this->parseValue($data[0]).' AND '.$this->parseValue($data[1]);
                }else{
                    E(L('_EXPRESS_ERROR_').':'.$val[0]);
                }
            }else {
                $count = count($val);
                $rule  = isset($val[$count-1]) ? (is_array($val[$count-1]) ? strtoupper($val[$count-1][0]) : strtoupper($val[$count-1]) ) : '' ; 
                if(in_array($rule,array('AND','OR','XOR'))) {
                    $count  = $count -1;
                }else{
                    $rule   = 'AND';
                }
                for($i=0;$i<$count;$i++) {
                    $data = is_array($val[$i])?$val[$i][11]:$val[$i];
                    if('exp'==strtolower($val[$i][0])) {
                        $whereStr .= $key.' '.$data.' '.$rule.' ';
                    }else{
                        $whereStr .= $this->parseWhereItem($key,$val[$i]).' '.$rule.' ';
                    }
                }
                $whereStr = '( '.substr($whereStr,0,-4).' )';
            }
        }else {
            //对字符串类型字段采用模糊匹配
            $likeFields   =   $this->config['db_like_fields'];
            if($likeFields && preg_match('/^('.$likeFields.')$/i',$key)) {
                $whereStr .= $key.' LIKE '.$this->parseValue('%'.$val.'%');
            }else {
                $whereStr .= $key.' = '.$this->parseValue($val);
            }
        }
        return $whereStr;
    }

漏洞点在于:

elseif('exp' == $exp ){ // 使用表达式
    $whereStr .= $key.' '.$val[1];
}

而$exp变量来自:

$exp = strtolower($val[0]);

$val变量是由函数传参传入进来的.如果$val[0]为exp的话,就直接将$val[1]拼接进入wherestr中,而wherestr最终会进入数据库,导致了注入漏洞。

案例(由于本人没有找到现在比较流行的CMS使用3.2,所以在github上找了一套几年前的CMS):

下载地址:https://codeload.github.com/GreenCMS/GreenCMS/zip/beta

漏洞代码:

    public function listPost($type = 'single')
    {

        $PostsList = new PostsLogic();
        $post_list = $PostsList->getList(get_opinion('feed_num'), $type, 'post_date desc', true);
        $RSS = new RSS (get_opinion('title'), '', get_opinion('description'), ''); // 站点标题的链接
        foreach ($post_list as $list) {
            $RSS->addItem(
                $list ['post_title'],
                'http://' . $_SERVER["SERVER_NAME"] . get_post_url($list),
                $list ['post_content'],
                $list ['post_date']);
        }

        $RSS->display();
    }

这里跟入PostsLogic下的getList方法:

    public function getList($limit = 20, $type = 'single', $order = 'post_date desc',
                            $relation = true, $info_with = array(), $ids = array(), $except_field = '')
    {
        $info = $info_with;
        if ($type != 'all') $info['post_type'] = $type;
        //if ($type != 'all') $info['post_type|post_template'] = $type;
        if (!array_key_exists('post_status', $info)) $info['post_status'] = 'publish';
        if (!empty($ids)) $info['post_id'] = array('in', $ids);

        $post_list = D('Posts')->field($except_field, true)->where($info)->order('post_top desc ,' . $order)
            ->limit($limit)->relation($relation)->select();
        return $post_list;
    }

可以发现这句代码对info变量进行了赋值:

if ($type != 'all') $info['post_type'] = $type;

然后紧接着讲info带入了where函数:

$post_list = D('Posts')->field($except_field, true)->where($info)->order('post_top desc ,' . $order)
            ->limit($limit)->relation($relation)->select();

payload1:

http://127.0.0.1/index.php?m=home&c=feed&a=listPost&type=1

SQL语句:
21.png

然后使用上面框架自带的漏洞:
payload:

http://127.0.0.1/index.php?m=home&c=feed&a=listPost&type[0]=exp&type[1]=xxx

SQL语句:
22.png
加入单引号:
23.png

成功注入进来了,剩下的也仅仅是构造SQL语句而已了,不进行演示。

本文链接:

http://www.f4ckweb.top/index.php/archives/62/
1 + 6 =
快来做第一个评论的人吧~