文件上传验证
后缀名,类型,文件头
后缀名:黑名单,白名单
黑名单:明确不允许上传的格式后缀
asp php jsp aspx cgi war…
缺陷:如果php phtml…没有定义到后名单里,可以用这格式绕过限制
白名单:明确可以上传的格式后缀
jpg png zip rar gif …
白名单验证要更安全
文件类型:MIME信息
Content-Type称之为MIME信息
文件头:内容头信息
相关知识
onsubmit 事件
onsubmit 事件会在表单中的确认按钮被点击时发生
getElementsByName类型
document.getElementsByName(‘upload_file’)[0].value
数组的第一个值
因为很多情况下name都有可能相等,所以会有个数组来定义
var类型
var在JavaScript中是一种动态类型、弱类型、基于原型的语言,内置支持类型。
substring() 函数
方法用于提取字符串中介于两个指定下标之间的字符
2个参数,从第一个参数索引到第二个参数索引之间子串,第二个参数可选,若不选,则默认为从第一个参数索引开始到最后的子串
file.lastIndexOf(".")函数
找出最后一个.所在的索引值
allow_ext.indexOf(ext_name)函数
allow_ext.indexOf(ext_name) == -1
返回指定字符串在字符串集中第一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1
substr(string,start,length)
反正string中从start开始到start+length-1的子串
function deldot($s)函数
{//从末尾开始搜索,1.php.返回1.php,1.php.p.返回1.php.p
for($i = strlen($s)-1;$i>0;$i--){
$c = substr($s,$i,1);
if($i == strlen($s)-1 and $c != '.'){
return $s;
}
if($c != '.'){
return substr($s,0,$i+1);
}
}
}
PHP strrchr(a,b) 函数
搜索 “b” 在字符串中的位置,并返回从该位置到字符串结尾的所有字符。
stripos() 函数
查找字符串在另一字符串中第一次出现的位置(不区分大小写)。
getimagesize()函数
获取文件大小
php basename函数
<?php
$path = "/testweb/home.php";
//显示带有文件扩展名的文件名
echo basename($path);
//显示不带有文件扩展名的文件名
echo basename($path,".php");
?>
输出
home.php
home
PHP pathinfo() 函数
演示案例:uploadlabs关卡分析
简要上传表单代码分析解释
less-1
黑盒测试
尝试上传php文件,发现只能是.jpg,.png.gif格式的文件
上传1.jpg(非图片内容,图片后缀文件)成功,且通过弹窗得出是白名单过滤
所以我们要想办法修改后缀使得在服务器中呈现的是php文件
首先我们上传后门文件1.php提交后抓包查看下内容
看到有个属性filename,尝试修改后缀为php提交
成功上传webshell
第二种方法:
禁用js绕过验证
less-2
上传php文件
上传1.jpg(非图片内容,图片后缀文件)成功,且通过弹窗得出是白名单过滤
可以通过第一种方法绕过
或者上传1.php抓包修改数据包的MIME为jpg
less1和less2的区别
less1是通过js验证后才提交表单,所以我们可以通过修改后缀为jpg,让他先通过js验证,然后再在抓包修改表单中的filename的名字,也就是文件名得以绕过
less2是通过表单验证,也就是前端会得到修改后的数据包才会进行验证,所以我们有两次方式绕过。
1、上传jpg文件,因为点击上传抓包后,出现的Content-Type默认为image/jpeg,所以我们修改filename为我们想要的文件名即可。
2、通过查看代码,发现是通过Content-Type进行验证的,所以我们可以上传php文件,将Content-Type的值改为白名单里面的值即可成功绕过。
less3
显示不能上传这类后缀文件,所以是黑名单过滤,所以我们可以尝试修改下后缀名为php5,上传成功。在upload文件中查看,发现其还改了文件名。
能够解析php5前提是apache的httpd.conf中有如下配置代码
AddType application/x-httpd-php .php .phtml .phps .php5 .pht
less4
常规操作后发现上面的方法都不行,所以应该检查的是filename所以我们要想办法绕过filename
查看源码发现
$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");
这一次还是黑名单,但是黑名单加多了,几乎过滤了各种后缀,但是没有过滤.htaccess
。 首先上传.htaccess
,文件内容为AddType application/x-httpd-php .ppp,然后上传ppp文件,就会被解析成php
.htaccess还可以写成:
<FilesMatch "shell.ppp">
SetHandler application/x-httpd-php
</FilesMatch>
这样文件名为shell.ppp就会被解析成php。
less5
做法类似.htaccess,上传一个写有auto_prepend_file=1.jpg为后缀的.user.ini文件,然后在上传一个写有一句话木马的jpg文件,上传成功后,蚁剑连接,但是要注意,题目提示上传目录存在php文件(readme.php),所以上传成功后我们应该访问readme.php
注意:
.htaccess可以覆盖apache的配置文件,而.user.ini则可以覆盖php.ini的配置。
.htaccess文件只能用于apahce,不能用于iis和nginx等中间件
.user.ini只能用于Server API为FastCGI模式下,而正常情况下apache不是运行在此模块下的。
.htaccess和.user.ini都只能用于访问本目录下的文件时进行覆盖。
less6
这题还是后缀黑名单过滤,也过滤了.htaccess和.ini,但是没有将文件后缀名转换为小写,故大写绕过即可。
less7
查看源码,发现少了:
$file_ext = trim($file_ext); //收尾去空
这样我们可以在文件名后缀最后面加上空格,来绕过黑名单的过滤。
less8
查看源码,少了:
$file_name = deldot($file_name);//删除文件名末尾的点
少了对点的过滤,因此直接在文件名后缀后面加上.即可。
注意:.php ,.php.都会在win里面自动解析为.php
less9
查看代码发现少了
::
D
A
T
A
作
用
:
p
h
p
在
w
i
n
d
o
w
s
中
如
果
文
件
名
+
"
:
:
DATA作用:php在windows中如果文件名+"::
DATA作用:php在windows中如果文件名+"::DATA"会把::
D
A
T
A
之
后
的
数
据
当
成
文
件
流
处
理
,
不
会
检
测
后
缀
名
.
且
保
持
"
:
:
DATA之后的数据当成文件流处理,不会检测后缀名.且保持"::
DATA之后的数据当成文件流处理,不会检测后缀名.且保持"::DATA"之前的文件名(Windows文件流特性)
上传一句话木马的.php后缀文件,抓包加::$DATA,上传成功
less10
首先需要了解deldot函数,当deldot函数检测到末尾的第一个点时将继续从后向前检测,当检测到空格时就停下来,因此当后缀是php.[空格].时,经过deldot函数过滤后,后缀变成php.[空格],最终绕过黑名单,且上传上去的文件名为php.,在windows下会自动去除点。
less11
f i l e n a m e = s t r i r e p l a c e ( file_name = str_ireplace( filename=strireplace(deny_ext,"", $file_name);很明显是双写绕过,str_ireplace()函数的作用如下:
把字符串 “Hello world!” 中的字符 “WORLD”(不区分大小写)替换成 “Shanghai”:
<?php
echo str_ireplace("WORLD","Shanghai","Hello world!");
?>
抓包,把.php后缀改为.pphphp,成功绕过
less12
发现双写绕过不行了
查看代码发现多了一行代码
$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
这行代码是找到后缀名然后进行检查是否在白名单里
所以我们可以进行00截断
截断条件:
1、php版本小于5.3.4
2、php.ini的magic_quotes_gpc为OFF状态
原理
url中%00表示ascll码中的0 ,而ascii中0作为特殊字符保留,表示字符串结束,所以当url中出现%00时就会认为读取已结束
使用情况
上传时路径可控,使用00截断
文件下载时,00截断绕过白名单检查
文件包含时,00截断后面限制(主要是本地包含时)
其它与文件操作有关的地方都可能使用00截断。
分析
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
构造sava_path=/upload/1.php%00绕过
然后访问1.php即可
less13
跟less12的方法类型,不过这个是post提交,POST不会像GET那样对%00进行自动解码,因此,我们需要在burp中选中%00右击->url->urldecode go即可。
less14
制作文件马
方法一
1.准备一张图片1.jpg,准备一句话php木马1.php
2.通过控制台cmd命令制造图片马
3.进入两个文件的文件夹打开控制台输入命令: copy 1.php/b+1.jpg 2.jpg
4.命令解释:
使用CMD制作一句话木马。
参数/b指定以二进制格式复制、合并文件; 用于图像类/声音类文件
参数/a指定以ASCII格式复制、合并文件。用于txt等文档类文件
copy 1.jpg/b+1.php 2.jpg
//意思是将1.jpg以二进制与1.php合并成2.jpg
那么2.jpg就是图片木马了。
方法二
1.打开010editor
2.以十六进制的方式打开1.jpg
3.在最后末尾加上一句话木马代码
4.保存即可
或者用notepad++也是可以的
上传照片即可然后打开路径
效果:
验证机制
function getReailFileType($filename){
$file = fopen($filename, "rb");
$bin = fread($file, 2); //只读2单元(即2个16进制)
fclose($file);
$strInfo = @unpack("C2chars", $bin); //用unpack将16进制转化为有符号的2进制
$typeCode = intval($strInfo['chars1'].$strInfo['chars2']); //把两个chars连接起来再用intval转换为整数型
$fileType = '';
switch($typeCode){
case 255216:
$fileType = 'jpg';
break;
case 13780:
$fileType = 'png';
break;
case 7173:
$fileType = 'gif';
break;
default:
$fileType = 'unknown';
}
return $fileType;
}
验证机制是获取文件头的内容头信息
jpg的文件头的内容头信息是FFD8FFE1
读2个单元FF和D8
转为2进制就是255,216,连起来就是255216,为jpg文件,通过验证
less15
图片马制作是一样的
验证机制
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){ //如果属于$types中的一个
return $ext;
}else{
return false;
}
}else{
return false;
}
}
less16
验证机制
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;
}
}
exif_imagetype() 读取一个图像的第一个字节并检查其签名。如果发现了恰当的签名则返回一个对应的常量,否则返回 FALSE。返回值跟getimagesize() 返回的数组中的索引 2 的值是一样的,但exif_imagetype函数快得多。
less17(待测试)
发现图片马上传后,里面的php代码给过滤掉了,上传只有php代码的jpg文件后提示文件错误
参考
less18
这里先将文件上传到服务器,然后通过rename修改名称,如果检查到不是合法文件后缀名,再通过unlink删除文件,因此可以通过条件竞争的方式,也就是不断查看上传后的文件,在unlink之前,访问webshell。
原理:
在查看文件的期间,服务器检测后发现是php文件,想要删除,但是用户正在查看此文件是无法删除的。
首先在burp中不断发送上传webshell的数据包:
然后不断在浏览器中访问,发现通过竞争可以访问到:
less19(待测试)
同18做法
less20
上传文件和文件名可控,且文件名是黑名单限制。
原理:
文件名改为19.php/.,move_uploaded_file函数遇到/.的时候会删除它
less 21
PHP reset函数
输出数组中的当前元素和下一个元素的值,然后把数组的内部指针重置到数组中的第一个元素
过滤机制对MIME和文件后缀都进行了检查。 但是取后缀的时候判断 f i l e 是 不 是 数 组 , 可 以 让 file是不是数组,可以让 file是不是数组,可以让file为数组来绕过
$file = explode('.', strtolower($file));
最后上传的时候文件名取的是
$file_name = reset($file) . '.' . $file[count($file) - 1];
综上,$file数组[“20.php”,"",“jpg”],最后一个为jpg可以绕过白名单检查,文件名为:20.php.,再配合move_uploaded_file的特性,最终上传的文件名为20.php。