ssycms代码审计:后台存储型XSS
2020-02-19 09:56:24 Author: xz.aliyun.com(查看原文) 阅读量:199 收藏

审计了一个比较小众的cms,官网提供了两个版本,基础版和ugc投稿版版,后者相对前者提供了用户注册投稿等功能,以下漏洞针对两个版本都存在

网站介绍

SSYCMS内容管理系统采用热门框架,方便开发者二次开发系统,前台采用Bootstrap4.x,后台采用vue2.x、iview3.x。系统使用Thinkphp5.x框架,架构模式为PHP+MYSQL

建站

只需要注意手动创建数据库,其他没什么

漏洞演示

首先登陆后台

在内容->全部内容处添加内容即可,需要注意标题和标签字段可以直接触发,描述和关键词字段需要闭合,内容字段则不能触发

结果如图所示

同样可以触发的还有功能->单个页面处,这里也可以自定义页面的路由,同样标题直接触发,关键词和描述需要闭合

如图所示

同样友情链接处也存在触发点,这里名称网址描述三个字段都可以直接触发

代码审计

以第一处为例

通过路由找到调用方法

POST /index.php?s=/article/ApiAdminArticle/itemAdd HTTP/1.1
public function itemAdd()
    {
        $data = $this->request->post();
        if (!isset($data['title']) || !$data['title']) {
            return jsonError('请输入标题');
        }
        $itemInfo = db($this->item)->where('title',$data['title'])->find();
        if ($itemInfo) {
            return jsonError('标题已存在');
        }
        $fieldList = input('post.fieldList');
        $fieldList = json_decode($fieldList,true);
        $res = model($this->itemModelNameSpace)->itemAdd($data,$fieldList);
        if ($res) {
            return jsonSuccess('操作成功');
        } else {
            return jsonError('操作失败');
        }
    }

通过post方法获取数据,跟踪

public function post($name = '', $default = null, $filter = '')
    {
        if (empty($this->post)) {
            $content = $this->input;
            if (empty($_POST) && false !== strpos($this->contentType(), 'application/json')) {
                $this->post = (array) json_decode($content, true);
            } else {
                $this->post = $_POST;
            }
        }
        if (is_array($name)) {
            $this->param       = [];
            $this->mergeParam  = false;
            return $this->post = array_merge($this->post, $name);
        }
        return $this->input($this->post, $name, $default, $filter);
    }

继续跟踪input

public function input($data = [], $name = '', $default = null, $filter = '')
    {

         ......

        // 解析过滤器
        $filter = $this->getFilter($filter, $default);

        if (is_array($data)) {
            array_walk_recursive($data, [$this, 'filterValue'], $filter);
            reset($data);
        } else {
            $this->filterValue($data, $name, $filter);
        }

        if (isset($type) && $data !== $default) {
            // 强制类型转换
            $this->typeCast($data, $type);
        }
        return $data;
    }

因为过滤器参数为空,所以直接返回数据

回到itemEAdd方法,除去判断之外,调用模块中的itemEdit方法,其中$this->itemModelNameSpace值为app\article\model\Articles,继续跟踪

鉴于代码比较长,只贴出关键部分

......

    $uuid = uuid();
        $imgUrl  = $paramData['img_url'] ? $paramData['img_url'] : getImgUrlByContent($paramData['content']);
        $paramData['description'] = $paramData['description'] ? $paramData['description'] : mb_substr(str_replace('"','',deleteHtml($paramData['content'])),0,88,'utf-8');
        $paramData['keywords'] = $paramData['keywords'] ? $paramData['keywords'] : $paramData['tags'];
        $indexId = db($this->item)->insertGetId(array (
            'uuid' => $uuid,
            'cid' => $paramData['cid'],
            'uid' => $paramData['uid'],
            'views' =>  $paramData['views'] ? $paramData['views'] : 0,
            'is_recommend' => $paramData['is_recommend'] ? $paramData['is_recommend'] : 0,
            'title' =>  $paramData['title'],
            'img_url' => $imgUrl,
            'publish_time' => $paramData['publish_time'] ? $paramData['publish_time'] : time(),
            'description' => $paramData['description'],
            'keywords' => $paramData['keywords'],
        ));
        $content = $paramData['content'];
        db($this->itemContent)->insert(array(
            'id' => $uuid,
            'content' => htmlspecialchars($content),
        ));

    ......

可以看到标题等参数没有经过过滤直接放到了数据库中,而内容则经过了过滤放到了另一个表中,所以不能触发

从数据库中能直观的看到这一点

UGC版还提供了用户投稿功能,但并没有出现XSS漏洞,原因也很简单

public function itemAdd()
    {
        $title = Htmlp::htmlp(input('post.title'));
        $content = Htmlp::htmlp(input('post.content'));
        $cid =  Htmlp::htmlp(input('post.cid'));
        $tags =  Htmlp::htmlp(input('post.tags'));
        $img_url =  Htmlp::htmlp(input('post.img_url'));
        $description = Htmlp::htmlp(input('post.description'));
        $keywords = Htmlp::htmlp(input('post.keywords'));
        $cid = $cid ? $cid : 0;

        ......

    }

跟进htmlp方法

class Htmlp
{
    static public function htmlp($dirty_html)
    {
       $config = HTMLPurifier_Config::createDefault();
       $purifier = new HTMLPurifier($config);
       return $purifier->purify($dirty_html);
   }

}

调用时利用HTMLPurifier进行了过滤,至于为什么管理员发布的文章没有过滤。。。可能是觉得管理员不会搞破坏?

漏洞危害也不大。。。聊胜于无


文章来源: http://xz.aliyun.com/t/7227
如有侵权请联系:admin#unsafe.sh