安服仔的首次PHP代审实践

前言:


是的,我就是那个从20年10月就开始说要学php代审的那个人,拖了一年的时间终于开始步入正轨。
本文水得一批,谨慎阅读

前期准备:


确认目标为zbzcms,听说这套系统漏洞很多,且没有用到MVC架构,很适合小白练手。个人采取黑盒加白盒的方式进行代审,代码量不多,也可以考虑通读源码。

环境搭建 + rips审计工具 + burp配合xray + 数据库监控+ 文件监控

  • XSS:

漏洞位于:/zbzcms/cms/cms/admin/art.php

if(isset($_GET['aid'])){
    $aid=$_GET['aid'];
    $arts=$c_sql->select("select * from art where id={$aid}");
    $arts=$arts[0];
    $tid=$arts['tid'];
}
else{
    $aid=0;
    $paixu=50;
    if(isset($_GET['tid'])){$tid=$_GET['tid'];}
    else if(!isset($tid)){$tid=0;}
  • SSRF:

漏洞位于:/zbzcms/cms/cms/include/function.php

function getImage($url,$save_dir='',$filename='',$type=0){
    if(trim($url)==''){
        return array('file_name'=>'','save_path'=>'','error'=>1);
    }
    if(trim($save_dir)==''){
        $save_dir='./';
    }
    if(trim($filename)==''){//保存文件名
        $ext=strrchr($url,'.');
        if(!stristr($ext,'.gif') && !stristr($ext,'.jpg') && !stristr($ext,'.png')){
            return array('file_name'=>'','save_path'=>'','error'=>3);
        }
        $filename=strrchr($url,'/');
        $filename=str_ireplace('/','',$filename);
    }
    if(0!==strrpos($save_dir,'/')){
        $save_dir.='/';
    }
    //创建保存目录
    if(!file_exists($save_dir)&&!mkdir($save_dir,0777,true)){
        return array('file_name'=>'','save_path'=>'','error'=>5);
    }
    //获取远程文件所采用的方法
    if($type){
        $ch=curl_init();
        $timeout=5;
        curl_setopt($ch,CURLOPT_URL,$url);
        curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
        curl_setopt($ch,CURLOPT_CONNECTTIMEOUT,$timeout);
        $img=curl_exec($ch);
        curl_close($ch);
    }else{
        ob_start();
        readfile($url);
        $img=ob_get_contents();
        ob_end_clean();
    }
    //$size=strlen($img);
    //文件大小
    $fp2=@fopen($save_dir.$filename,'a');
    fwrite($fp2,$img);
    fclose($fp2);
    unset($img,$url);
    return $save_dir.$filename;

可以发现当$url可控,$type=0的时候,会执行readfile($url),且并没有对$url进行过滤处理,最后将其保存到$save_dir所在的位置。追踪所有引入了getImage函数的php文件,最终发现了如下代码:

//编辑栏目
if($run=='edit_type'){
    //下载远程图片到本地S
    preg_match_all('/0){
        foreach($tupians[1] as $v){
            if(strstr($v,'http://') || strstr($v,'https://')){
                $tupian_src=getImage($v,"../../upload");
                $_POST['lanmuneirong']=str_ireplace($v,$tupian_src,$_POST['lanmuneirong']);
            }
        }
    }
    //下载远程图片到本地E
    $where="id=".$_POST['id'];
    unset($_POST['id']);
    echo $c_sql->update('type',$_POST,$where);

我们可以通过post传入一个lanmuneirong的值并通过正则匹配执行getImage()函数,最终造成SSRF:

这里也可以利用不同浏览器的解析机制不同,造成存储型XSS

  • 任意文件读取:

漏洞位于:/zbzcms/cms/cms/admin/run_ajax.php

//文件
if($run=='wenjian'){
    $path=$_POST['path'];
    echo get($path);
}

然后定位get函数,动态变量可控且无任何过滤:

//读取文件方便编码处理
function get($path){
    if(!file_exists($path)){
        return '文件'.$path.'不存在!';
    }
    $get=file_get_contents($path);
    return $get;
}
  • SQL注入:

后台注入点太多了,这里就列举几例:
公共函数:

    public function digui($table,$ziduan,$tid=0,&$result=array(),$spac=0){
        $spac=$spac+1;
        $sql="select * from $table where $ziduan='$tid' order by paixu,id asc";
        $res=mysqli_query($this->link(),$sql);
        while($row=mysqli_fetch_assoc($res)){
            $row['jibie']=$spac;
            $result[]=$row;
            $row['jibie']=$spac;
            $this->digui($table,$ziduan,$row['id'],$result,$spac);
        }
        return $result; 

后台注入点一:/zbzcms/cms/cms/admin/art_list.php

if(isset($_GET['tid'])){
    $tid=$_GET['tid'];
    $tid_zis=$c_sql->select("select id from type where tid={$tid}");

后台注入点二:/zbzcms/cms/api/caiji.php

//审核并自动更新
else if($run=='shenhe'){
    $tid=$_GET['lanmu'];
    $up_arr=array('fabushijian',time());
    $ids=$c_sql->select("select id from art where (fabushijian=1 and tid={$tid}) limit 1");
    if(isset($ids[0]['id'])){$id=$ids[0]['id'];}
    else{exit('待审核文章库没有文章了!');}
    $up_arr=array('fabushijian'=>time());
    $up=$c_sql->update('art',$up_arr,"id={$id}");
    $up=1;
    if($up!=1){
        exit('审核系统出错');
    }
}
else{
    exit("run参数有误");

后台注入点三:/zbzcms/cms/cms/admin/run_ajax.php

//新增栏目提交
if($run=='type_add'){
    $_POST['fulanmumingcheng']=pinyin($_POST['lanmumingcheng'],$lx='head');
    $_POST['youhuabiaoti']=$_POST['lanmumingcheng'];
    $_POST['youhuaguanjianci']=$_POST['lanmumingcheng'];
    $_POST['youhuazhaiyao']=$_POST['lanmumingcheng'];
    $_POST['paixu']=50;
    $jibie=$_POST['jibie'];
    unset($_POST['jibie']);
    $typeid=$c_sql->insert('type',$_POST);
    
    if($_POST['tid']==0){
        $red['id']=$typeid;
        $red['tid']=$_POST['tid'];
        $red['jibie']=$jibie+1;
        $red['weizhi_id']=0;
    }
    else{
        $diguis=$c_sql->digui('type','tid',$_POST['tid'],$result); //$tid可控

后台注入点四:此处会进行delete操作

//删除联动
if($run=='delliandong'){
    $id=$_POST['id'];
    $liandongids=$c_sql->digui('liandong','lid',$id,$result,$spac=0);
    $ids[]=$id;
    if(count($liandongids)>0){
        foreach($liandongids as $arr){
            $ids[]=$arr['id'];
        }   
    }
    $id=implode(',',$ids);
    $sql="delete from liandong where id in ({$id})";
    $res=$c_sql->zhixing($sql);
    if($res==1){
        echo json_encode($ids);
    }
    else{
        exit(0);
    }
}

后台注入点五:

//增或改
if($run=='addedit'){
    $table=$_GET['table'];
    $where="id=".$_POST['id'];
    $cha=$c_sql->select("select id from $table where $where");
    if(isset($cha[0]['id'])){
        $res=$c_sql->update($table,$_POST,$where);
    }
    else{
        $res=$c_sql->insert($table,$_POST);
    }
    echo $res;
  • 任意文件删除:

触发点一:/zbzcms/cms/cms/include/function.php

/*删除目录及目录下所有文件或删除指定文件($path待删除目录路径,$delDir1或true删除目录,0或false则只删除文件保留目录*/
function delDirAndFile($path, $delDir = FALSE) {
    if($path=='' || $path=='/' || $path=='./' || $path=='../' || $path=='../../' || $path=='../../../'){
        exit('严禁该操作');
    }
    $handle = opendir($path);
    if ($handle) {
        while (false !== ( $item = readdir($handle) )){
            if ($item != "." && $item != "..")
                is_dir("$path/$item") ? delDirAndFile("$path/$item", $delDir) : unlink("$path/$item");
        }
        closedir($handle);
        if ($delDir)
            return rmdir($path);
    }else {
        if (file_exists($path)) {
            return unlink($path);
        } else {
            return false;
        }
    }
}
//删除文件
if($run=='delpath'){
    $path=$_POST['path'];
    delDirAndFile($path, $delDir = true);

$delDir为真时,会删除整个目录内容

触发点二:/zbzcms/cms/cms/include/up.php

//删除文件
if($run=='del'){
    $url=$_POST['url'];
    if(!unlink($url)){
        echo 0;
    }
    else{
        echo 1;
    }
}

触发点三:/zbzcms/cms/cms/include/upload.php

//删除
else if(isset($_GET['del'])){
    $src=$_GET['del'];
    if(!unlink($src)){
        echo 0;
    }
    else{
        echo 1;
    }
    exit;
}
  • 任意文件上传:
  • 上传点一:/zbzcms/cms/cms/admin/ajax.php

//图片上传
if($run=='youad_pic'){
    $path='../../upload/';
    $path_res='../../upload/';
    $res='';
    foreach($_FILES as $i=> $arr){
        $tmp_name=$arr['tmp_name'];//临时文件
        
        if($data_pic_name==0){
            //后缀
            $houzhuis=explode('.',$arr['name']);
            $houzhui=$houzhuis[count($houzhuis)-1];
            $pathurl=$path.time().'_'.$i.'.'.$houzhui;
        }
        else{
            $pathurl=$path.$arr['name'];
        }
        
        is_dir($path) OR mkdir($path, 0777, true);//文件夹不存在创建文件夹
        $pathurl=iconv("UTF-8","gb2312",$pathurl);//目标路径
        if(move_uploaded_file($tmp_name,$pathurl)){
            $pathurl_res=str_ireplace($path,$path_res,$pathurl);
            
            $res.=$pathurl_res;
        }
    }
    print_r($_FILES);
    echo $res;
//图片命名,0改名,1原名
if(isset($_GET['data_pic_name'])){$data_pic_name=$_GET['data_pic_name'];}
else{$data_pic_name='0';}
  • 上传点二:zbzcms/cms/cms/include/upload.php
if(isset($_FILES) && isset($_GET['path']) && isset($_GET['name']) && isset($_GET['id'])){
    $path = $_GET['path'];//文件上传根目录
    $name = $_GET['name'];//文件命名规则
    $id = $_GET['id'];//上传插件的id
    if (!file_exists($path)) {mkdir($path, 0777, true);}//上传目录不存在则创建
    $res='';//返回的参数
    $i=1;//递增的文件名
    foreach($_FILES as $arr){
        //文件名S
        if($name==1){$file_name=$arr['name'];}
        else{
            if($arr['type']=='image/png'){$file_name=time().$i.'.png';}
            if($arr['type']=='image/jpeg'){$file_name=time().$i.'.jpg';}
            if($arr['type']=='image/gif'){$file_name=time().$i.'.gif';}
        }
        //文件名E
        $tmp_name=$arr['tmp_name'];//临时文件
        $url=$path.$file_name;//新文件路径
        if(move_uploaded_file($tmp_name,$url)){
            $res.="";
        }
        $i++;
    }
    print_r($_FILES);
    echo $res;

可通过动态变量$path将文件上传至任意处,当$name=1时即可上传任意文件:

  • 上传点三:zbzcms/cms/cms/include/up.php
//上传
if($run=='file'){
    $res=array();
    $path=$_GET['path'];//上传的路径
    $filename=$_GET['filename'];//1不更改,0更改
    is_dir($path) OR mkdir($path, 0777, true);//文件夹不存在创建文件夹
    foreach($_FILES as $k=>$arr){
        $path=$_GET['path'];//上传的路径
        $name=$arr['name'];//文件名
        $type=$arr['type'];//文件类型
        $tmp_name=$arr['tmp_name'];//临时文件
        $size=$arr['size'];//文件大小
        
        /*目标地址*/
        if($filename==1){
            $path.=$name;
        }
        else{
            $path.=time().$k.hz($name);
        }
        
        /*上传*/
        if(move_uploaded_file($tmp_name,$path)){
            $res[]=$path;
        }
    }
    echo json_encode($res);
}
  • 上传点四:/zbzcms/cms/cms/zbzedit/php/zbz.php
 $arr){
        $tmp_name=$arr['tmp_name'];//临时文件
        
        if($data_pic_name==0){
            //后缀
            $houzhuis=explode('.',$arr['name']);
            $houzhui=$houzhuis[count($houzhuis)-1];
            $pathurl=$path.time().'_'.$i.'.'.$houzhui;
        }
        else{
            $pathurl=$path.$arr['name'];
        }
        
        is_dir($path) OR mkdir($path, 0777, true);//文件夹不存在创建文件夹
        $pathurl=iconv("UTF-8","gb2312",$pathurl);//目标路径
        if(move_uploaded_file($tmp_name,$pathurl)){
            $res[]=str_ireplace($path,$path_res,$pathurl);
        }
    }
    echo json_encode($res);//返回json
}
?>
  • 条件竞争:

漏洞位于:/zbzcms/cms/cms/admin/run_ajax.php

//批量上传txt文章
if($run=='uptxt'){
    $id=$_GET['id'];
    $chatu=$_GET['chatu'];
    $fabushijian=$_GET['fabushijian'];
    
    foreach($_FILES as $k=>$arr){
        $tmp_name=$arr['tmp_name'];//临时文件
        $path='uptxt';
        is_dir($path) OR mkdir($path, 0777, true);//文件夹不存在创建文件夹
        $pathurl=$path.'/'.$arr['name'];
        $pathurl=utf8($pathurl);
        if($arr['error']==0){
            if(!move_uploaded_file($tmp_name,iconv("UTF-8","gb2312",$pathurl))){
                move_uploaded_file($tmp_name,$pathurl);
            }
            $gml='http://'.$_SERVER['SERVER_NAME'].$_SERVER["REQUEST_URI"];//当前文件夹
            $gml=dirname($gml);
            $neirong=file_get_contents($gml.'/'.$pathurl);
            $neirong=utf8($neirong);
            $neirong='

'.str_ireplace("\r\n","

",$neirong).'

'; if($chatu==1){ $tuku=ii('tuku');//图库 $tukus=explode(";",$tuku); $tupians=array(); $suoluetu=''; $tupianshu=rand(1,3); for ($x=1; $x<=$tupianshu; $x++) { $k=rand(0,count($tukus)-1); if($x==1){ $suoluetu=$tukus[$k]; } $tupians[]="\r\n

{$key}

"; unset($tukus[$k]); $tukus = array_values($tukus); } $neirongs=explode('

',$neirong); if(count($tupians)>0){ foreach($tupians as $k=>$v){ $neirongs[$k]=$neirongs[$k].$v; } } $neirong=implode('',$neirongs); } $art['tid']=$id; $art['biaoti']=str_ireplace('.txt','',$arr['name']); $art['neirong']=$neirong; $art['zhaiyao']=jiequ($neirong,100);//截取摘要 $art['suoluetu']=$suoluetu; $art['paixu']=50; $art['fabudao']=0; $art['zuozhe']=$_SESSION['guanliyuan']; if($fabushijian==0){ $art['fabushijian']=1; } else{ $art['fabushijian']=time(); } $c_sql->insert('art',$art); } } delDirAndFile($path,1); }

这里也是一处任意文件上传,但是执行到最后的时候又将其进行了删除,从文件监控日志中也可以看出来:

由于上传的路径以及文件名都是确定的,所以可以利用条件竞争的方式进行利用:

  • 任意文件写入:

漏洞位于:/zbzcms/cms/cms/admin/run_ajax.php

//文件编辑保存
if($run=='wenjian_edit'){
    $path=$_POST['path'];
    $neirong=$_POST['neirong'];
    //如果get_magic_quotes_gpc()是打开的
    if(get_magic_quotes_gpc()){
        $neirong=stripslashes($neirong);//将字符串进行处理
    }
    echo file_put_contents($path,$neirong);
}
  • 任意文件/目录重命名:

漏洞位于:/zbzcms/cms/cms/admin/run_ajax.php

//文件安全检测
if($run=='anquan_run'){
    $cms_oldname=$_POST['cms_oldname'];
    $admin_oldname=$_POST['admin_oldname'];
    $cms=$_POST['cms'];
    $admin=$_POST['admin'];
    $admin_url=ii('电脑站网址');
    
    if(stristr($admin_url,'http://') || stristr($admin_url,'https://')){$admin_url.=cms.'/';}
    else{$admin_url='http://'.$_SERVER['HTTP_HOST'].ii('电脑站网址').cms.'/';}

    if($_GET['i']==1){
        rename("../{$admin_oldname}","../{$admin}");
        echo 2;
    }
    else if($_GET['i']==2){
        if(rename("../../{$cms_oldname}","../../{$cms}")){
            echo $admin_url.$cms.'/'.$admin;//返回新后台地址
        }
    }
}

你可能感兴趣的:(安服仔的首次PHP代审实践)