代码审计-YXcms1.4.7

题外: 今天是上班第一天,全都在做准备工作,明天开始正式实战做事。

看着周围稍年长的同事和老大做事,自己的感觉就是自己还是差的很多很多,自己只能算个废物。

学无止境,我这样的垃圾废物就该多练,保持战斗的欲望,嗯。

 

今晚回来了没什么事,跟着网上的师傅复现审计一套awd的源码吧。

看了下也是很简单的一套源码,不过在某的小地方可能不容易发现,所以这篇自己把过程写的尽可能详细、对新手友好。

代码审计-YXcms1.4.7_第1张图片

 

 

 

 

0x01 前台储存型XSS

在留言板处存在储存型XSS

代码审计-YXcms1.4.7_第2张图片

 

 

来看看代码  存储xss,首先是前台接收数据没有过滤,然后是后台存储后输出的时候也没有过滤,两个点,一个输入,一个输出 ,都没过滤造成xss存储漏洞。

首先是前台接收:

提交留言的时候我们请求的地址是:

POST /YXCMS_v1.4.7/index.php?r=default/column/index&col=guestbook HTTP/1.1

对应文件的地址:

default模块中column控制器的index()方法.快速审计我们只用找到接收插入数据的地方即可:

if(empty($ename)) throw new Exception('栏目名不能为空~', 404);
        $sortinfo=model('sort')->find("ename='{$ename}'",'id,name,ename,path,url,type,deep,method,tplist,keywords,description,extendid');
        $path=$sortinfo['path'].','.$sortinfo['id'];

这里$sortinfo=model('sort')->find("ename='{$ename}'",'id,name,ename,path,url,type,deep,method,tplist,keywords,description,extendid');

 这里其实很简单,不过有的新手朋友看不懂一些细节,这里就记录的细一些:

先看这个model('sort') 追踪到函数:

function model($model){
    static $objArray = array();
    $className = $model . 'Model';
    if( !is_object($objArray[$className]) ){
        if( !class_exists($className) ) {
            throw new Exception(config('_APP_NAME'). '/' . $className . '.php 模型类不存在');
        }
        $objArray[$className] = new $className();
    }
    return $objArray[$className];
}

这个函数就是找到对应的model文件,如果存在该model的类就新建对象,mvc都是这个套路,这里就是sortModel,

然后是:调用这个函数的find("ename='{$ename}'",'id,name,ename,path,url,type,deep,method,tplist,keywords,description,extendid');函数,我们继续来看看find(),

追踪到model的基类(protected\base\model\model.php):

public function find($condition = '', $field = '', $order = ''){
        return $this->model->table($this->table, $this->ignoreTablePrefix)->field($field)->where($condition)->order($order)->find();
    }

这里其实非常非常明显了,table方法指定操作的表名,field操作指定的字段,where是指定条件,order是排序,

这里->find()函数是cpModel的find函数,内部调用的select函数,将封装在options数组中的信息进行拼接查询。

//查询多条信息,返回数组
     public function select() {
        $table = $this->options['table'];    //当前表
        $field = $this->options['field'];    //查询的字段
        $where = $this->_parseCondition();    //条件
        return $this->query("SELECT $field FROM $table $where", array(), true);
     }

 

关于这里如果 知道model是cpModel和相应函数的作用,写了下追踪过程:

这里调用了自己的model属性的table方法,我们再转到构造函数来看,虽然有点绕,但是别晕,mvc都是这样,清楚了后一点不难。

public function __construct( $database= 'DB', $force = false){
        $this->model = self::connect( config($database), $force);
        $this->db = $this->model->db;
    }

这里model属性是自己connect函数的返回值,看看connect函数;

static public function connect($config, $force=false){
        static $model = NULL;
        if( $force==true || empty($model) ){
            $model = new cpModel($config);
        }
        return $model;
    }

不用追求细节末枝,我们只需要看到这里新建了cpModel,接着看

$model = new cpModel($config);

 

 

ok回到我们这里:

public function find($condition = '', $field = '', $order = ''){
        return $this->model->table($this->table, $this->ignoreTablePrefix)->field($field)->where($condition)->order($order)->find();
    }

调用了model对象的table方法,这里model对象我们已经知道是cpModel这个对象了,来看看它的table方法:

public function table($table, $ignorePre = false) {
        if ( $ignorePre ) {
            $this->options['table'] = $table;
        } else {
            $this->options['table'] = $this->config['DB_PREFIX'] . $table;
        }
        return $this;
    }

这里我们是走到:

$this->options['table'] = $this->config['DB_PREFIX'] . $table;

注意这里是调用model对象的table方法,也就是cpModel这个对象,而不是基类model,这里容易搞混淆哦~~~

而这里就大致知道我们插到哪个数据库了,config方法是定义数据库前缀,表前缀也就是我们当初传入的sort,操作的表也就是yx_sort,其他的函数追踪也大同小异 ,我们再回到当初的:

if(empty($ename)) throw new Exception('栏目名不能为空~', 404);
        $sortinfo=model('sort')->find("ename='{$ename}'",'id,name,ename,path,url,type,deep,method,tplist,keywords,description,extendid');
        $path=$sortinfo['path'].','.$sortinfo['id'];

这里分析了作用 ,我们接着往下看:

$deep=$sortinfo['deep']+1;
        $this->col=$ename;
        switch ($sortinfo['type']) {
            case 1://文章
                $this->newslist($sortinfo,$path,$deep);
                break;
            case 2://图集
                $this->photolist($sortinfo,$path,$deep);
                break;
            case 3://单页
                $this->page($sortinfo,$path,$deep);
                break;
            case 4://应用
                
                break;
            case 5://自定义
                
                break;
            case 6://表单
                $this->extend($sortinfo,$path,$deep);
                break;
            default:
                throw new Exception('未知的栏目类型~', 404);
                break;
        }

这里根据不同的type值调用不同的方法,我们来看看我们留言板的数据库是插在哪个表的。

而这个type是查询返回的字典,我们提交的col的值是guestbook,对应的type是6,所以我们执行的是

case 6://表单
                $this->extend($sortinfo,$path,$deep);
                break;

代码审计-YXcms1.4.7_第3张图片

看到这里的extend()函数,我们跟踪:

代码审计-YXcms1.4.7_第4张图片

到这里我们只关注插入相关的操作:

$list=model('extend')->Extselect($tablename,"ispass='1'",'','id desc',$limit);

 

又是和上面新建model对象一样,这次是extendModel的extselect,我们跟踪看看是否是插入操作:

代码审计-YXcms1.4.7_第5张图片

 

这里还是最后调用的cpModel的insert方法,中间没有任何过滤。到这里前台接收的代码就分析完成,我们再来看看后台从数据库读取数据的代码.

 

 

 

然后是后台读取:

 

http://192.168.5.149/YXCMS_v1.4.7/index.php?r=admin/index/login

admin 123456

代码审计-YXcms1.4.7_第6张图片

 

 

 

代码审计-YXcms1.4.7_第7张图片

 

 

来看看代码部分

文件位置protected/apps/admin/controller/extendfieldController.php:

//自定义表中信息编辑
    public function mesedit()
    {
        $tableid=intval($_GET['tabid']);
        if(!$this->checkConPower('extend',$tableid)) $this->error('您没有权限管理此独立表内容~');
        $id=intval($_GET['id']);//信息id
        if(empty($tableid) || empty($id) ) $this->error('参数错误~');
        $tableinfo = model('extend')->select("id='{$tableid}' OR pid='{$tableid}'",'id,tableinfo,name,type,defvalue','pid,norder DESC');
        if(empty($tableinfo)) $this->error('自定义表不存在~');
        if (!$this->isPost()) {
           $info=model('extend')->Extfind($tableinfo[0]['tableinfo'],"id='{$id}'");
           $this->info=$info;
           $this->tableid=$tableid;
           $this->id=$id;
           $this->tableinfo=$tableinfo;
           $this->display();
        }else{
           for($i=1;$i<count($tableinfo);$i++){
               if(is_array($_POST[$tableinfo[$i]['tableinfo']]))
                 $data[$tableinfo[$i]['tableinfo']]=implode(',',$_POST[$tableinfo[$i]['tableinfo']]);
               else
                 $data[$tableinfo[$i]['tableinfo']]=html_in($_POST[$tableinfo[$i]['tableinfo']]);
           }
           if(model('extend')->Extup($tableinfo[0]['tableinfo'],"id='{$id}'",$data)) $this->success('修改成功~',url('extendfield/meslist',array('id'=>$tableid)));
           else $this->error('信息修改失败~');
         }
    }

 

发现读取数据也没有任何过滤和转移。造成存储xss。

 

 

 

 

0x02 后台模版getshell

代码审计-YXcms1.4.7_第8张图片

 

这里可以直接对php文件进行操作。。

并且没有任何过滤措施:
代码审计-YXcms1.4.7_第9张图片

代码审计-YXcms1.4.7_第10张图片

 

 

 

0x03  任意文件删除

文件位置protected/apps/admin/controller/photoController.php:

public function delpic()
    {
        if(empty($_POST['picname'])) $this->error('参数错误~');
        $picname=$_POST['picname'];
        $path=$this->uploadpath;
        if(file_exists($path.$picname))
          @unlink($path.$picname);
        else{echo '图片不存在~';return;} 
        if(file_exists($path.'thumb_'.$picname))
           @unlink($path.'thumb_'.$picname);
        else {echo '缩略图不存在~';return;}
        echo '原图以及缩略图删除成功~';
    }

代码首先判断是否含有POST参数picname,然后赋给$picname,获取路径uploadpath=ROOT_PATH.'upload/photos/',使用file_exists()函数判断文件是否存在,然后再unlink()进行删除。参数$picname完全可控,导致任意文件删除:

 

 

yxcms代码不是很绕,主要还是心细

 

你可能感兴趣的:(代码审计-YXcms1.4.7)