- CVE-2024-1086 ใน
nf_tablesของเคอร์เนล Linux เกิดจากการตรวจสอบอินพุต verdict ของ Netfilter ล้มเหลว ทำให้เกิดการ free ซ้ำของsk_buffและเมื่อเงื่อนไขเหมาะสมอาจนำไปสู่การยกระดับสิทธิ์ภายในเครื่องได้ - ลำดับการโจมตีทำให้ skb ที่ถูก free ระหว่างการประมวลผล
NF_DROPยังถูกประมวลผลต่อเหมือนNF_ACCEPTส่งผลให้อ็อบเจ็กต์เดียวกันถูก free อีกครั้งในเส้นทางถัดไป - PoC ได้รับการตรวจสอบบน KernelCTF mitigation, Debian, Ubuntu และเคอร์เนล vanilla โดยอย่างน้อยช่วง v5.14.21~v6.6.14 จะอยู่ในข่ายได้รับผลกระทบตามค่า kconfig และมีการปล่อยแพตช์แก้ไขใน stable branch เมื่อเดือนกุมภาพันธ์ 2024
- แกนหลักคือ Dirty Pagedirectory ซึ่งเป็นเทคนิค KSMA แบบ data-only ที่จัดสรรหน้า PTE และ PMD ซ้ำลงบน physical page เดียวกัน เพื่อเข้าถึง physical address ใดก็ได้ด้วยการอ่าน/เขียนจาก user space เท่านั้น
- มีการผสานการค้นหา physical KASLR, การเลี่ยง
modprobe_path, fileless execution และการเชื่อม file descriptor เพื่อหลุดออกจาก namespace จนเป็นกรณี LPE เชิงปฏิบัติที่มุ่งโจมตีทั้งการจัดการหน่วยความจำของเคอร์เนลและ network subsystem
เงื่อนไขของช่องโหว่และขอบเขตผลกระทบ
- บั๊ก
nf_tablesถูกจดทะเบียนเป็น CVE-2024-1086 โดยประเด็นหลักคือการตรวจสอบอินพุตในกระบวนการจัดการ verdict ของ Netfilter ในเคอร์เนล Linux ล้มเหลว ทำให้อนุญาต ค่า drop error แบบบวก ได้ - การ exploit ต้องมีเงื่อนไขต่อไปนี้
- ต้องเปิดใช้งาน
nf_tables - ต้องเปิดใช้งาน unprivileged user namespace
- ในดิสโทรหลักอย่าง Debian และ Ubuntu หากการตั้งค่านี้เปิดเป็นค่าเริ่มต้น จะถือเป็นสมมติฐานของการโจมตี
- ต้องเปิดใช้งาน
- จากการทดสอบ stable branch
linux-5.15.y,linux-6.1.y,linux-6.6.yอยู่ในข่ายได้รับผลกระทบ และlinux-6.7.1ก็มีความเป็นไปได้เช่นกัน - มีการปล่อย การแก้ไขบั๊ก ใน stable branch เมื่อเดือนกุมภาพันธ์ 2024
- ซอร์สโค้ด PoC เผยแพร่ไว้ที่ CVE-2024-1086 PoC repository
ลำดับโค้ดที่ทำให้เกิด double-free
- verdict ของ Netfilter คือค่าที่ตัดสินว่าจะ drop, accept หรือส่งแพ็กเก็ตเข้าคิว เป็นต้น
- ลำดับที่มีช่องโหว่เริ่มจาก
nft_verdict_init()ไม่ได้จำกัดค่า verdict ที่รับจากผู้ใช้อย่างเพียงพอ ทำให้สามารถตั้งค่าที่ดูเหมือนNF_DROPแต่มี drop error เป็นค่าบวกได้ - เมื่อ
nf_hook_slow()ดูบิตล่างของ verdict แล้วตัดสินว่าเป็นNF_DROPจะ free skb ก่อนด้วยkfree_skb_reason() - หลังจากนั้น หากผลลัพธ์ของ
NF_DROP_GETERR()คืนค่าเป็นค่าที่สอดคล้องกับNF_ACCEPTฝั่ง caller จะมองว่าแพ็กเก็ตถูก accept และประมวลผลต่อ - ผลคือ skb ที่ถูก free ไปแล้วถูก free ซ้ำอีกครั้งในเส้นทางถัดไป ก่อให้เกิด double-free primitive
อ็อบเจ็กต์ที่เสียหายและวิธีประมวลผลแพ็กเก็ต
- double-free ส่งผลต่อ
struct sk_buffในskbuff_head_cacheและอ็อบเจ็กต์sk_buff->head sk_buff->headเก็บเนื้อหาแพ็กเก็ตจริง และตามขนาดของแพ็กเก็ต IPv4 อาจถูกจัดสรรตั้งแต่kmalloc-256ไปจนถึง order 4 page ของ buddy allocator- PoC ถูกออกแบบให้ใช้แพ็กเก็ต IP ขนาดใหญ่เพื่อให้เข้าสู่เส้นทาง buddy allocator แทน slab allocator
- fragmentation queue ของ IPv4 ถูกใช้เพื่อหน่วงเวลาการ free ครั้งที่สองของ skb หรือชักนำให้เกิดขึ้นในเวลาที่ต้องการ
- หากฟิลด์ skb ที่เสียหายถูกใช้งานในเส้นทางแพ็กเก็ต อาจทำให้เคอร์เนล panic ได้ จึงหลีกเลี่ยง TCP/UDP stack และใช้เส้นทางข้อผิดพลาดของ IP fragment เฉพาะแทน
ขอบเขตการทดสอบและอัตราความสำเร็จ
- มีการทดสอบเคอร์เนลหลายเวอร์ชันและการตั้งค่าหลายแบบในสภาพแวดล้อม vanilla kernel, KernelCTF, Debian และ Ubuntu
- กรณีที่สำเร็จรวมถึงสภาพแวดล้อมต่อไปนี้
- Linux v5.14.21, v5.15.148, v5.16.20, v5.17.15, v5.18.19, v5.19.17, v6.0.19
- Linux v6.1.55 ของ KernelCTF Mitigation v3
- Linux v6.1.69 ของ Debian Bookworm 6.1.0-17
- Linux v6.1.72 ของ KernelCTF LTS
- Ubuntu Jammy v6.2.0-37
- Linux v6.2.16, v6.3.13
- กรณีที่ล้มเหลวรวมถึง v5.4.270, v5.10.209, v6.4.16, Ubuntu Jammy v6.5.0-15, v6.5.13, v6.6.14, v6.7.1 เป็นต้น
- ความล้มเหลวบางส่วนหลัง v6.4.0 เชื่อมโยงกับการตรวจจับ bad_page() จาก
CONFIG_INIT_ON_ALLOC_DEFAULT_ON=y - ในสภาพแวดล้อม v6.4.16 อัตราความสำเร็จอยู่ที่ 99.4% และบางกรณีลดลงถึง 93.0% โดยจำนวนตัวอย่างคือ n=1000 ทั้งสองกรณี
วิธีแก้ไข
- การแก้ไขที่เสนอในตอนแรกอาจสร้าง breaking change กลาง Netfilter stack ได้
- การแก้ไขของ maintainer ของ Netfilter จำกัด verdict ที่มาจากอินพุตผู้ใช้ให้เข้มงวดขึ้นในระดับ API
- แพตช์ใช้วิธีปฏิเสธพารามิเตอร์ verdict แบบ DROP/QUEUE สำหรับอินพุตจาก userland
- ตามคำอธิบาย CVE
nft_verdict_init()อนุญาตค่าบวกเป็น drop error ภายใน hook verdict และnf_hook_slow()อาจสร้าง double free ได้ในสถานการณ์ที่NF_DROPและNF_ACCEPTทับซ้อนกัน - ดูแพตช์แก้ไขได้ที่ [PATCH nf] netfilter: nf_tables: reject QUEUE/DROP verdict parameters.
Dirty Pagedirectory และการเข้าถึง physical memory ใดก็ได้
- เทคนิคหลักของ PoC คือ Dirty Pagedirectory ซึ่งเป็นรูปแบบดัดแปลงของเทคนิค Dirty Pagetable เดิม
- ไอเดียหลักคือการจัดสรรหน้า PTE และหน้า PMD ซ้ำให้ชี้ไปยัง physical page เดียวกัน
- เมื่อเขียนค่าที่ดูเหมือนค่า PTE ลงในพื้นที่ virtual address หนึ่ง พื้นที่ virtual address อีกส่วนจะตีความค่านั้นเป็น page table entry และแมป physical page ที่ระบุ
- วิธีนี้ทำงานเป็น kernel-space mirroring attack ที่เข้าถึง physical address ใดก็ได้และกำหนด permission flags ได้ด้วยการอ่าน/เขียน address ใน user space เท่านั้น
- ใช้สำหรับเลี่ยงมาตรการบรรเทาอย่าง virtual KASLR, KPTI, SMAP, SMEP และ
CONFIG_STATIC_USERMODEHELPER
การจัดการ page allocator
- การจัดสรรหน้าของเคอร์เนลเกี่ยวข้องกับ slab allocator, buddy allocator และ PCP allocator
- skb head ขนาดใหญ่ใช้ order 4 page ของ buddy allocator แต่หน้า PTE/PMD เป็น order 0 page จึงไม่เข้ากันโดยตรง
- เพื่อแก้ปัญหานี้ มีการใช้วิธี page conversion สองแบบ
- PCP list draining: ใส่ order 4 page ลงใน buddy freelist แล้วทำให้ PCP order 0 freelist ว่าง เพื่อให้ buddy allocator เติมกลับมาเป็น order 0 page
- race condition: ใช้การแข่งขันระหว่างการ free ครั้งที่สองเพื่อใส่ order 4 page ลงใน order 0 freelist
- PCP list draining เป็นวิธีที่เรียบง่าย เสถียรกว่า และเร็วกว่า
- วิธี race condition เคยใช้ใน exploit ช่วงแรกของ KernelCTF แต่ถูกจัดว่า obsolete เพราะพึ่งพาสภาพแวดล้อมที่มีความหน่วง serial TTY สูงอย่าง QEMU VM
การเลี่ยง KernelCTF mitigation
- ในสภาพแวดล้อม KernelCTF mitigation มาตรการบรรเทาที่ต้องเลี่ยงอย่างจริงจังคือการตรวจสอบ freelist corruption ของ
sk_buff - เนื่องจาก
skbuff_head_cache->offset == 0x70ทำให้ freelist next pointer ทับกับskb->len - หลังจาก free skb ครั้งแรก
skb->lenจะถูกเขียนทับด้วยบางส่วนของ freelist pointer และระหว่างการ parse แพ็กเก็ตถัดมา ค่านี้อาจเปลี่ยนจนถูกตรวจจับด้วย corruption check - เลี่ยงการตรวจจับโดย free skb ปกติเพิ่มเติมทับบน skb ที่เสียหาย เพื่อเขียนทับ freelist head
- ผู้พัฒนา KernelCTF เห็นว่าหากตรวจสอบ freelist head next pointer ตอน free ด้วย ก็อาจบรรเทาการเลี่ยงนี้ได้
TLB flush และการค้นหา physical KASLR
- เมื่อเปลี่ยน page table ด้วย Dirty Pagedirectory ในรูปแบบที่ไม่คาดคิด ข้อมูล translation เก่าอาจค้างอยู่ใน TLB cache ของ CPU
- ทำการ flush TLB จาก user space โดยให้ child หลัง
fork()เรียกmunmap()แล้วเข้าสู่สถานะ sleep - วิธีนี้ได้รับการยืนยันว่าทำงาน 100% บน CPU AMD และ QEMU VM
- การค้นหา physical KASLR ลดช่วงค้นหาโดยอาศัยข้อเท็จจริงที่ว่า kernel physical base address ถูก align ตาม
CONFIG_PHYSICAL_STARTหรือCONFIG_PHYSICAL_ALIGN - หากสมมติ physical memory 8GiB และ alignment 16MiB จะมี candidate 512 ค่า และใช้สคริปต์ get-sig สร้าง kernel base signature เพื่อระบุค่า
modprobe_path และการได้ root shell
- หลังจากได้สิทธิ์อ่าน/เขียน physical memory ใดก็ได้ PoC จะสแกนช่วงประมาณ 80MiB หลัง kernel base เพื่อหา
modprobe_path - ในการตั้งค่าทั่วไป จะค้นหาแพตเทิร์น
"/sbin/modprobe"พร้อม null padding และตรวจสอบตัวแปรจริงจากการสะท้อนไปยัง/proc/sys/kernel/modprobe - หากเปิดใช้
CONFIG_STATIC_USERMODEHELPERจะเล็งสตริง"/sbin/usermode-helper"แทน - เพื่อให้ได้ root shell จะเขียนทับ
modprobe_pathหรือสตริง static usermode helper ด้วยพาธ memfd ในรูป/proc/<pid>/fd/<fd> - สคริปต์ privilege escalation เชื่อม file descriptor ของ exploit เข้ากับ stdin/stdout ของ shell เพื่อให้ทำงานได้ทั้งบน local terminal และ reverse shell
Fileless execution และโครงสร้าง PoC
- PoC รองรับ fileless execution ที่ไม่เขียนไฟล์ลงดิสก์
- หากเป้าหมายมี Perl สามารถใช้
memfd_create()โหลดไบนารี exploit ขึ้นหน่วยความจำและรันผ่าน/proc/$$/fd/<fd>ได้ - dependency สำหรับคอมไพล์คือ
libnftnl-devและlibmnl-dev - static build สำหรับ KernelCTF ใช้
musl-gccซึ่งเป็นการเลือกเพื่อหลีกเลี่ยงปัญหา static linking ของ glibc และปัญหา AVX512 opcode ใน QEMU - ซอร์ส exploit แบ่งเป็นหลายไฟล์ และเนื่องจากมีเป้าหมายเป็นไบนารีรันเดี่ยว เมื่อเกิดข้อผิดพลาดจึงเลือก crash/exit มากกว่าการคืน error code
ความเสถียรและข้อจำกัด
- สถานะ pagetable ของโปรเซส exploit อาจไม่เสถียร ดังนั้นหลังสำเร็จหรือล้มเหลวจะไม่จบ child process แต่ปล่อยให้อยู่ในสถานะ sleep เพื่อลดความไม่เสถียรของเคอร์เนล
- หากมีกิจกรรมเครือข่าย จะเกิด noise ใน skb freelist และส่งผลต่อความเสถียรได้
- ในสภาพแวดล้อม SSH หรือ reverse shell จะลดการพิมพ์ stdout รอบช่วง double-free เพื่อลดการจัดสรร/ปล่อย skb จากเครือข่าย
- ในการทดสอบบนฮาร์ดแวร์บางเครื่อง ระบบ crash หลังผ่านไปไม่กี่วินาที และเนื่องจาก WiFi frame ก็ใช้ skb เช่นกัน จึงเป็นไปได้ว่ากิจกรรม WiFi มีผลกระทบ
- เมื่อปิดใช้งานอะแดปเตอร์ WiFi ใน BIOS exploit ทำงานได้ตามปกติในสภาพแวดล้อมดังกล่าว
ข้อสรุปจากกระบวนการวิจัย
- PoC ถูกปรับแต่งโดยมีเป้าหมายเพื่อความเข้ากันได้กว้าง ความเสถียรสูง และการรันแบบซ่อนร่องรอย
- ใช้เวลาพัฒนา 2 เดือน และใช้อีก 2 เดือนเพื่อปรับปรุงความเสถียรและความเข้ากันได้
- ตัว exploit เองไม่ได้พึ่งพาพฤติกรรมของ slab allocator มากนัก แต่ประกอบขึ้นจากฟังก์ชันที่มักเปิดใช้แพร่หลายอย่าง IPv4 subsystem และ virtual memory
- บั๊กตั้งต้นต้องมี unprivileged user namespace และ nftables แต่เทคนิคอย่าง Dirty Pagedirectory และ PCP draining สามารถนำไปใช้กับ exploit จริงอื่น ๆ ได้ด้วย
- งานนี้เป็นกรณีศึกษาที่เจาะลึกทั้ง networking subsystem และ memory management subsystem ของเคอร์เนล Linux ไปพร้อมกัน
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
วันนี้มีการเผยแพร่ exploit แบบพิสูจน์แนวคิดของ CVE-2024-1086 แล้ว และทำงานได้บน Debian, Ubuntu เป็นต้น
เวอร์ชันที่ได้รับผลกระทบคือ Linux kernel v5.14~v6.6 และการรองรับ v6.4~v6.6 ขึ้นอยู่กับการตั้งค่า kernel
CONFIG_INIT_ON_ALLOC_DEFAULT_ONบั๊กนี้ถูกแพตช์ไปแล้วในเดือนกุมภาพันธ์ 2024 ดังนั้นควรอัปเดตเครื่อง Linux
https://github.com/Notselwyn/CVE-2024-1086/blob/main/src/mai...
modprobe_pathและถึงกับใส่การ brute-force pid เพราะต้องการวิธีแบบไร้ไฟล์เช่น อยากถามว่าทำไมไม่เลือกวิธีแพตช์ kernel
.textด้วย shellcode สั้น ๆ เพื่อกลายเป็น root และหลุดออกจาก namespaceอย่างไรก็ตาม ระบุว่ายกเว้น branch ที่แพตช์แล้วคือ
v5.15.149>,v6.1.76>,v6.6.15>ในแพตช์มีประโยคนี้: “This reverts commit e0abdadcc6e1. [...] Its not clear to me why this commit was made.”
อยากรู้ว่ามีใครค้นหา ประวัติความเป็นมา ของ commit นี้แล้วหรือยัง
[1] https://lore.kernel.org/all/20240120215012.129529-1-fw@strle...
ลองค้นในรายชื่อเมล netdev เก่า ๆ แล้วแต่ไม่พบอะไร และอาจเป็นแพตช์ที่ส่งตรงไปยัง committer คือ Pablo Neira Ayuso ก็ได้
ผู้เขียนเดิมคือ Patrick McHardy ซึ่งปัจจุบันกลายเป็นบุคคลที่ผู้คนหลีกเลี่ยง และหาก Pablo จำไม่ได้หรือค้นหาในอีเมลไม่เจอ ก็ดูเหมือนว่าการใช้งานจริงที่แน่ชัดจะยากจะเปิดเผยได้ด้วยการสืบค้นพื้นฐาน
คือ
netfilter: nf_tables: accept QUEUE/DROP verdict parametersซึ่งทำให้ user space สามารถระบุหมายเลข queue หรือรหัส errno ให้กับ verdict แบบQUEUEและDROPได้บทความนี้น่าประทับใจมากแม้ในมุมมองของ การเขียนด้านความปลอดภัย
เวลาจะเขียนบล็อกความปลอดภัย มักต้องคิดเสมอว่า “จะสมมติว่าผู้อ่านมีความรู้พื้นฐานและความรู้เดิมมากแค่ไหน” และการหาสมดุลให้ทั้งเข้าถึงได้และเขียนได้จริงนั้นทำได้ยาก
จุดที่ดีมากคือกำหนดกลุ่มผู้อ่านเป้าหมายก่อนแล้วให้คำอธิบายพื้นฐานอย่างเพียงพอ จึงบุ๊กมาร์กไว้เพื่อใช้เป็นเอกสารแนะนำให้นักวิจัยหน้าใหม่ที่เจอทุกปี
exploit นี้พึ่งพาการเข้าถึง unprivileged user namespace:
sysctl kernel.unprivileged_userns_clone = 1นี่เป็นค่าเริ่มต้นของ kernel บน Debian/Ubuntu และ Arch Linux และถ้าไม่มีความจำเป็นอย่างการรันคำสั่ง Docker โดยไม่ใช้ sudo ก็ควรปิดไว้
Chrome sandbox ที่ใช้ในแอป Electron หรือ 1Password ก็เกี่ยวข้องด้วย และ binary ช่วยงานของ sandbox ยังทำงานเป็นโปรแกรม setuid ได้ด้วย
ถ้า Proton จะเริ่มใช้ user namespace ในอนาคตก็คงไม่น่าแปลกใจ ดังนั้นบน Linux เดสก์ท็อปทั่วไป อาจจะดีกว่าที่จะไม่ปิดไว้
ส่วนบนเซิร์ฟเวอร์หรือ Linux ที่ harden เป็นพิเศษ หลายกรณีก็ไม่เสียหายที่จะปิด
ปัญหาคือเพื่อให้ container “ทำงานได้ทันที” ต้องมีการแฮ็กมากมายในฝั่งเครือข่าย และจุดขายหลักของ Docker ก็แทบจะเป็นการทำแฮ็กอันตรายเหล่านั้นแทนให้
สุดท้ายโซลูชัน container แบบใดก็ตามก็ต้องยอมรับแฮ็กเหล่านั้น และกลายเป็นโครงสร้างที่แม้ฟังก์ชัน namespace ปกติจะลดการเข้าถึงลง แต่ kernel ก็เปิดประตูไว้เพราะการแฮ็กด้านเครือข่าย
ไม่เข้าใจว่าทำไม unprivileged user namespace ถึงเปิดเป็นค่าเริ่มต้น
แม้จะรันอยู่ภายใน namespace แบบ “ไม่มีสิทธิ์พิเศษ” แต่ก็สงสัยว่าทำไมถึงให้ความสามารถแก่ผู้ใช้ในการรันสิ่งอย่าง iptables หรือ mount เป็นค่าเริ่มต้น
ตัวอย่างเช่น Chrome ใช้ namespace เพื่อทำ process sandbox แต่ก็ติดตั้ง binary แบบ setuid-root เพื่อให้ทำได้แม้ไม่มี unprivileged namespace
เนื่องจาก binary แบบ setuid-root เองก็เป็นความเสี่ยงด้านความปลอดภัย ในระยะยาวจึงควรไปในทางที่ Chrome ไม่ต้องติดตั้ง binary แบบนั้น
แต่การจะทำเช่นนั้นได้ unprivileged user namespace ต้องมีให้ใช้อย่างแพร่หลาย และบั๊กแบบนี้ก็ทำให้อนาคตนั้นช้าลง
อีกทั้งโปรแกรมที่สร้าง sandbox อิง namespace อย่าง Chrome มักใช้ seccomp ร่วมด้วย เพื่อป้องกันไม่ให้โค้ดภายใน sandbox ใช้ฟังก์ชัน kernel แปลก ๆ อย่าง namespace
บนเดสก์ท็อปผู้ใช้คนเดียว ประโยชน์จากการบังคับแยกระหว่างผู้ใช้กับ root อย่างเข้มงวดไม่ได้มากนัก และสิ่งที่น่าสนใจส่วนใหญ่ก็เข้าถึงได้แม้จากบัญชีผู้ใช้
ในทางกลับกัน sandbox อย่าง Chrome เป็นสิ่งจำเป็นต่อความปลอดภัยของเดสก์ท็อป ดังนั้นบนเดสก์ท็อปผู้ใช้คนเดียว จึงมักมองว่าการเปิด unprivileged user namespace จะเพิ่มความปลอดภัยโดยรวม
แน่นอนว่าระบบหลายผู้ใช้เป็นอีกเรื่องหนึ่ง
ถ้าไม่มีบั๊กแบบนี้โผล่มาเรื่อย ๆ user namespace แบบไม่ต้องมีสิทธิ์พิเศษก็น่าจะเป็น ฟีเจอร์ด้านความปลอดภัย ที่ยอดเยี่ยม
เช่น คงจะดีถ้าการรัน Flatpak ไม่จำเป็นต้องใช้ไบนารี setuid ของโฮสต์เพื่อแยกแอปออกจากกัน
ค่าเริ่มต้นส่วนใหญ่ไม่ปลอดภัย และต้องอาศัยความรู้ทางเทคนิคกับการตั้งค่าเพิ่มความแข็งแกร่ง
คอมมิตที่ทำให้เกิดปัญหา: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...
ใน
switch/caseที่ซ้อนกันมีreturnอยู่ในเส้นทางเริ่มต้น แต่โครงสร้างที่คาดหวังให้ fall-through หลังจากนั้นดูแปลก ๆhttps://lwn.net/Articles/882397/
https://bugs.launchpad.net/bugs/cve/2024-1086
เป็นช่องโหว่แบบ use-after-free ในคอมโพเนนต์
netfilter: nf_tablesของเคอร์เนล Linux ซึ่งทำให้ยกระดับสิทธิ์ในเครื่องได้ฟังก์ชัน
nft_verdict_init()อนุญาตให้ใช้ค่าบวกเป็น drop error ภายใน hook verdict ส่งผลให้nf_hook_slow()ประมวลผลNF_DROPพร้อมกับ drop error ที่ดูเหมือนNF_ACCEPTจนอาจเกิดช่องโหว่ double-free ได้แนะนำให้อัปเกรดเป็นเวอร์ชันหลัง
f342de4e2f33e0e39165d8639387aa6c19dff660สงสัยว่าการ exploit แบบนี้ทำได้อย่างไร ทั้งที่มี mitigation สมัยใหม่อย่าง ASLR
ตอนเรียนมหาวิทยาลัย เคยมีงานที่ได้รับไบนารีหลายตัวซึ่งต้องรันบน Ubuntu เวอร์ชันเฉพาะ แล้วต้องหาบั๊กอย่าง use-after-free หรือ buffer overflow เพื่อนำไป exploit ซึ่งยากมากจริง ๆ
แค่หาข้อบกพร่องก็ยากแล้ว และการเขียน shellcode ที่ถูกต้องเพื่อให้ข้อบกพร่องนั้นทำงานที่เป็นประโยชน์ได้ยิ่งยากกว่า
ในขั้นที่ยากขึ้น ยังเปิด mitigation อย่าง ASLR และ stack canary ไว้ด้วย และแม้ในสภาพแวดล้อมนักศึกษาที่ควบคุมได้ก็รู้สึกว่าแทบเป็นไปไม่ได้
สุดท้ายต้อง 1) พบข้อบกพร่องที่ exploit ได้ และ 2) หาไบนารี payload ที่ถูกต้องซึ่งทำสิ่งที่เป็นประโยชน์ได้โดยไม่ทำให้โปรแกรมแค่แครช แต่ในโลกจริงน่าจะยากกว่านั้น จึงสงสัยว่าทำได้อย่างไร
[0] https://web.stanford.edu/class/archive/cs/cs107/cs107.1194/a...
เมื่อเรียนควบคู่กับวิชาอื่น เวลาที่ใช้กับบั๊กหนึ่งตัวคงมีตั้งแต่ไม่กี่ชั่วโมงถึงไม่กี่วัน
Zerodium จ่าย 50,000 ดอลลาร์ สำหรับการยกระดับสิทธิ์ในเครื่องบน Linux ทั่วไป และถ้าคิดตามต้นทุนของนักพัฒนา exploit ที่มีทักษะ ก็ซื้อเวลาได้ราว 200 ชั่วโมง-คน
กล่าวคือผู้เชี่ยวชาญใช้เวลามากกว่านักศึกษามือใหม่ 10–100 เท่า
ให้นึกถึงความต่างระหว่างคนที่เพิ่งเข้าร้านงานไม้ครั้งแรกกับช่างไม้, มือใหม่หัดปั้นเครื่องปั้นดินเผากับช่างฝีมือ, หรือจิตรกรปีหนึ่งกับศิลปินมืออาชีพ
แถมยังทุ่มเวลาเพิ่มอีก 10–100 เท่าด้วย
[1] https://zerodium.com/program.html
มีเทคนิค “คลาสสิก” ที่เลี่ยงกลไกป้องกันสมัยใหม่ส่วนใหญ่ได้ และถ้าไม่มีก็มักมีการโจมตีหรือวิธีเลี่ยงแบบใหม่เกิดขึ้น
ตัวอย่างเช่น การเลี่ยงการป้องกันของ heap ดูได้จาก how2heap[0], เคยมีกรณี exploit ที่เลี่ยง KASLR[1], และ exploit ครั้งนี้ดูเหมือนจะใช้เทคนิค dirty pagetable[2]
โครงสร้างจึงเป็น เกมแมวจับหนู ต่อเนื่องระหว่างการเพิ่ม mitigation กับนักวิจัยที่หาทางเลี่ยงมัน
[0] https://github.com/shellphish/how2heap
[1] https://www.willsroot.io/2022/12/entrybleed.html
[2] https://pwning.tech/nftables/#452-the-technique
การค้นพบสิ่งแบบนี้ต้องการเพียงคนประเภทสุดท้ายสักคนก็พอ
ทุกวันนี้มันยากมาก และแม้ปิด mitigation แล้ว การหาช่องโหว่และเขียน exploit ก็ยังไม่ใช่เรื่องง่าย
แต่หลายคนที่ค้นหาช่องโหว่เหล่านี้ทำงานเป็นทีม สามารถรัน fuzzing แบบขนาน และผสานหรือ chain ความรู้กับ exploit อื่น ๆ ได้
ความเชี่ยวชาญและพรสวรรค์ของนักวิจัยบางคนอยู่ในระดับน่าทึ่ง และในสาขานี้ ประสบการณ์หลายปีถึงหลายสิบปีมีคุณค่ามหาศาล
แต่ถ้าคิดว่าบั๊กสำคัญ ๆ ที่ถูกค้นพบในปัจจุบันมาจากทีมที่มีเงินทุนมากหรือ actor ระดับรัฐ ก็เข้าใจได้มากขึ้นว่าการทุ่มกำลังคนและทรัพยากรจำนวนมากสามารถเลี่ยง mitigation ที่มีอยู่ได้
ตามข้อมูลของ Ubuntu รุ่น LTS ทั้งหมดได้รับผลกระทบ และได้รับการแก้ไขแล้วในเคอร์เนลที่แพตช์แล้วปัจจุบัน: https://ubuntu.com/security/CVE-2024-1086
Focal แก้ไขใน
5.4.0-174.193, Jammy ใน5.15.0-101.111, Mantic ใน6.5.0-26.26หากใช้การสนับสนุนแบบขยาย จะรวม Xenial และ Bionic ด้วย
ลองรันบนระบบ Debian ที่มีช่องโหว่แล้ว แม้จะไม่ได้ ยกระดับสิทธิ์ สำเร็จ แต่ในการรันครั้งที่สองทั้งระบบค้างไปเลย
การรันครั้งแรกแค่ล้มเหลวเฉย ๆ ดังนั้นก็ยังคุ้มค่าพอที่จะใช้เวลาแพตช์
สามารถตรวจสอบการตั้งค่าเคอร์เนลปัจจุบันได้จากไฟล์อย่าง
/boot/configหรือ/proc/config.gz