惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IT之家
IT之家
A
About on SuperTechFans
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
N
Netflix TechBlog - Medium
Microsoft Security Blog
Microsoft Security Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 三生石上(FineUI控件)
博客园 - 聂微东
博客园 - Franky
D
Docker
Martin Fowler
Martin Fowler
Engineering at Meta
Engineering at Meta
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
Last Week in AI
Last Week in AI
U
Unit 42
F
Fortinet All Blogs
H
Help Net Security
Blog — PlanetScale
Blog — PlanetScale
Microsoft Azure Blog
Microsoft Azure Blog
罗磊的独立博客
P
Proofpoint News Feed
月光博客
月光博客
G
Google Developers Blog

Hi, I Am I

[I Am I 年度简报] — 不知终日梦为鱼 初探 ESP32-CAM QQ 聊天记录 MHT 文件转 HTML [I Am I 年度简报] - 草木本无意,荣枯自有时。 Hexo 中实现 Live Photos 支持 写在当下 NKCTF 2024 1z_F0r3ns1c5 Writeup 春秋杯冬季赛 2023 Writeup [I Am I 年度简报] - 2023 某内网渗透内部赛 Writeup 强网拟态 2023 Writeup Github Actions 自动化部署 Hexo 浅析CobaltStrike流量解密 陇剑杯 2023 Writeup CTF线下赛AWDP总结 ISCC 2023 Writeup ISCC 2023 实战题 Writeup CISCN 2023 Writeup 福建闽盾杯网络空间安全大赛 2023 Writeup 天一永安杯宁波市网络安全大赛 2023 Writeup 贵阳大数据及网络安全精英对抗赛 2023 Writeup 红明谷杯 2023 Writeup Confetti 带来有仪式感的鼓励 记一次 JS 逆向密码加密 [I Am I 年度简报] – 2022 PHP 读取 Excel 文件内容并写入数据库 从0开始的 MoeCTF 开发之路 观安杯 2022 Writeup 利用微信服务号实现早安自动化 Cloudflare批量拉黑IP脚本
upload-labs 文件上传漏洞通关记录
2021-02-24 · via Hi, I Am I

upload-labs 是一个使用 php 语言编写的,专门收集渗透测试和CTF中遇到的各种上传漏洞的靶场。旨在帮助大家对上传漏洞有一个全面的了解。目前一共21关,每一关都包含着不同上传方式。

运行环境

博主使用的 PHPstudy + apache+php5.2 (扩展: php_gd2 , php_exif),自建容易有坑,建议直接下载 releases 方便快速搭建。

第1关

直接上传,弹窗了。盲猜js验证 直接 禁用js 上传成功。

也可以burp抓包修改文件类型(上传 webshell.jpg 修改为 webshell.php)。

image-20210223222306415

image-20210223220706651

第2关

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        // 判断Content-type是否为以下类型 image/jpeg、image/png、image/gif
        if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']            
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '文件类型不正确,请重新上传!';
        }
    } else {
        $msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
    }
}

直接抓包把 application/octet-stream 修改为允许的类型 image/jpeg、image/png、image/gif 即可

image-20210223221238723

也可以用第一关修改后缀的方法上传。

第3关

源码直接写死了禁止上传 .asp .aspx .php .jsp 这四种后缀的文件,这里百度了一番,直接修改后缀为 phtml

  • Apache的解析顺序是从右到左开始解析文件后缀的,如果最右侧扩展名不可识别,就继续往左判断。直到遇到可以解析的文件后缀为止
  • 可以上传例如 php3, phtml 后缀的文件绕过,前提是 Apachehttpd.conf 中配置有如下代码:
    AddType application/x-httpd-php .php .php3 .phtml

image-20210223223312675

image-20210223223440012

第4关

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $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",".ini");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //收尾去空

        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.$file_name;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

这题基本把各种后缀全禁止了 但没有限制 .htaccess

.htaccess 文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。

通过 .htaccess 文件,可以实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能IIS平台上不存在该文件,该文件默认开启,启用和关闭在 httpd.conf 文件中配置。

因此我们可以上传一个 .htaccess 文件内容:

SetHandler application/x-httpd-php

这样所有文件都会被当成PHP来进行解析 接着我们直接上传个图片马。

image-20210223224256529

第5关

黑名单里多了个 .htaccess 少了个 ini

创建一个 .user.ini 文件, 内容如下:

auto_prepend_file=123.gif

.user.ini 上传到服务器, 然后将包含一句话的 php文件 重命名为 123.gif, 上传到服务器

然后访问 upload/readme.php 会自动包含并解析 123.gif 文件

第6关

查看源码发现后缀全限制死了 但是发现少了一行

$file_ext = strtolower($file_ext); //转换为小写

直接后缀大小写混合 绕过上传成功。

image-20210223230623317

第7关

查看源码发现少了

$file_ext = trim($file_ext); //首尾去空

直接后缀后面加个空格绕过。

image-20210223231126124

第8关

查看源码发现这题少了

$file_name = deldot($file_name);//删除文件名末尾的点

利用 windows 特性,会自动去掉后缀名中最后的 .,直接后缀后面加 . 即可绕过。 image-20210223231440263

第9关

查看源码发现少了

$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA

Windows + PHP 的情况下:如果 文件名 + ::$DATA,::$DATA 之后的数据当成文件流处理,不会检测后缀名,且保持 ::$DATA 之前的文件名。

例如 webshell.php::$DATA 变为 webshell.php

直接后缀加上 ::$DATA 绕过上传成功,访问上传的文件是注意去掉地址最后的 ::$DATA

image-20210223232126890

第10关

查看源码后,发现 第15行 拼接的是 $file_name 而不是 $file_ext,而 $file_name 只处理了 文件名末尾的点 所以我们可以输入 . . 来绕过。

这题自我感觉有点绕 写个解析 以备参考。

第6行 使用 trim 先去除了文件前后的空格,第7行 使用 deldot 去除了文件名末尾的 .第8行 使用 strrchr 来寻找 . 来确定文件的后缀

此时请我们输入 webshell.php. . (点空格点) 会匹配为 . (点空格) 从而达到绕过的目的。

image-20210223233057946

第11关

源码 第8行 使用 str_ireplace 进行了匹配替换 如果存在黑名单内的内容 则替换为空且只替换一次例如 webshell.php 替换为 webshell. 我们可以通过双写绕过

image-20210223235759186

第12关

源码 第8行 进行了一次GET传参 修改一句话的后缀名为 .jpg 然后修改URL后直接使用 %00 url终止符进行截断。成功上传访问时记得删除PHP后面的内容

截断条件:PHP版本小于5.3.4,PHP的magic_quotes_gpc为OFF状态 否则magic_quotes_gpc会在敏感字符前加上反斜杠(\)

image-20210224000947285

第13关

和上一关唯一的区别就是 GET 变为了 POST

我们使用00截断

img

img

第14关

function getReailFileType($filename){
    $file = fopen($filename, "rb");
    $bin = fread($file, 2); //只读2字节
    fclose($file);
    $strInfo = @unpack("C2chars", $bin);    
    $typeCode = intval($strInfo['chars1'].$strInfo['chars2']);    
    $fileType = '';    
    switch($typeCode){      
        case 255216:            
            $fileType = 'jpg';
            break;
        case 13780:            
            $fileType = 'png';
            break;        
        case 7173:            
            $fileType = 'gif';
            break;
        default:            
            $fileType = 'unknown';
        }    
        return $fileType;
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $file_type = getReailFileType($temp_file);

    if($file_type == 'unknown'){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}

查看源码后,第3行 读取上传文件的前两个字节,接着使用 unpack 解码后,intval 转换为10进制,根据转换后的结果进行 case

CMD生成图片马

copy webshell.jpg /b + webshell.php /a shell.jpg

shell.jpg 就是生成的图片马,直接上传即可。直接访问图片并不能把图片当做PHP解析,利用文件包含解析图片马里的PHP语句,file 为我们的图片马位置。

<?php
/*
本页面存在文件包含漏洞,用于测试图片马是否能正常运行!
*/
header("Content-Type:text/html;charset=utf-8");
$file = $_GET['file']; //这里是传参
if(isset($file)){
    include $file;
}else{
    show_source(__file__);
}
?>

image-20210224003601737

第15关

同上一关 直接用上一关的图片马上传即可

查看源码 第4行 使用 getimagesize 获取图片类型,打印一下返回了一个数组

array(7) {
  [0]=> //图像宽度像素值
  int(50)
  [1]=> //图像高度像素值
  int(52)
  [2]=> //图像类型,1=GIF,2=JPG,3=PNG,4=SWF,5=PSD,6=BMP,7=TIFF_II,8=TIFF_MM,9=JPC,10=JP2,11=JPX,12=JB2,13=SWC,14=IFF,15=WBMP,16=XBM,17=ICO,18=COUNT
  int(2)
  [3]=> //图像宽度和高度的字符串
  string(22) "width="50" height="52""
  ["bits"]=> //图像的每种颜色的位数,二进制格式
  int(8)
  ["channels"]=> //图像的通道值
  int(3)
  ["mime"]=> //图像的MIME信息
  string(10) "image/jpeg"
}

第16关

这里用的 php_exif 扩展来判断的上传的文件类型 同上一关 直接用上一关的图片马上传即可

第17关

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
    // 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
    $filename = $_FILES['upload_file']['name'];
    $filetype = $_FILES['upload_file']['type'];
    $tmpname = $_FILES['upload_file']['tmp_name'];

    $target_path=UPLOAD_PATH.'/'.basename($filename);

    // 获得上传文件的扩展名
    $fileext= substr(strrchr($filename,"."),1);

    //判断文件后缀与类型,合法才进行上传操作
    if(($fileext == "jpg") && ($filetype=="image/jpeg")){
        if(move_uploaded_file($tmpname,$target_path)){
            //使用上传的图片生成新的图片
            $im = imagecreatefromjpeg($target_path);

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

    }else if(($fileext == "png") && ($filetype=="image/png")){
        if(move_uploaded_file($tmpname,$target_path)){
            //使用上传的图片生成新的图片
            $im = imagecreatefrompng($target_path);

            if($im == false){
                $msg = "该文件不是png格式的图片!";
                @unlink($target_path);
            }else{
                 //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".png";
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagepng($im,$img_path);

                @unlink($target_path);
                $is_upload = true;               
            }
        } else {
            $msg = "上传出错!";
        }

    }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";
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagegif($im,$img_path);

                @unlink($target_path);
                $is_upload = true;
            }
        } else {
            $msg = "上传出错!";
        }
    }else{
        $msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
    }
}

判断了文件后缀与类型 利用 imagecreatefromgif 判断是否为 gif 图片,最后再做了二次渲染,具体可以参考 upload-labs Pass17

第18关

$is_upload = false;
$msg = null;

if(isset($_POST['submit'])){
    $ext_arr = array('jpg','png','gif');
    $file_name = $_FILES['upload_file']['name'];
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $file_ext = substr($file_name,strrpos($file_name,".")+1);
    $upload_file = UPLOAD_PATH . '/' . $file_name;

    if(move_uploaded_file($temp_file, $upload_file)){
        if(in_array($file_ext,$ext_arr)){
             $img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
             rename($upload_file, $img_path);
             $is_upload = true;
        }else{
            $msg = "只允许上传.jpg|.png|.gif类型文件!";
            unlink($upload_file);
        }
    }else{
        $msg = '上传出错!';
    }
}

这里是条件竞争 先将文件上传到服务器 第9行 拼接了文件上传后的路径,第11行 使用 move_uploaded_file 把上传的文件移动到临时目录

然后判断文件是否在白名单内 在的话使用 rename 进行重命名,否则使用 unlink 删除文件 这里我们直接赶在删除前 去访问文件 写入一个shell就行了

这里可以使用burp的 Intruder 不断上传文件,然后我们不断的访问刷新该地址。

<?php fputs(fopen('webshell.php','w'),'<?php phpinfo(); ?>');?>

image-20210224165225438

第19关

看了下 upload-labs 的介绍 Pass-19 必须在linux下运行,直接上传了图片马 然后利用前几关的文件包含漏洞…

第20关

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $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);

        if(!in_array($file_ext,$deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH . '/' .$file_name;
            if (move_uploaded_file($temp_file, $img_path)) { 
                $is_upload = true;
            }else{
                $msg = '上传出错!';
            }
        }else{
            $msg = '禁止保存为该类型文件!';
        }

    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

这题给了黑名单 但是能自定义上传的文件名,我们直接用前几关的知识绕过就行了 文件名后面加个 空格/点/::$DATA 都可以。

第21关

先检查 Content-type,接着检查文件名,保存名称为空的就用上传的文件名,接着 !is_array 判断文件名是否为数组,不是的话就使用 explode 通过 . 号分割成数组。然后获取后缀名,进行白名单验证。符合条件就拼接数组的第一个和最后一个作为文件名,保存。

具体参考:https://blog.csdn.net/Fly_hps/article/details/99660226

写在后面

增加了大量奇怪的上传姿势,也增加了防御的各种思路。部分题参考了网上的题解,其他差不多都是用 burp 的重发器和 var_dump() 打印函数慢慢调的 有几题没做出来 学习中…