QCMS建站CMS的XSS漏洞【通过】
2020-04-10 20:05:11 Author: forum.90sec.com(查看原文) 阅读量:327 收藏

影响范围

前端URL

http://localhost:8088/guest/index.html

后端URL

http://localhost:8088/backend/guest.html

证明图片

前端

image

后端

image

XSS语句:

//经典语句,哈哈!

前端URL 可触发方式:

http://localhost:8088/guest/index.html 的标题、内容、email、姓名 四个点

其中:姓名、标题、内容、eail可以前端触发

标题、姓名、邮箱后端可以触发

后端可触发XSS点http://localhost:8088/backend/guest.html

只有标题可以触发。

审计证明

追踪数据是否过滤或者转义

1、填写XSS语句

image

2、查看数据库表中是否转义

image

发现改数据存储,并且没有转义。

那么出现XSS的地方就是因为打印的时候没有过滤。

追踪数据输出点,以及输出方式

前端数据追踪,该URL为http://localhost:8088/guest/index.html

真实路径为index.php/guest/index.html

即采用了MVC结构。那么GUEST就为控制器。Index为方法。找到guest的index方法

路径为:System/Controller/guest.php

Index方法为

public function index_Action($page = 0){

        if(!empty($_POST)){

            foreach($_POST as $k => $v){

                $_POST[$k] = trim($v);

            }

            if(empty($_POST['title'])){

                exec_script('alert("标题不能为空");history.back();');exit;

            }

            if(empty($_POST['name'])){

                exec_script('alert("姓名不能为空");history.back();');exit;

            }

            if(empty($_POST['email'])){

                exec_script('alert("邮箱不能为空");history.back();');exit;

            }

            if(empty($_POST['content'])){

                exec_script('alert("留言内容不能为空");history.back();');exit;

            }

            $result = $this->_guestObj->insert(array('title' => $_POST['title'], 'name' => $_POST['name'], 'email' => $_POST['email'], 'content' => $_POST['content'], 'addtime' => time()));

            if($result){

                exec_script('window.location.href="'.url(array('guest', 'index')).'"');exit;

            }else{

                exec_script('alert("留言失败");history.back();');exit;

            }

        }

        $temp['rs'] = array('name' => '客户留言');

        $count = 0;

        $this->pageNum = 6;

        $temp['module_name'] = 'guest';

        $offset = ($page <= 0) ? 0 : ($page - 1) * $this->pageNum;

        $temp['guestRs'] = $this->_guestObj->selectAll(array($offset, $this->pageNum), $count, array());

        $temp['page'] = $this->page_bar($count[0]['count'], $this->pageNum, url(array('guest', 'index', '{page}' )), 9, $page);

        $this->load_view('template/'.$this->web['tempname'].'/guest', $temp);

    }

前面的if可以不看,因为IF(!EMPTY($_POST))为提交变量才触发。默认展示是在下面。

由上面的代码可以看出。$temp就是留言系统查询的数据。该数据最后通过$this->load_view()方法加载并传入/guest视图。

该视图路径为

System/Template/default/guest.php

该视图第32行上下

<?php

      foreach($guestRs as $k => $v){

      ?>

      <blockquote>

  <p><?=$v['title']?></p>

  <footer><?=$v['content']?><br><br><?=$v['name']?> <cite ><?=date('Y-m-d H:i:s', $v['addtime'])?></cite></footer>

</blockquote>

直接输出$guestRS

该变量为$temp['guestRs']

没有过滤任何参数。

追踪查询(sql)过程是否过滤

小结:数据库没过滤没编码。查询过后显示没过滤没编码。那么只需要确定该查询语句也没有过滤即可证明该处存在存储型XSS了。

追踪

$this->_guestObj->selectAll(array($offset, $this->pageNum), $count, array());

$this->guestObj 来自于外部加载的类:

System/Controller/guest.php/guest.php第7行(VSCODE追踪会追踪到后台使用的这个类。行数可能就不一样了。但是使用的类是同一个类,所以后台XSS这步我就省了先)

$this->_guestObj = $this->load_model('QCMS_Guest');

找到该class

Lib/Model/QCMS_Guest.php 49行

public function selectAll($limit = '', &$count, $cond_arr='', $sort = array('id' => 'DESC')){

        $count = $this->exec_select($cond_arr, 'COUNT(*) AS count', 0,  0, '', '', 0);

        return $this->exec_select($cond_arr, '*', 0,  0, $limit, $sort, 0);

    }

追踪exec_select 到 Lib/Config/Db_pdo.php 73行

public function exec_select($cond_arr=array(), $field='*', $tb_name = 0,  $index = 0, $limit = '', $sort='', $fetch = 0, $isDebug = 0){

        $tb_name = empty($tb_name) ? 0 : $tb_name;

        $limit_str = !is_array($limit) ? $limit : ' limit '.$limit[0].','.$limit[1].'';

        $sort_str = $this->sort($sort);

        $sql = "SELECT ".$field." FROM ".parent::$s_dbprefix[parent::$s_dbname].$this->p_table_name[$tb_name].$this->get_sql_cond($cond_arr).$sort_str.$limit_str."";

        ! $isDebug || var_dump ( $sql );

        if($fetch == 1){

            return $this->q_select($sql, 1);

        }

        if(empty($index)){

            return $this->q_select($sql);

        }else{

            return $this->set_index($this->q_select($sql), $index);

        }

    }

该方法内部存在的方法有 get_sql_cond() q_select()都存在于Lib/Config/Db.php

值得注意的是再QCMS_Guest最开始的时候传入的第三个参数为空

如下:

image

所以在DB.PHP的get_sql_cond()方法中,直接返回了空

image

再返回DB_PDO.Php中77行,最后拼接的数据sql语句为

$sql = "SELECT ".$field." FROM ".parent::$s_dbprefix[parent::$s_dbname].$this->p_table_name[$tb_name].$this->get_sql_cond($cond_arr).$sort_str.$limit_str."";

Select (变量$conut被赋值为空,使用默认值) from 数据库.表

最后一步了,审核你坚持住!

获得查询语句,看看查询结果是否过滤

在Lib/Config/Db.php76行

image

没有任何过滤

并返回结果。 至此。证明了从数据库读取数据的时候selectAll()方法不执行过滤。

前端XSS小结

前端输入直接到数据库不过滤。后端查询后直接再页面展示。查询数据不进行数据过滤

存储XSS成立。

后端XSS审计证明

后端的URL为http://localhost:8088/backend/guest.html

使用的backend下的guest控制器

文件路径Syetem/Controller/backend/guest.php

方法为index 在9-17行

如下

public function index_Action($page = 0){

        $count = 0;

        $this->pageNum = 6;

        $temp['module_name'] = 'guest';

        $offset = ($page <= 0) ? 0 : ($page - 1) * $this->pageNum;

        $temp['rs'] = $this->_guestObj->selectAll(array($offset, $this->pageNum), $count, array());

        $temp['page'] = $this->page_bar($count[0]['count'], $this->pageNum, url(array('guest', 'index', '{page}' )), 9, $page);

        $this->load_view('backend/guest/index', $temp);

    }

似曾相识有没有。加载的视图为BACKEND/GUEST/INDEX

文件目录

System/View/backend/guest/index.php

image

$rs直接foreach 不做处理。

那么它的查询获取书的类呢?

$temp['rs'] = $this->_guestObj->selectAll(array($offset, $this->pageNum), $count, array());

image

在第6行定义,有没有很熟悉。 没错!!!!和前端的一样的。所以查询就不做分析了。直接给结论,没有过滤。

思考以及一些调皮。

可以从前后端总结,其实输入的时候没有过滤。 查询的时候使用的selectAll()方法默认数据库内的数据是安全的。视图也默认该数据是安全的。而只要处理好其中的一个就可以避免。

解决办法。在前端输入的时候试着做一些HTML实体编码之类的东西。

重后端开始。后端暂时了标题,姓名,邮箱 都是可控参数。那么其他的也存在XSS注入吗?做一下测试,再次不再证明了

image

前端后端,同样存在。

至此证明结束。


文章来源: https://forum.90sec.com/t/topic/944
如有侵权请联系:admin#unsafe.sh