文件上传绕过waf
php 上传文件的步骤
实现思路
- 传文件
- 取文件
- 移动文件
必备知识
$_FILES
$_FILES 是一个超全局变量二位数组
第一个参数是表单上传文件 input 的name属性值,第二个下标可以是 “name”, “type”, “size”, “tmp_name” 或 “error”
$_FILES数组内容如下:
$_FILES[s’myFile’][‘name’] 客户端文件的原名称。
$_FILESs[‘myFile’][‘type’] 文件的 MIME 类型,需要浏览器提供该信息的支持,例如”image/gif”。
$_FILES[‘myFile’][‘size’] 已上传文件的大小,单位为字节。
$_FILES[‘myFile’][‘tmp_name’] 文件被上传后在服务端储存的临时文件名,一般是系统默认。可以在php.ini的upload_tmp_dir 指定,但 用 putenv() 函数设置是不起作用的。
$_FILES[‘myFile’][‘error’] 和该文件上传相关的错误代码。[‘error’] 是在 PHP 4.2.0 版本中增加的。下面是它的说明:(它们在PHP3.0以后成了常量)关于错误类型
UPLOAD_ERR_OK
值:0; 没有错误发生,文件上传成功。
UPLOAD_ERR_INI_SIZE
值:1; 上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值。
UPLOAD_ERR_FORM_SIZE
值:2; 上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值。
UPLOAD_ERR_PARTIAL
值:3; 文件只有部分被上传。
UPLOAD_ERR_NO_FILE
值:4; 没有文件被上传。
值:5; 上传文件大小为0.这个部分只需要查文档就可以完美的解决了
文件上传中常用的函数
strrchr — 查找指定字符在字符串中的最后一次出现
substr — 返回字符串的子串
in_array — 检查数组中是否存在某个值
date_default_timezone_set — 设定用于一个脚本中所有日期时间函数的默认时区
file_exists — 检查文件或目录是否存在
mkdir — 新建目录
date — 格式化一个本地时间/日期
move_uploaded_file — 将上传的文件移动到新位置
一个简单的上传脚本
1 |
|
http 上传文件数据包的解析
文件上传的本质就是客户端的post 请求,前端需要指定enctype 为 multipart/from-data 才能正常上传
可以先尝试分析一下一个文件上传的包,使用uploadlabs进行尝试
选取一个比较简单的上传包的信息
1 |
|
可以发现在Content-Type
中存在multipart/form-data
和 boundary
multipart/form-data 请求一个文件上传
boundary 简单理解为数据包分隔符,用于区分post 的内容和数据,就是区分每一个项目
文件上传数据包中可以修改的部分
Content-Disposition:一般可更改name:表单参数值,不能更改filename:文件名,可以更改Content-Type:文件 MIME,视情况更改boundary:内容划分,可以更改
waf 如何拦截恶意文件
基于文件名
文件名,文件后缀是否是白名单内的,或者是黑名单内的,进行不同的处理
步骤:
- 获取Request Header里的
Content-Type
值中获取boundary值 - 根据第一步的boundary值,解析POST数据,获取文件名
- 判断文件名是否在拦截黑名单内
- 获取Request Header里的
基于文件内容
根据文件内容中的
- 基于文件头进行检测
- 文件内容扫描是否存在恶意的代码
- 深度内容检测,如果发现是一个压缩包,会进行解压,在进行文件内容检测
基于文件目录权限
用户行为分析
分析用户上传文件时,上下文。比如上传频率,时间,用户啥的
具体绕过步骤
去掉引号
1
2
3Content-Disposition: form-data; name=file_x; filename="xx.php"
Content-Disposition: form-data; name=file_x; filename=xx.php
Content-Disposition: form-data; name="file_x"; filename=xx.php双引号变单引号
1
Content-Disposition: form-data; name='file_x'; filename='xx.php'
大小写绕过
针对三个固定的字符串进行大小写转换
- Content-Disposition
- name
- filename
空格绕过
在
:
;
=
添加1个或者多个空格,不过测试只有filename在=
前面添加空格,上传失败。在filename=后面添加空格
去掉或修改Content-Disposition值
意思就是不指定Content-Disposition 的值,因为有一些WAF 会觉得这个就是form-data 然后进行绕过
交换name和filename的顺序
规定Content-Disposition必须在最前面,所以只能交换name和filename的顺序。
有的WAF可能会匹配name在前面,filename在后面,所以下面姿势会导致Bypass。
1
Content-Disposition: form-data; filename="xx.php"; name=file_x
多个boundary
最后上传的文件是test.php而非test.txt,但是取的文件名只取了第一个就会被Bypass。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15------WebKitFormBoundaryj1oRYFW91eaj8Ex2
Content-Disposition: form-data; name="file_x"; filename="test.txt"
Content-Type: text/javascript
<?php phpinfo(); ?>
------WebKitFormBoundaryj1oRYFW91eaj8Ex2
Content-Disposition: form-data; name="file_x"; filename="test.php"
Content-Type: text/javascript
<?php phpinfo(); ?>
------WebKitFormBoundaryj1oRYFW91eaj8Ex2
Content-Disposition: form-data; name="submit_x"
upload
------WebKitFormBoundaryj1oRYFW91eaj8Ex2--多个filename
最终上传成功的文件名是test.php。但是由于解析文件名时,会解析到第一个。正则默认都会匹配到第一个。
1
Content-Disposition: form-data; name="file_x"; filename="test.txt"; filename="test.php"
多个分号
文件解析时,可能解析不到文件名,导致绕过。
1
Content-Disposition: form-data; name="file_x";;; filename="test.php"
multipart/form-DATA
就是改变他的大小写
Header在boundary前添加任意字符
这个只能说,PHP很皮,这都支持。试了JAVA会报错。
1
Content-Type: multipart/form-data; bypassboundary=----WebKitFormBoundaryj1oRYFW91eaj8Ex2
filename换行
1
2
Content-Disposition: form-data; name="file_x"; file
name="test.php"
可以换行其他部分
name和filename添加任意字符串
1
Content-Disposition: name="file_x"; bypass waf upload; filename="test.php";
如何防御
通过正则
- 由于是文件上传,所以必须有Content-Type: multipart/form-data,先判断这个是否存在。
- POST数据去掉所有换行,匹配是否有
Content-Disposition:.*filename\s*=\s*(.*php)
类似的规则。