文件上传常见验证:
后缀名,类型、文件头等
后缀名:黑名单、白名单
文件类型:M工ME信息
文件头:内容头信息
方法:查看源码、抓包修改包信息
%00截断:可以把这个放在文件名内,绕过检测。get:会自动解码 post:不会自动解码,所以想以post提交数据%00需要把它进行url编码变成%25%30%30
参考地址:https://blog.csdn.net/qq_39511050/article/details/126881335
PASS 2 Content-Type绕过
源码
$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.'文件夹不存在,请手工创建!';
}
}
代码分析
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif'))
只允许上传文件类型为image/jpeg或者为image/png或者为image/gif的文件
通关方法
上传php脚本文件,使用bp抓包,然后更改其content-type类型为:image/jpeg即可绕过,即符合源码中对于Content-Type的限制。
Content-Type参考:https://blog.csdn.net/huyanpeng1988/article/details/47173385
pass 3 黑名单绕过
源码
$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 . '文件夹不存在,请手工创建!';
}
}
代码分析
这一题定义了一个黑名单数组
$deny_ext = array('.asp','.aspx','.php','.jsp')
,里面是不能上传的文件类型,接着接收了上传文件的文件名,然后进行过滤文件名两端的预定义字符、删除文件名末尾的点、提取后缀名、转换为小写、删除特定字符串、继续对后缀名进行过滤预定义字符。再进行判断如果提取出来的后缀名不在黑名单数组里面。
通关方法
上传php脚本文件,使用bp抓包,将文件名修改成非黑名单的后缀即可绕过。上传后缀名为.php1、.php2、phtml等等都可以完成绕过。
pass 4 .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 . '文件夹不存在,请手工创建!';
}
}
代码分析
这一题和pass 3相似,定义了一个更详细的黑名单数组,但是黑名单内没有.htaccess
通关方法
.htaccess文件,全称是超文本入口,提供了针对目录改变配置的方法,即在一个特定的文档目录中放置一个包含一个或多个指令的文件,以作用于此目录及其所有子目录。作为用户,所能使用的命令受到限制。管理员可以通过Apache的AllowOverride指令来设置我们可以通过此文件,将我们的jpg文件全部解析为php文件,只需在此文件写入下面的代码
#将所有.jpg后缀的文件当作.php文件解析
AddType application/x-httpd-php .jpg
先上传.htaccess文件到服务器,然后再上传包含后门的 .jpg至服务器,访问.jpg文件,服务器会将.jpg文件解析为.php文件
pass 5
源码
$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 . '文件夹不存在,请手工创建!';
}
}
代码分析
这一题的黑名单包含.htaccess。
通关方法
我们可以用第十关的方法来进行绕过也就是点空格点绕过(. .),因为它的验证首先会删除文件名两端的空格,但是我们构造的后缀名的两端没有空格,所以此验证无效,接着它会删除我们后缀名中最后的一个点,然后又会过滤掉我们的一个空格,此时上传文件的后缀名为.php.不在黑名单里面,所以可以成功被上传,而且又因为windows操作系统的原因,文件后缀名最后的点也会被删除,所以我们就完成了绕过,此时上传至服务器的文件名为flag.php。
访问服务器flag.php
pass 6 大小写绕过
源码
$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 . '文件夹不存在,请手工创建!';
}
}
代码分析
与前面几题不同,第六题少了
$file_ext = strtolower($file_ext); //转换为小写
,windows 系统会忽略大小写。而Linux系统默认对大小写敏感,如忽略则需要特殊配置,这一题根据后缀的过滤,正确写出没有被过滤的文件名字,即可上传后面。
通关方法
通过观察发现黑名单中没有.phP
pass 7 空格绕过
源码
$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 . '文件夹不存在,请手工创建!';
}
}
代码分析
与前面几题不同,第六题少了
$file_ext = trim($file_ext); //首尾去空
,Windows下xx.jpg[空格] 或者xx.jpg.这两类文件都是不允许存在的,若这样命名,windows会默认去除空格或点.
通关方法
抓包在文件名后加空格
pass 8 点绕过
源码
$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 . '文件夹不存在,请手工创建!';
}
}
代码分析
与前面几题不同,第8题少了
$file_name = deldot($file_name);//删除文件名末尾的点
,Windows下xx.jpg[空格] 或者xx.jpg.这两类文件都是不允许存在的,若这样命名,windows会默认去除空格或点.
通关方法
抓包在文件名后加 ’ . ’
pass 9 ::$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 . '文件夹不存在,请手工创建!';
}
}
代码分析
与前面几题不同,第9题少了
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
php在windows的时候如果文件名+“::$DATA"会把其之后的数据当成文件流处理,不会检测后缀名。 且保持”:: $DATA"之前的文件名。
通关方法
抓包在文件名后加::$DATA,以达到不检测后缀名的效果
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",".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.不在黑名单里面,所以可以成功被上传,而且又因为windows操作系统的原因,文件后缀名最后的点也会被删除,所以我们就完成了绕过,此时上传至服务器的文件名为flag.php。
通关方法
与第五关相似
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","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 . '文件夹不存在,请手工创建!';
}
}
代码分析
本题代码中有
$file_name = str_ireplace($deny_ext,"", $file_name);
,意思是如果上传的文件名中有黑名单内的字符串,则将该字段替换为空。
例:1.php
-> 1
str_replace() 函数以其他字符替换字符串中的一些字符(区分大小写)。
把字符串 "Hello world!" 中的字符 "world" 替换为 "Shanghai":
<?php
echo str_replace("world","Shanghai","Hello world!");
?>
通关方法
通过双写文件名后缀来绕过
例:1.pphp
hp->1.php
pass 12 %00截断绕过(GET方式)
源码
$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类型文件!";
}
}
代码分析
substr(string,start,length)函数返回字符串的一部分
从字符串中返回 "world":
<?php
echo substr("Hello world",6);
?>
strrpos(string,find,start) 函数查找字符串在另一字符串中最后一次出现的位置。
查找 "php" 在字符串中最后一次出现的位置:
<?php
echo strrpos("You love php, I love php too!","php");
?>
输出: 21
此源码不同于前面的是,作者这次设置了白名单,除了白名单之内所有后缀名都不能进行上传。作者首先提取了文件的后缀名(从获得到名字的字符串里面,从中检索点最后一次出现的位置来进行截取后缀名)。
然后进行移动文件,但是此关移动文件的路径是由GET方式得到的,所以是可控的
通关方法
我们可以运用%00来进行截断,将想要上传的文件拼接到save_path变量中。
首先我们上传包含后门的xxx.jpg文件,然后抓包在save_path变量后拼接一个1.php%00,用%00截断来截断后面那些多余的内容。
访问1.php文件,发现执行了xxx.jpg内的后门代码
注释:
PHP版本小于5.3.4
魔术开关:php.ini magic_quotes_gpc = Off
pass 13 %00截断绕过(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类型文件!";
}
}
代码分析
此题和pass 12类似,只是这次的拼接路径是有POST来接收的。
POST不会对%00进行网址编码,需要我们手动编码。
通关方法
首先我们上传包含后门的xxx.jpg文件,然后抓包在save_path变量后拼接一个2.php%00,手动对%00进行网址编码。上传成功后访问2.php即可运行xxx.jpg内的后门代码。
注释:
PHP版本小于5.3.4
魔术开关:php.ini magic_quotes_gpc = Off
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 = "上传出错!";
}
}
}
代码分析
fopen() 函数打开一个文件或 URL。
如果 fopen() 失败,它将返回 FALSE 并附带错误信息。您可以通过在函数名前面添加一个 '@' 来隐藏错误输出。
fopen(filename,mode,include_path,context)
filename 必需。规定要打开的文件或 URL。
mode 必需。规定要求到该文件/流的访问类型。可能的值见下表。
include_path 可选。如果也需要在 include_path 中检索文件的话,
可以将该参数设 为 1 或 TRUE。
context 可选。规定文件句柄的环境。Context 是可以修改流的行为的一套选项。
"r" 只读方式打开,将文件指针指向文件头。
"rb" 以二进制方式打开
"r+" 读写方式打开,将文件指针指向文件头。
"w" 写入方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。
"w+" 读写方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。
"a" 写入方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。
"a+" 读写方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。
"x" 创建并以写入方式打开,将文件指针指向文件头。如果文件已存在,则 fopen() 调用 失败并返回 FALSE,并生成一条 E_WARNING 级别的错误信息。如果文件不存在则尝试创建之。
这和给底层的 open(2) 系统调用指定 O_EXCL|O_CREAT 标记是等价的。
此选项被 PHP 4.3.2 以及以后的版本所支持,仅能用于本地文件。
"x+" 创建并以读写方式打开,将文件指针指向文件头。如果文件已存在,则 fopen() 调用失败并返回 FALSE,并生成一条 E_WARNING 级别的错误信息。如果文件不存在则尝试创建之。
这和给底层的 open(2) 系统调用指定 O_EXCL|O_CREAT 标记是等价的。
此选项被 PHP 4.3.2 以及以后的版本所支持,仅能用于本地文件。
实例:
<?php
$file = fopen("test.txt","r");
$file = fopen("/home/test/test.txt","r");
$file = fopen("/home/test/test.gif","wb");
$file = fopen("http://www.example.com/","r");
$file = fopen("ftp://user:password@example.com/test.txt","w");
?>
fread() 函数读取打开的文件。
函数会在到达指定长度或读到文件末尾(EOF)时(以先到者为准),停止运行。
该函数返回读取的字符串,如果失败则返回 FALSE。
string fread ( resource $handle , int $length )
handle 文件系统指针,是典型地由 fopen() 创建的 resource(资源)。
length 必需。规定要读取的最大字节数。
实例:
从文件中读取 10 个字节:
<?php
$file = fopen("test.txt","r");
$contents = fread($file,"10");
fclose($file);
?>
fclose() 函数关闭打开的文件。
unpack(format,data) 函数从二进制字符串对数据进行解包。
format 必需。规定在解包数据时所使用的格式。
data 可选。规定被解包的二进制数据。
format 参数的可能值:
a - NUL-padded string
A - SPACE-padded string
h - Hex string, low nibble first
H - Hex string, high nibble first
c - signed char
C - unsigned char
s - signed short (always 16 bit, machine byte order)
S - unsigned short (always 16 bit, machine byte order)
n - unsigned short (always 16 bit, big endian byte order)
v - unsigned short (always 16 bit, little endian byte order)
i - signed integer (machine dependent size and byte order)
I - unsigned integer (machine dependent size and byte order)
l - signed long (always 32 bit, machine byte order)
L - unsigned long (always 32 bit, machine byte order)
N - unsigned long (always 32 bit, big endian byte order)
V - unsigned long (always 32 bit, little endian byte order)
f - float (machine dependent size and representation)
d - double (machine dependent size and representation)
x - NUL byte
X - Back up one byte
@ - NUL-fill to absolute position
实例:
<?php
$data = "PHP";
print_r(unpack("C*",$data));
?>
输出:
Array ( [1] => 80 [2] => 72 [3] => 80 )
intval(mixed $var [, int $base = 10 ]) 函数用于获取变量的整数值。
$var:要转换成 integer 的数量值。
$base:转化所使用的进制。
如果 base 是 0,通过检测 var 的格式来决定使用的进制:
如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);否则,
如果字符串以 "0" 开始,使用 8 进制(octal);否则,
将使用 10 进制 (decimal)。
通关方法
将后门代码写入图片文件中
图片马制作方法:
- 手动写入
- copy a.jpg /b + shell.php /a shell.jpg
/b 表示一个二进制文件
+表示将多个文件合并成一个文件
/a 表示一个ASCII文本文件
由于本题含有文件包含漏洞,我们只要将上传的文件的路径作为 file参数的值,然后进行访问即可触发后门。
pass 15 getimagesize()图片马绕过
源码
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 = "上传出错!";
}
}
}
代码分析
定义和用法
file_exists() 函数检查文件或目录是否存在。
如果指定的文件或目录存在则返回 TRUE,否则返回 FALSE。
语法
file_exists(path)
path 必需。规定要检查的路径。
实例
<?php
echo file_exists("test.txt");
?>
上面的代码将输出:1
getimagesize() 函数用于获取图像大小及相关信息,成功返回一个数组,失败则返回 FALSE 并产生一条 E_WARNING 级的错误信息。
语法格式:
array getimagesize ( string $filename [, array &$imageinfo ] )
getimagesize() 函数将测定任何 GIF,JPG,PNG,SWF,SWC,PSD,TIFF,BMP,IFF,JP2,JPX,JB2,JPC,XBM 或 WBMP 图像文件的大小并返回图像的尺寸以及文件类型及图片高度与宽度。
索引 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
索引 3 给出的是一个宽度和高度的字符串,可以直接用于 HTML 的 <image> 标签
索引 bits 给出的是图像的每种颜色的位数,二进制格式
索引 channels 给出的是图像的通道值,RGB 图像默认是 3
索引 mime 给出的是图像的 MIME 信息,此信息可以用来在 HTTP Content-type 头信息中发送正确的信息,如: header("Content-type: image/jpeg");
实例1:本地图片文件
<?php
list($width, $height, $type, $attr) = getimagesize("runoob-logo.png");
echo "宽度为:" . $width;
echo "高度为:" . $height;
echo "类型为:" . $type;
echo "属性:" . $attr;
?>
以上实例输出结果为:
宽度为:290
高度为:69
类型为:3
属性:width="290" height="69"
image_type_to_extension — 根据指定的图像类型返回对应的后缀名。
语法
string image_type_to_extension ( int $imagetype [, bool $include_dot = TRUE ] )
根据给定的常量 IMAGETYPE_XXX 返回后缀名。
定义和用法
stripos() 函数查找字符串在另一字符串中第一次出现的位置(不区分大小写)。
查找 "php" 在字符串中第一次出现的位置:
<?php
echo stripos("I love php, I love php too!","PHP");
?>
输出:7
通关方法
和上一题一样,只是这关判断图片类型的方法不同
pass 16 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 = "上传出错!";
}
}
}
代码分析
int exif_imagetype ( string filename )
exif_imagetype() 读取一个图像的第一个字节并检查其签名。
本函数可用来避免调用其它 exif 函数用到了不支持的文件类型上或和 $_SERVER['HTTP_ACCEPT'] 结合使用来检查浏览器是否可以显示某个指定的图像。
如果发现了恰当的签名则返回一个对应的常量,否则返回 FALSE。返回值和 getimagesize() 返回的数组中的索引 2 的值是一样的,但本函数快得多。
值 常量
1 IMAGETYPE_GIF
2 IMAGETYPE_JPEG
3 IMAGETYPE_PNG
4 IMAGETYPE_SWF
5 IMAGETYPE_PSD
6 IMAGETYPE_BMP
7 IMAGETYPE_TIFF_II(Intel 字节顺序)
8 IMAGETYPE_TIFF_MM(Motorola 字节顺序)
9 IMAGETYPE_JPC
10 IMAGETYPE_JP2
11 IMAGETYPE_JPX
12 IMAGETYPE_JB2
13 IMAGETYPE_SWC
14 IMAGETYPE_IFF
15 IMAGETYPE_WBMP
16 IMAGETYPE_XBM
通关方法
和前面两题一样,只是这关判断图片类型的方法不同
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的图片文件!";
}
}
代码分析
basename() 函数返回路径中的文件名部分。
语法
basename(path,suffix)
实例
<?php
$path = "/testweb/home.php";
echo basename($path) ."<br/>";
echo basename($path,".php");
?>
上面的代码将输出:
home.php
home
imagecreatefromjpeg(string $filename) — 由文件或 URL 创建一个新图象。
成功后返回图象资源,失败后返回 FALSE 。
filename JPEG 图像的路径。
定义和用法
unlink() 函数删除文件。
如果成功,该函数返回 TRUE。如果失败,则返回 FALSE。
语法
unlink(filename,context)
filename 必需。规定要删除的文件。
context 可选。规定文件句柄的环境。context 是一套可以修改流的行为的选项。
srand(seed) 函数播下随机数发生器种子。
seed 可选。用 seed 播下随机数发生器种子。
strval() — 获取变量的字符串值。
imagejpeg(resource $image [, string $filename [, int $quality ]]) — 输出图象到浏览器或文件。
image
由图象创建函数(例如imagecreatetruecolor())返回的图象资源。
filename
文件保存的路径,如果未设置或为 NULL,将会直接输出原始图象流。
如果要省略这个参数而提供 quality 参数,使用NULL。
quality
quality 为可选项,范围从 0(最差质量,文件更小)到 100(最佳质量,文件最大)。默认为 IJG 默认的质量值(大约 75)。
二次渲染是指服务器把我们上传的图片内容信息提取,然后重新组合我们可以获取到重新生成的图片,然后和我们的图片马进行对比看他从新生成后的和我们上传的原始文件的差异,进而再次构造图片马
通关方法
我们先上传一张gif图片马,然后分别使用010打开服务器内二次渲染过的和原本的gif,进行对比。发现我们写入的后门代码消失了。
在两个图片相同的位置,写入后门代码,然后再次上传一次图片(渲染过的图片)。
通过文件包含漏洞访问图片,后门代码被执行
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() 函数重命名文件或目录。
如果成功,该函数返回 TRUE。如果失败,则返回 FALSE。
语法
rename(oldname,newname,context)
参数 描述
oldname 必需。规定要重命名的文件或目录。
newname 必需。规定文件或目录的新名称。
context 可选。规定文件句柄的环境。context 是一套可以修改流的行为的选项。
在本题,如果上传的文件后缀名不在白名单里面,服务器会直接删除你上传的文件。但是服务器在判断上传的文件是否在白名单前,就已经将文件上传至服务器中了,如果不满足条件再进行删除文件操作。
通关方法
通过使用burpsuite的爆破模块,在文件删除前访问文件。
-
抓包,把数据包发到Intruder模块,添加变量
-
设置数据包数量
-
正在发送数据包
-
访问后门文件,要不断刷新,以达到在文件上传后删除前访问文件的目的。
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 . '文件夹不存在,请手工创建!';
}
}
代码分析
定义和用法
pathinfo() 函数以数组的形式返回关于文件路径的信息。
返回的数组元素如下:
[dirname]: 目录路径
[basename]: 文件名
[extension]: 文件后缀名
[filename]: 不包含后缀的文件名
语法
pathinfo(path,options)
参数 描述
path 必需。规定要检查的路径。
options 可选。规定要返回的数组元素。默认是 all。
可能的值:
PATHINFO_DIRNAME - 只返回 dirname
PATHINFO_BASENAME - 只返回 basename
PATHINFO_EXTENSION - 只返回 extension
PATHINFO_FILENAME - 只返回 filename
在本题,使用了POST方法来对我们的文件名进行接收$file_name = $_POST['save_name']
,然后再将它拼接到文件路径上。
通关方法
使用burpsuite抓包,修改文件名
方法1:使用%00截断后面的格式。
方法2:使用.php/.结尾,将文件伪造成文件夹,以绕过黑名单
pass 21 MIME、数组、%00绕过
源码
$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 = "请选择要上传的文件!";
}
代码分析
explode(separator,string,limit) 函数把字符串打散为数组。
separator 必需。规定在哪里分割字符串。
string 必需。要分割的字符串。
limit 可选。规定所返回的数组元素的数目。
可能的值:
大于 0 - 返回包含最多 limit 个元素的数组
小于 0 - 返回包含除了最后的 -limit 个元素以外的所有元素的数组
0 - 返回包含一个元素的数组
strtolower(string) 函数把字符串转换为小写。
empty() 函数用于检查一个变量是否为空。
empty() 判断一个变量是否被认为是空的。当一个变量并不存在,或者它的值等同于 FALSE,那么它会被认为不存在。如果变量不存在的话,empty()并不会产生警告。
end() 函数将数组内部指针指向最后一个元素,并返回该元素的值(如果成功)。
reset() 函数将内部指针指向数组中的第一个元素,并输出。
count(array,mode) 函数返回数组中元素的数目。
array 必需。规定数组。
mode 可选。规定模式。可能的值:
0 - 默认。不对多维数组中的所有元素进行计数
1 - 递归地计数数组中元素的数目(计算多维数组中的所有元素)
在本题首先检查了我们上传文件的MIME类型,然后判断我们由POST接收的文件名是否为空,然后判断我们的file变量如果不是数组,那么我们将file变量先转换为小写然后再用explode打成数组(以点为分隔符),然后取它的后缀名,然后判断如果我们取的后缀名在白名单里面,然后取到了我们的文件名,最后进行移动。
通关方法
上传一个图片马,使用burpsuite抓包,将save_name拆分为数组,数组的最后一个字符串为(‘jpg’,‘png’,‘gif’),以绕过第二道白名单。