环境配置

docker pull c0ny1/upload-labs
docker run --name upload-labs -dt -p 666:80 c0ny1/upload-labs:latest​
docker exec -it 25fee1da40ca /bin/bash​
mkdir upload
chown www-data:www-data upload
https://cdn.jsdelivr.net/gh/XFishalways/blog_img@master/2023/07/12/CleanShot 2023-07-12 at 15.50.16@2x.jpg
https://cdn.jsdelivr.net/gh/XFishalways/blog_img@master/2023/07/12/CleanShot 2023-07-12 at 15.50.59@2x.jpg

Pass-01

js checkFile函数检查,所以简单禁用前端javascript即可上传php文件

https://cdn.jsdelivr.net/gh/XFishalways/blog_img@master/2023/07/12/CleanShot 2023-07-12 at 15.54.10@2x.jpg

Pass-02

if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif'))

Content-Type验证,上传php文件时改为image/jpg即可

https://cdn.jsdelivr.net/gh/XFishalways/blog_img@master/2023/07/12/CleanShot 2023-07-12 at 16.09.43@2x.jpg

Pass-03

$deny_ext = array('.asp','.aspx','.php','.jsp');

限制上述后缀,可以用phtml这些后缀绕过,改后缀的同时以php执行

https://cdn.jsdelivr.net/gh/XFishalways/blog_img@master/2023/07/12/CleanShot 2023-07-12 at 16.14.51@2x.jpg

Pass-04

$deny_ext = array(".php",".php5",".php4",".php3",".php2","php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2","pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf");

所有后缀都被禁掉,只能用.htaccess文件

htaccess文件是Apache服务器中的一个配置文件 负责相关目录下的网页配置,可以通过它改变网站文件拓展名,所以先上传一个.htaccess文件

AddType application/x-httpd-php .txt

再上传一个txt文件就可以连接了

https://cdn.jsdelivr.net/gh/XFishalways/blog_img@master/2023/07/12/CleanShot 2023-07-12 at 16.22.49@2x.jpg

Pass-05

$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess"); 
       
$file_name = trim($_FILES['upload_file']['name']);        
$file_name = deldot($file_name);//删除文件名末尾的点        
$file_ext = strrchr($file_name, '.');        
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA     

$file_ext = trim($file_ext); //首尾去空​if (!in_array($file_ext, $deny_ext))  //判断

.htaccess后缀被过滤,但观察到::$DATA 只删除了一次,可以通过双写成功上传

https://cdn.jsdelivr.net/gh/XFishalways/blog_img@master/2023/07/12/CleanShot 2023-07-12 at 16.52.24@2x.jpg

Pass-06

这里仍然是 $file_ext = str_ireplace('::$DATA', '', $file_ext); //去除字符串::$DATA 所以继续 shell.php::$D::$DATAATA 就行

另外,观察到此题比上一题多了 $file_ext = strtolower($file_ext); //转换为小写 ,所以上一题其实也可以大写后缀名绕过

Pass-07

首尾没有去除.shell.php. 上传

https://cdn.jsdelivr.net/gh/XFishalways/blog_img@master/2023/07/13/CleanShot 2023-07-13 at 14.16.20@2x.jpg

Pass-08

没有去除 ::$DATA

https://cdn.jsdelivr.net/gh/XFishalways/blog_img@master/2023/07/13/CleanShot 2023-07-13 at 14.20.55@2x.jpg

Pass-09

同Pass-05,双写::$DATA

https://cdn.jsdelivr.net/gh/XFishalways/blog_img@master/2023/07/13/CleanShot 2023-07-13 at 14.25.17@2x.jpg

Pass-10

$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");​

$file_name = trim($_FILES['upload_file']['name']);

$file_name = str_ireplace($deny_ext,"", $file_name);
$temp_file = $_FILES['upload_file']['tmp_name'];

只替换了一次后缀,双写php

https://cdn.jsdelivr.net/gh/XFishalways/blog_img@master/2023/07/13/CleanShot 2023-07-13 at 14.30.08@2x.jpg

Pass-11

$ext_arr = array('jpg','png','gif');
$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);

白名单过滤,图片后缀

%00 截断:%00是NULL的十六进制形式,在读取文件时,遇到NULL就会认为已结束,也就不会继续读取后面的文件了,这样的效果就是后端上传验证的时候能够通过,但后续读取的时候把.php后的部分截断舍去,从而读出一个php文件

https://cdn.jsdelivr.net/gh/XFishalways/blog_img@master/2023/07/13/CleanShot 2023-07-13 at 14.39.00@2x.jpg

Pass-12

另一种方式的上传,和11差不多,改掉上传路径

https://cdn.jsdelivr.net/gh/XFishalways/blog_img@master/2023/07/13/CleanShot 2023-07-13 at 14.44.42@2x.jpg

Pass-13

不验证后缀,验证文件头

常用格式的文件头:

GIF47 49 46 38 39 61(GIF89a)
JPGFF D8
PNG89 50 4E 47 0D 0A 1A 0A
常用文件头
https://cdn.jsdelivr.net/gh/XFishalways/blog_img@master/2023/07/13/CleanShot 2023-07-13 at 14.49.20@2x.jpg

把对应文件头的数值php文件起始处的Hex对应一下即可

Pass-14

function isImage($filename){
    $types = '.jpeg|.png|.gif';
    if(file_exists($filename)){
        $info = getimagesize($filename);
        $ext = image_type_to_extension($info[2]);  // 这里的info[2]是图片类型
        if(stripos($types,$ext)){
            return $ext;
        }else{
            return false;
        }
    }else{
        return false;
    }
}

使用getimagesize()函数的检查

仍然是伪造文件头就可以,同上

Pass-15

仍然是伪造文件头就可以,同上

Pass-16

else if(($fileext == "gif") && ($filetype=="image/gif")){
        if(move_uploaded_file($tmpname,$target_path))
        {
            //使用上传的图片生成新的图片
            $im = imagecreatefromgif($target_path);
            if($im == false){
                $msg = "该文件不是gif格式的图片!";
                @unlink($target_path);
            }else{
                //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".gif";
                $newimagepath = UPLOAD_PATH.$newfilename;
                imagegif($im,$newimagepath);
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.$newfilename;
                @unlink($target_path);
                $is_upload = true;
            }
        } else {
            $msg = "上传出错!";
        }

二次渲染后php木马会丢失,通过观察对比后在二次渲染后不变的部分添加php代码

Pass-17

图片后缀白名单,无其他验证,把php文件简单改个后缀就可以上传

另外,14-17都可以使用老师提供的图片,效果如下:

https://cdn.jsdelivr.net/gh/XFishalways/blog_img@master/2023/07/13/CleanShot 2023-07-13 at 16.15.50@2x.jpg

Pass-18

$is_upload = false;
$msg = null;
if (isset($_POST['submit']))
{
    require_once("./myupload.php");
    $imgFileName =time();
    $u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
    $status_code = $u->upload(UPLOAD_PATH);
    switch ($status_code) {
        case 1:
            $is_upload = true;
            $img_path = $u->cls_upload_dir . $u->cls_file_rename_to;
            break;
        case 2:
            $msg = '文件已经被上传,但没有重命名。';
            break; 
        case -1:
            $msg = '这个文件不能上传到服务器的临时文件存储目录。';
            break; 
        case -2:
            $msg = '上传失败,上传目录不可写。';
            break; 
        case -3:
            $msg = '上传失败,无法上传该类型文件。';
            break; 
        case -4:
            $msg = '上传失败,上传的文件过大。';
            break; 
        case -5:
            $msg = '上传失败,服务器已经存在相同名称文件。';
            break; 
        case -6:
            $msg = '文件无法上传,文件不能复制到目标目录。';
            break;      
        default:
            $msg = '未知错误!';
            break;
    }
}

//myupload.php
class MyUpload{
......
......
...... 
  var $cls_arr_ext_accepted = array(
      ".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
      ".html", ".xml", ".tiff", ".jpeg", ".png" );

......
......
......  
  /** upload()
   **
   ** Method to upload the file.
   ** This is the only method to call outside the class.
   ** @para String name of the directory we upload to
   ** @returns void
  **/
  function upload( $dir ){
    
    $ret = $this->isUploadedFile();
    
    if( $ret != 1 ){
      return $this->resultUpload( $ret );
    }

    $ret = $this->setDir( $dir );
    if( $ret != 1 ){
      return $this->resultUpload( $ret );
    }

    $ret = $this->checkExtension();
    if( $ret != 1 ){
      return $this->resultUpload( $ret );
    }

    $ret = $this->checkSize();
    if( $ret != 1 ){
      return $this->resultUpload( $ret );    
    }
    
    // if flag to check if the file exists is set to 1
    
    if( $this->cls_file_exists == 1 ){
      
      $ret = $this->checkFileExists();
      if( $ret != 1 ){
        return $this->resultUpload( $ret );    
      }
    }

    // if we are here, we are ready to move the file to destination

    $ret = $this->move();
    if( $ret != 1 ){
      return $this->resultUpload( $ret );    
    }

    // check if we need to rename the file

    if( $this->cls_rename_file == 1 ){
      $ret = $this->renameFile();
      if( $ret != 1 ){
        return $this->resultUpload( $ret );    
      }
    }
    
    // if we are here, everything worked as planned :)

    return $this->resultUpload( "SUCCESS" );
  
  }
......
......
...... 
};

上传文件后会判断后缀名,相同就重命名

通过条件竞争,直接爆破,目的是重复上传大量文件,payload点不重要,随便设在一个没用的地方,比如origin这里,爆破过程中会有某些时刻中upload路径下存有目标文件

https://cdn.jsdelivr.net/gh/XFishalways/blog_img@master/2023/07/13/CleanShot 2023-07-13 at 16.04.35@2x.jpg

Pass-19

$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");

$file_name = $_POST['save_name'];
$file_ext = pathinfo($file_name,PATHINFO_EXTENSION);

过滤黑名单后缀,并使用POST接收自定义上传文件名’save_name’

https://cdn.jsdelivr.net/gh/XFishalways/blog_img@master/2023/07/13/CleanShot 2023-07-13 at 16.28.33@2x.jpg

00截断: 构造 save_name=upload.php/.jpg ,把php后面的”/”对应的0x2f改为0x00,完成00截断

Pass-20

//检查MIME
    $allow_type = array('image/jpeg','image/png','image/gif');
    if(!in_array($_FILES['upload_file']['type'],$allow_type)){
        $msg = "禁止上传该类型文件!";
    }else{
        //检查文件名
        $file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
        if (!is_array($file)) {
            $file = explode('.', strtolower($file));
        }

        $ext = end($file);
        $allow_suffix = array('jpg','png','gif');
        if (!in_array($ext, $allow_suffix)) {
            $msg = "禁止上传该后缀文件!";
        }else{
            $file_name = reset($file) . '.' . $file[count($file) - 1];
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH . '/' .$file_name;
            if (move_uploaded_file($temp_file, $img_path)) {
                $msg = "文件上传成功!";
                $is_upload = true;
            } else {
                $msg = "文件上传失败!";
            }
        }
    }
  1. 检查Content-Type是否为图片,直接修改即可
  2. 检查自定义POST上传的文件名是否在图片后缀白名单中,再用 $file_name = reset($file) . '.' . $file[count($file) - 1]; 重新组合了文件名,新文件名由第一个部分.最后一个数组元素组成所以最后这一步可以通过自定义数组来完成文件名的拼接,定义:
    1. save_name[0]=shell.php
    2. save_name[1]为空
    3. save_name[2]=jpg

这样拼接后就是shell.php.

https://cdn.jsdelivr.net/gh/XFishalways/blog_img@master/2023/07/13/CleanShot 2023-07-13 at 17.00.05@2x.jpg

Tagged in:

,

About the Author

XFishalways

Fisher不钓鱼 川大21级在读 网络空间安全专业 7年前的围棋业余5段 素描彩铅水粉国画书法童子功拥有者 Hala Madrid Letsgo Pat Self-Commentator Analyzer ing 七年前的业余5段 AI Skipper nparadigm申工智能yyds 飞禽岛少年Lee Sedol

View All Articles