DataSight Analytics2
Series note
หมวด Secure Architecture & Design
ภาพรวมโจทย์
ข้อนี้เป็นโจทย์ช่องโหว่เชิงตรรกะใน MCP ที่ต่อช่องโหว่เป็นลำดับได้สวยมาก มันเริ่มจาก service จัดการ plugin ที่หน้าตาเหมือนไม่มีอะไรเลย ถ้าเปิด / จะเจอแค่ health check:
{"status":"ok","server":"datasight-plugin-manager-ctf"}
แต่เบื้องหลังมันเปิด MCP endpoint ออกมาเต็มรูปแบบ และให้เครื่องมือที่ทรงพลังมากเกินไป ทั้ง register plugin, execute plugin, อ่าน config, ดู audit log และเข้าระบบ diagnostics ได้ด้วย
โจทย์จึงไม่ได้แตกจาก bug ตัวเดียว แต่แตกจาก ข้อมูลรั่วหลายชั้นที่ต่อกันได้พอดี
เริ่มจากช่องทางสื่อสารของ MCP
เช่นเดียวกับโจทย์ก่อนหน้า ระบบใช้ /sse เป็น event stream และส่ง endpoint ประมาณ:
event: endpoint
data: /messages/?session_id=c808e811fb7d4c518d8e6f51f2e96e1c
จากนั้นเราสามารถทำ MCP handshake ตามปกติ:
initializenotifications/initializedtools/list
เมื่อดูรายการเครื่องมือ แล้วจะเจอชุดเครื่องมือหลัก:
plugin_list_toolsplugin_register_toolplugin_execute_toolplugin_get_infoplugin_get_configplugin_audit_logplugin_system_diagnostics
แค่เห็นชื่อพวกนี้ก็พอเดาได้แล้วว่า ถ้ามีการแยกสิทธิ์ไม่ดี หรือมีข้อมูลภายในรั่วแม้แต่นิดเดียว เส้นทางโจมตีน่าจะเกิดได้ง่าย
จุดรั่วแรก: config รั่ว
สิ่งที่ผิดพลาดชัดที่สุดคือ plugin_get_config คืนค่าภายในออกมามากเกินความจำเป็น ในคำตอบจะเห็นข้อมูลลักษณะนี้:
{
"plugin_system": {
"_internal_tool_prefix": "_ptools_",
"name_validation": "NFKC+lower+strip"
},
"security": {
"maintenance_key_hash": "...",
"_maintenance_key": "6d61696e7431"
},
"debug": {
"encryption_algo": "xor_rotating"
}
}
ค่าที่สำคัญที่สุดคือ _maintenance_key ซึ่งอยู่ในรูป hex และถอดได้เป็น:
maint1
นี่คือ maintenance key ของระบบ ซึ่งไม่ควรโผล่มาในคำตอบของ config ตั้งแต่แรก
จุดรั่วที่สอง: การลดระดับ maintenance mode
เมื่อเอา key นี้ไปใช้กับ plugin_system_diagnostics เพื่อเปิด maintenance mode ระบบตอบว่า sandbox ถูก downgrade จาก v2 ลงเหลือ v1
ข้อความลักษณะนี้คือสิ่งที่ writeup เดิมพบ:
Status: ENABLED
Sandbox downgraded: v2 -> v1
Advanced security checks disabled
ตรงนี้เป็นจุดเปลี่ยนสำคัญมาก เพราะหมายความว่า มาตรการป้องกันที่เดิมตรวจ normalization, zero-width characters และการ shadow ชื่อ tool ถูกลดระดับลงอย่างตั้งใจโดยระบบเอง
ใช้ zero-width character ทำ tool เงา
เมื่อ sandbox อ่อนลง เราสามารถ register tool ชื่อที่ดูเหมือน built-in เดิม แต่แอบต่างกันด้วย zero-width character เช่น:
get_system_info\u200b
แล้ว register ผ่าน plugin_register_tool
จากนั้นเมื่อเรียก plugin_execute_tool กับชื่อดังกล่าว ระบบกลับไหลเข้า path ภายในที่ไม่ควรถูกเปิด และคืนข้อมูลลักษณะ:
Internal Configuration (encrypted):
encrypted_flag: 021f75753f3e57421b270402280c475a703704451b370701340c575a70625a6d29674746772149
encryption: xor_rotating
note: Use the correct decryption key to reveal the flag.
นี่หมายความว่าเราหลุดเข้า debug/shadow path สำเร็จแล้ว
ดึง key จาก audit log
แม้จะมี encrypted_flag แล้ว แต่ยังต้องมี key สำหรับถอดรหัส จุดนี้ระบบพลาดอีกครั้งผ่าน plugin_audit_log เพราะ detailed log หลุดบริบทการทำงาน ออกมาด้วย เช่น:
encryption_context:
algo: xor_rotating
key_hex: 44533432
key_len: 4
เมื่อได้ key_hex แล้ว เรามีทั้ง ciphertext และ key ครบถ้วน
วิธีจบโจทย์
มีสองทาง:
ถอด XOR เองจาก
encrypted_flagกับkey_hexใช้
debug_keyส่งกลับเข้าplugin_execute_toolเพื่อให้ระบบเปิด flag แบบตรง ๆ
ใน writeup เดิม วิธีหลังให้ผลลัพธ์เป็น:
flag: FLAG{mcp_t00l_sh4d0w_d33p_ch41n_m4st3r}
และนั่นคือ flag สุดท้าย
ทำไมโจทย์นี้ถึงดีมาก
เพราะมันเป็นตัวอย่างที่ชัดมากของลำดับช่องโหว่เชิงตรรกะ:
config รั่ว หลุด maintenance key
maintenance mode ลดระดับ sandbox
weakened normalization เปิดทางให้ shadow ชื่อ tool
shadow path หลุด encrypted flag
audit log หลุด decryption key
debug path หรือการถอดรหัสเองคืน flag จริง
ไม่มี memory corruption, ไม่มี RCE, ไม่มี payload หวือหวา แต่ระบบก็พังหมดได้จากการเปิดข้อมูลภายในทีละน้อย
บทเรียนจากโจทย์นี้
ระบบ MCP หรือ plugin manager ควรแยกหน้าที่ของเครื่องมือชัดมาก:
config endpoints ต้องไม่เปิดค่าลับ
maintenance mode ต้องไม่ลดการป้องกันในแบบที่ผู้โจมตีใช้ต่อได้
audit logs ต้องไม่หลุด key material ระหว่าง runtime
การตรวจชื่อ ต้อง normalize และ compare อย่างสอดคล้องทุกจุด
ถ้าปล่อยให้มาตรการป้องกันแต่ละส่วนคิดถึงบริบทของตัวเองอย่างเดียว โดยไม่มองทั้งลำดับ ผู้โจมตีก็จะเป็นคนประกอบ puzzle แทนเรา
Flag
FLAG{mcp_t00l_sh4d0w_d33p_ch41n_m4st3r}