影响范围
前端URL
http://localhost:8088/guest/index.html
后端URL
http://localhost:8088/backend/guest.html
证明图片
前端
后端
XSS语句:
//经典语句,哈哈!
前端URL 可触发方式:
http://localhost:8088/guest/index.html 的标题、内容、email、姓名 四个点
其中:姓名、标题、内容、eail可以前端触发
标题、姓名、邮箱后端可以触发
后端可触发XSS点http://localhost:8088/backend/guest.html
只有标题可以触发。
审计证明
追踪数据是否过滤或者转义
1、填写XSS语句
2、查看数据库表中是否转义
发现改数据存储,并且没有转义。
那么出现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最开始的时候传入的第三个参数为空
如下:
所以在DB.PHP的get_sql_cond()方法中,直接返回了空
再返回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行
没有任何过滤
并返回结果。 至此。证明了从数据库读取数据的时候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
$rs直接foreach 不做处理。
那么它的查询获取书的类呢?
$temp['rs'] = $this->_guestObj->selectAll(array($offset, $this->pageNum), $count, array());
在第6行定义,有没有很熟悉。 没错!!!!和前端的一样的。所以查询就不做分析了。直接给结论,没有过滤。
思考以及一些调皮。
可以从前后端总结,其实输入的时候没有过滤。 查询的时候使用的selectAll()方法默认数据库内的数据是安全的。视图也默认该数据是安全的。而只要处理好其中的一个就可以避免。
解决办法。在前端输入的时候试着做一些HTML实体编码之类的东西。
重后端开始。后端暂时了标题,姓名,邮箱 都是可控参数。那么其他的也存在XSS注入吗?做一下测试,再次不再证明了
前端后端,同样存在。
至此证明结束。