【旧论坛文章】Catfish CMS 审计之存储XSS

表哥们,新年好,菜鸟初学,分享一下,大过年的,大牛勿喷,清清嗓子,大吼一声:“审计牛,带我玩转审计好么”。

话入正题,本次审计的是Catfish CMS V4.2.3(最新版),看了一下,使用的是ThinkPHP框架,框架的版本是5.X,既然是代码审计,下面就用代码说话。

漏洞文件:/application/user/controller/Index.php(93-133行)

public function touxiang()
{
    $this->checkUser();
    if(Request::instance()->isPost())
    {
        //验证输入内容
        $rule = [
            'avatar' => 'require'
        ];
        $msg = [
            'avatar.require' => Lang::get('Please upload your avatar')
        ];
        $data = [
            'avatar' => Request::instance()->post('avatar')
        ];
        $validate = new Validate($rule, $msg);
        if(!$validate->check($data))
        {
            $this->error($validate->getError());//验证错误输出
            return false;
        }
        $avatar = Db::name('users')
            ->where('id', Session::get($this->session_prefix.'user_id'))
            ->field('avatar')
            ->find();
        $yuming = Db::name('options')->where('option_name','domain')->field('option_value')->find();
        //删除原图
        if(Request::instance()->post('avatar') != $avatar['avatar'])
        {
            $yfile = str_replace($yuming['option_value'],'',$avatar['avatar']);
            if(!empty($yfile)){
                $yfile = substr($yfile,0,1)=='/' ? substr($yfile,1) : $yfile;
                $yfile = str_replace("/", DS, $yfile);
                @unlink(APP_PATH . '..'. DS . $yfile);
            }
        }
        $data = ['avatar' => Request::instance()->post('avatar')];//看这里,对,就是这里
        Db::name('users')
            ->where('id', Session::get($this->session_prefix.'user_id'))
            ->update($data);
    }
    $this->receive();
    $this->assign('active', 'touxiang');
    $view = $this->fetch();
    return $view;
}

在代码中看到post传递的avatar参数值并没有进行处理就直接更新到数据库,我们要找到avatar的输出点,首先要知道avatar代表的什么,作为看过《阿凡达》的我,拍案而起,这不是“阿凡达”吗?后来想一想“阿凡达”跟它好像没什么关系,其实呢,这里并不是什么“阿凡达”,而是头像,接下来我们就找下哪里会出现头像。

第一处输出模板:/application/user/view/index/index.html(8-10行)

<div class="col-md-3">
  <img src="{empty name="user.avatar"}{$domain}public/common/images/headicon_128.png{else /}{$user.avatar}{/empty}" class="img-responsive">
</div>

在代码中可以看出,如果用户修改过头像,浏览器渲染后的代码为:<img src="user.avatar" class="img-responsive">,既然user.avatar可控且没有进行处理,那么通过构造便可实现存储XSS。有些表哥可能会说:“这不是个人中心?自己搞自己有毛线用呀。”,那么我们接着往下看,看一下第二处输出。

第二处输出模板:/public/blog/article.html(79-81行)

<div class="col-md-2">
    <img src="{empty name="vo.touxiang"}{$domain}public/common/images/headicon_128.png{else /}{$vo.touxiang}{/empty}" class="img-circle img-responsive">
</div>

第二处输出的代码跟第一处输出的代码差不多,这里就不再详细说明。有些表哥又要说了:“管理员在后台就管理评论了,你在这里搞有毛线用呀”,那么我们继续往下看,看一下第三处输出。

第三处输出模板:/application/admin/view/index/comments.html(39行)

<td>{if condition="$vo.avatar neq ''"}<img src="{$vo.avatar}" width="50">{/if}</td>

这一处直接搞到了后台评论管理页面,有些表哥又要说了:“后台一处受影响,这个命中率太低,能不能再来一处。”,既然表哥有要求,那就再来一处输出。

第四处输出模板:/application/admin/view/index/general.html(29行)

<td>{notempty name="$vo.avatar"}<img src="{$vo.avatar}" width="50">{/notempty}</td>

看到这里,有些表哥又要说了:“既然我们知道漏洞的存在了,那么怎么防御呢?”,接下来就看看怎么处理这个存储XSS。

在最开始的时候,已经说明了Catfish CMS使用的是ThinkPHP 5.X框架,学习过ThinkPHP框架的表哥可能都知道在config.php文件中可以设置默认全局过滤方法,我们在这里把“'default_filter'=>''”改成“'default_filter'=>'htmlspecialchars'”即可,要说明一下,ThinkPHP的默认全局过滤方法只过滤输入的数据,不对输出的数据进行过滤,为了保证原有数据输出的安全,我们同样可以对输出做htmlspecialchars处理,这里用第四处输出做一下说明,把“<td>{notempty name="$vo.avatar"}<img src="{$vo.avatar}" width="50">{/notempty}</td>”里面的“{$vo.avatar}”改为“{$vo.avatar|htmlspecialchars}”即可,其他几处不做说明。

最后再说两个问题,第一:若文章有错误之处,请各位表哥指正,第二:论坛的审计牛如果愿意开车的话,载菜鸟一程,2017一起玩转代码审计。