- CVE-2024-1086 trong
nf_tablescủa nhân Linux tạo ra lỗi giải phóng képsk_buffdo thất bại xác thực đầu vào verdict của Netfilter, và khi hội đủ điều kiện có thể dẫn đến leo thang đặc quyền cục bộ - Luồng tấn công khiến skb đã được giải phóng trong quá trình xử lý
NF_DROPtiếp tục được xử lý nhưNF_ACCEPT, khiến cùng một đối tượng bị giải phóng lại ở đường đi phía sau - PoC đã được kiểm chứng trên KernelCTF mitigation, Debian, Ubuntu và nhân vanilla; tối thiểu dải v5.14.21~v6.6.14 bị ảnh hưởng tùy theo kconfig, và bản sửa đã được phát hành lên nhánh stable vào tháng 2/2024
- Trọng tâm là Dirty Pagedirectory, một biến thể KSMA thuần dữ liệu, trong đó trang PTE và PMD được cấp phát chồng lên cùng một trang vật lý để truy cập địa chỉ vật lý tùy ý chỉ bằng đọc/ghi từ không gian người dùng
- Kỹ thuật này còn kết hợp dò tìm physical KASLR, vượt
modprobe_path, thực thi không cần tệp và nối file descriptor để thoát namespace, trở thành một ví dụ LPE thực chiến nhắm đồng thời vào quản lý bộ nhớ của kernel và subsystem mạng
Điều kiện lỗ hổng và phạm vi ảnh hưởng
- Lỗi
nf_tablesđược đăng ký là CVE-2024-1086, với cốt lõi là lỗi xác thực đầu vào cho phép drop error dương trong xử lý verdict của Netfilter trên nhân Linux - Việc khai thác cần các điều kiện sau
nf_tablesphải được bật- user namespace không đặc quyền phải được bật
- Trên các bản phân phối lớn như Debian và Ubuntu, đây thường là cấu hình bật mặc định, tạo tiền đề cho tấn công
- Theo thử nghiệm, các nhánh stable
linux-5.15.y,linux-6.1.y,linux-6.6.ynằm trong vùng ảnh hưởng, vàlinux-6.7.1cũng có khả năng bị tác động - Bản vá lỗi đã được phát hành lên nhánh stable vào tháng 2/2024
- Mã nguồn PoC được công bố tại CVE-2024-1086 PoC repository
Luồng mã tạo ra giải phóng kép
- Verdict của Netfilter là giá trị quyết định có drop gói tin, accept, đưa vào queue hay không
- Luồng dễ bị tấn công bắt đầu từ việc
nft_verdict_init()không giới hạn đủ chặt giá trị verdict do người dùng nhập, nên có thể đặt một giá trị trông nhưNF_DROPnhưng lại có drop error dương nf_hook_slow()nhìn các bit thấp của verdict và nếu xác định làNF_DROPthì sẽ giải phóng skb trước bằngkfree_skb_reason()- Sau đó, nếu kết quả của
NF_DROP_GETERR()trả về một giá trị tương ứng vớiNF_ACCEPT, phía gọi sẽ coi gói tin đã được chấp nhận và tiếp tục xử lý - Kết quả là skb đã giải phóng vẫn đi tiếp theo luồng phía sau và bị giải phóng lần nữa, tạo ra primitive double-free
Đối tượng bị hỏng và cách xử lý gói tin
- Lỗi giải phóng kép ảnh hưởng đến
struct sk_bufftrongskbuff_head_cachevà đối tượngsk_buff->head sk_buff->headchứa nội dung gói tin thực tế, và tùy kích thước gói IPv4 có thể được cấp phát từkmalloc-256cho tới trang order 4 của buddy allocator- PoC được thiết kế dùng gói IP lớn để đi vào đường buddy allocator thay vì slab allocator
- Fragmentation queue của IPv4 được tận dụng để trì hoãn lần giải phóng skb thứ hai hoặc kích hoạt nó đúng thời điểm mong muốn
- Nếu các trường skb bị hỏng được dùng tiếp trong đường đi gói tin, kernel có thể panic; vì vậy PoC tránh stack TCP/UDP và dùng một số đường lỗi fragment IP cụ thể
Phạm vi kiểm thử và tỷ lệ thành công
- Nhiều phiên bản và cấu hình kernel đã được kiểm thử trên môi trường vanilla, KernelCTF, Debian và Ubuntu
- Các môi trường khai thác thành công gồm
- 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 trên KernelCTF Mitigation v3
- Linux v6.1.69 trên Debian Bookworm 6.1.0-17
- Linux v6.1.72 trên KernelCTF LTS
- Ubuntu Jammy v6.2.0-37
- Linux v6.2.16, v6.3.13
- Các môi trường thất bại gồm v5.4.270, v5.10.209, v6.4.16, Ubuntu Jammy v6.5.0-15, v6.5.13, v6.6.14, v6.7.1
- Một phần nguyên nhân thất bại từ v6.4.0 trở đi được liên hệ với phát hiện
bad_page()doCONFIG_INIT_ON_ALLOC_DEFAULT_ON=y - Trên môi trường v6.4.16, tỷ lệ thành công đạt 99.4% và trong một số trường hợp giảm xuống 93.0%, với cỡ mẫu đều là n=1000
Cách sửa lỗi
- Bản sửa được đề xuất ban đầu có thể tạo ra breaking change ở giữa Netfilter stack
- Bản sửa của maintainer Netfilter siết chặt hơn việc giới hạn verdict đến từ đầu vào người dùng ngay ở tầng API
- Patch từ chối các tham số verdict kiểu DROP/QUEUE đến từ userland
- Theo mô tả CVE,
nft_verdict_init()cho phép giá trị dương trong drop error bên trong hook verdict, cònnf_hook_slow()có thể tạo ra double free khiNF_DROPvàNF_ACCEPTchồng lấp nhau - Có thể xem patch sửa tại [PATCH nf] netfilter: nf_tables: reject QUEUE/DROP verdict parameters.
Dirty Pagedirectory và truy cập bộ nhớ vật lý tùy ý
- Kỹ thuật trung tâm của PoC là Dirty Pagedirectory, một biến thể của kỹ thuật Dirty Pagetable có từ trước
- Ý tưởng cốt lõi là cấp phát chồng trang PTE và trang PMD lên cùng một trang vật lý
- Khi ghi một giá trị trông như giá trị PTE vào một vùng địa chỉ ảo, vùng địa chỉ ảo khác sẽ diễn giải nó như mục bảng trang và ánh xạ tới trang vật lý được chỉ định
- Cách này hoạt động như một kernel-space mirroring attack, cho phép đặt địa chỉ vật lý tùy ý và cờ quyền truy cập chỉ bằng đọc/ghi địa chỉ không gian người dùng
- Nó được dùng để vượt các cơ chế giảm thiểu như virtual KASLR, KPTI, SMAP, SMEP và
CONFIG_STATIC_USERMODEHELPER
Điều khiển page allocator
- Việc cấp phát trang trong kernel có sự tham gia của slab allocator, buddy allocator và PCP allocator
- skb head ở kích thước lớn dùng trang order 4 của buddy allocator, trong khi trang PTE/PMD là trang order 0 nên không khớp trực tiếp
- Để giải quyết điều này, có hai cách chuyển đổi trang được dùng
- PCP list draining: đưa trang order 4 vào buddy freelist rồi làm cạn PCP order 0 freelist để nó được nạp lại từ buddy allocator thành các trang order 0
- race condition: lợi dụng cạnh tranh ở lần free thứ hai để đưa trang order 4 vào freelist order 0
- PCP list draining là cách đơn giản hơn, ổn định hơn và nhanh hơn
- Cách dùng race condition từng được dùng trong exploit KernelCTF ban đầu, nhưng bị xem là lỗi thời vì phụ thuộc vào độ trễ serial TTY lớn trong môi trường như QEMU VM
Vượt KernelCTF mitigation
- Trong môi trường KernelCTF mitigation, cơ chế cần vượt đáng kể là kiểm tra hỏng freelist của
sk_buff skbuff_head_cache->offset == 0x70, nên con trỏ next của freelist chồng lênskb->len- Sau lần giải phóng skb đầu tiên,
skb->lenbị ghi đè một phần bởi freelist pointer, rồi trong lúc phân tích gói tin sau đó giá trị này lại thay đổi, có thể kích hoạt kiểm tra hỏng - Việc giải phóng thêm một skb bình thường lên trên skb đã hỏng để ghi đè freelist head là cách dùng để vượt phát hiện này
- Nhà phát triển KernelCTF cho rằng có thể giảm hiệu quả cách vượt này nếu kiểm tra cả next pointer của freelist head ngay tại thời điểm free
TLB flush và dò tìm physical KASLR
- Khi Dirty Pagedirectory thay đổi page table theo cách bất thường, CPU có thể còn giữ thông tin ánh xạ cũ trong TLB cache
- TLB được flush bằng cách từ không gian người dùng gọi
fork(), để tiến trình con thực hiệnmunmap()rồi ngủ - Cách này được xác nhận hoạt động 100% trên CPU AMD và QEMU VM
- Việc dò physical KASLR thu hẹp phạm vi nhờ tận dụng việc địa chỉ base vật lý của kernel được căn theo
CONFIG_PHYSICAL_STARThoặcCONFIG_PHYSICAL_ALIGN - Với giả định RAM vật lý 8GiB và căn hàng 16MiB, sẽ có 512 ứng viên; script get-sig được dùng để tạo kernel base signature nhằm nhận diện đúng địa chỉ
modprobe_path và lấy root shell
- Sau khi có khả năng đọc/ghi bộ nhớ vật lý tùy ý, PoC quét khoảng 80MiB sau kernel base để tìm
modprobe_path - Với cấu hình thông thường, nó tìm mẫu
"/sbin/modprobe"cùng phần đệm null, rồi xác minh biến thực bằng cách đối chiếu xem có phản ánh trong/proc/sys/kernel/modprobehay không - Nếu
CONFIG_STATIC_USERMODEHELPERđược bật, chuỗi mục tiêu sẽ là"/sbin/usermode-helper" - Để lấy root shell,
modprobe_pathhoặc chuỗi static usermode helper sẽ bị ghi đè thành đường dẫn memfd dạng/proc/<pid>/fd/<fd> - Script leo thang đặc quyền nối file descriptor của exploit vào stdin/stdout của shell để chạy được cả trên terminal cục bộ lẫn reverse shell
Thực thi không cần tệp và cấu trúc PoC
- PoC hỗ trợ fileless execution, không ghi tệp ra đĩa
- Nếu máy đích có Perl, có thể dùng
memfd_create()để nạp binary exploit vào bộ nhớ và thực thi qua/proc/$$/fd/<fd> - Phụ thuộc khi biên dịch là
libnftnl-devvàlibmnl-dev - Bản build tĩnh cho KernelCTF dùng
musl-gcc, nhằm tránh liên kết tĩnh glibc và vấn đề opcode QEMU AVX512 - Mã nguồn exploit được tách thành nhiều tệp, và vì hướng tới binary chạy độc lập nên khi gặp lỗi thường chọn crash/exit thay vì trả về error code
Độ ổn định và giới hạn
- Trạng thái pagetable của tiến trình exploit có thể trở nên không ổn định, nên sau khi thành công hoặc thất bại, tiến trình con không bị kết thúc mà được giữ ở trạng thái ngủ để giảm bất ổn cho kernel
- Hoạt động mạng có thể tạo nhiễu trong skb freelist và ảnh hưởng đến độ ổn định
- Trong môi trường SSH hoặc reverse shell, PoC giảm tối đa output ra stdout quanh thời điểm double-free để hạn chế cấp phát/giải phóng skb do lưu lượng mạng
- Một số thử nghiệm trên phần cứng cho thấy hệ thống bị crash sau vài giây; vì WiFi frame cũng dùng skb, có khả năng hoạt động WiFi đã tác động tới kết quả
- Khi tắt adapter WiFi trong BIOS, exploit hoạt động bình thường trong môi trường đó
Kết luận rút ra từ quá trình nghiên cứu
- PoC được tinh chỉnh để hướng tới độ tương thích rộng, độ ổn định cao và khả năng chạy kín đáo
- Sau 2 tháng phát triển, tác giả tiếp tục dành thêm 2 tháng để cải thiện tính ổn định và tương thích
- Bản thân exploit không phụ thuộc quá nhiều vào hành vi của slab allocator mà chủ yếu dựa vào các tính năng phổ biến như subsystem IPv4 và bộ nhớ ảo
- Dù bug ban đầu cần unprivileged user namespace và nftables, các kỹ thuật như Dirty Pagedirectory và PCP draining vẫn có thể được tái sử dụng trong những exploit thực tế khác
- Công trình này là một ví dụ đào sâu đồng thời vào networking subsystem và memory management subsystem của nhân Linux
1 bình luận
Ý kiến trên Hacker News
Hôm nay đã công bố khai thác proof-of-concept cho CVE-2024-1086, hoạt động trên Debian, Ubuntu, v.v.
Các phiên bản bị ảnh hưởng là Linux kernel v5.14~v6.6; việc hỗ trợ v6.4~v6.6 phụ thuộc vào thiết lập kernel
CONFIG_INIT_ON_ALLOC_DEFAULT_ONLỗi này đã được vá vào tháng 2/2024, nên cần cập nhật các máy Linux
https://github.com/Notselwyn/CVE-2024-1086/blob/main/src/mai...
modprobe_pathvà còn thêm cả brute-force pid do phương án không dùng fileVí dụ muốn hỏi vì sao không chọn cách vá kernel
.textbằng shellcode ngắn để trở thành root và thoát namespaceTuy nhiên có ghi là loại trừ các nhánh đã vá
v5.15.149>,v6.1.76>,v6.6.15>Trong bản vá có câu này: “This reverts commit e0abdadcc6e1. [...] Its not clear to me why this commit was made.”
Không biết có ai đã tìm hiểu lịch sử bối cảnh của commit này chưa
[1] https://lore.kernel.org/all/20240120215012.129529-1-fw@strle...
Tôi đã tìm trong danh sách netdev cũ nhưng không thấy gì; cũng có thể đó là bản vá gửi trực tiếp cho committer Pablo Neira Ayuso
Tác giả ban đầu là Patrick McHardy, người hiện đã trở thành nhân vật bị xa lánh; trừ khi Pablo còn nhớ hoặc tìm lại được trong email, có vẻ khó làm rõ use case chính xác chỉ bằng điều tra cơ bản
netfilter: nf_tables: accept QUEUE/DROP verdict parameters, cho phép user space chỉ định số hàng đợi hoặc mã errno cho các verdictQUEUEvàDROPBài này cũng rất ấn tượng từ góc độ viết về bảo mật
Khi viết blog bảo mật, tôi luôn băn khoăn “nên giả định người đọc có bao nhiêu kiến thức nền và kiến thức tiên quyết”; đạt được sự cân bằng vừa dễ tiếp cận vừa khả thi khi viết là việc khó
Việc xác định trước độc giả mục tiêu và cung cấp đủ giải thích nền là rất tuyệt, nên tôi đã bookmark để làm tài liệu hướng dẫn cho các nhà nghiên cứu mới vào nghề mà tôi gặp hằng năm
Khai thác này phụ thuộc vào quyền truy cập unprivileged user namespace:
sysctl kernel.unprivileged_userns_clone = 1Đây là giá trị mặc định của kernel Debian/Ubuntu và Arch Linux; nếu không cần những thứ như chạy lệnh Docker không cần sudo thì nên tắt đi
Chrome sandbox được dùng trong các ứng dụng Electron hoặc 1Password cũng liên quan, và binary hỗ trợ sandbox cũng có thể hoạt động như chương trình setuid
Sẽ không ngạc nhiên nếu Proton cũng bắt đầu dùng user namespace trong tương lai, nên trên Linux desktop thông thường có lẽ tốt hơn là không tắt
Trên server hoặc Linux được harden đặc biệt, tắt nó thường không phải là lựa chọn tệ
Vấn đề là để container “chạy được ngay”, phía mạng cần vô số hack, và điểm bán hàng cốt lõi của Docker cũng gần như là làm thay những hack nguy hiểm đó
Cuối cùng bất kỳ giải pháp container nào cũng chấp nhận các hack như vậy; dù chức năng namespace bình thường làm giảm quyền truy cập, kernel lại mở cửa vì các hack mạng
Tôi không hiểu vì sao unprivileged user namespace lại được bật mặc định
Dù chạy bên trong namespace “không đặc quyền”, vẫn thắc mắc lý do gì mà mặc định lại trao cho người dùng khả năng chạy những thứ như iptables hay mount
Ví dụ Chrome dùng namespace để triển khai sandbox cho tiến trình, nhưng để không phụ thuộc vào unprivileged namespace, nó cài một binary setuid-root
Vì bản thân binary setuid-root là rủi ro bảo mật, về lâu dài sẽ tốt hơn nếu Chrome không cần cài binary như vậy
Tuy nhiên để làm được điều đó, unprivileged user namespace phải được cung cấp rộng rãi, và các lỗi như thế này đang làm chậm tương lai đó
Ngoài ra, các chương trình cấu hình sandbox dựa trên namespace như Chrome thường dùng kèm seccomp để ngăn mã bên trong sandbox sử dụng các tính năng kernel đặc biệt như namespace
Trên desktop một người dùng, lợi ích của việc áp dụng mạnh ranh giới giữa user và root không lớn, và hầu hết những thứ thú vị đều có thể truy cập từ tài khoản người dùng
Trong khi đó sandbox như Chrome là thiết yếu cho bảo mật desktop, nên trên desktop một người dùng, tôi cho rằng bật unprivileged user namespace sẽ nâng cao bảo mật tổng thể
Hệ thống nhiều người dùng thì dĩ nhiên là câu chuyện khác
Nếu những lỗi như thế này không liên tục xuất hiện thì user namespace không đặc quyền hẳn đã là một tính năng bảo mật tuyệt vời
Ví dụ, sẽ rất tốt nếu việc chạy Flatpak không cần các binary setuid trên host để cách ly ứng dụng
Phần lớn giá trị mặc định không an toàn, và cần kiến thức kỹ thuật cùng công việc gia cố
Commit đã đưa vấn đề vào: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...
Trong
switch/caselồng nhau córeturnở đường đi mặc định, nhưng cấu trúc sau đó lại kỳ vọng fall-through, trông khá lạhttps://lwn.net/Articles/882397/
https://bugs.launchpad.net/bugs/cve/2024-1086
Đây là lỗ hổng use-after-free trong thành phần
netfilter: nf_tablescủa nhân Linux, cho phép leo thang đặc quyền cục bộHàm
nft_verdict_init()cho phép giá trị dương làm drop error trong phán quyết hook, dẫn đến việcnf_hook_slow()xử lýNF_DROPvới một drop error trông giốngNF_ACCEPT, có thể tạo ra lỗ hổng giải phóng hai lầnNên nâng cấp lên sau
f342de4e2f33e0e39165d8639387aa6c19dff660Tôi thắc mắc làm sao những exploit như thế này vẫn khả thi dù đã có các cơ chế giảm thiểu hiện đại như ASLR
Ở một môn đại học, tôi từng được giao nhiều binary chạy trên một phiên bản Ubuntu cụ thể, rồi phải tìm và khai thác các lỗi như use-after-free hoặc tràn bộ đệm; việc đó thật sự rất khó
Tìm ra lỗi đã khó, viết shellcode chính xác để làm được việc hữu ích từ lỗi đó còn khó hơn
Ở các mức khó hơn, các cơ chế giảm thiểu như ASLR và stack canary cũng được bật, và ngay cả trong môi trường sinh viên được kiểm soát, nó gần như bất khả thi
Rốt cuộc phải 1) phát hiện một lỗi có thể khai thác và 2) tìm đúng payload nhị phân làm được việc hữu ích mà không chỉ khiến chương trình crash; trong thế giới thực hẳn còn khó hơn, nên tôi tự hỏi làm sao điều đó có thể xảy ra
[0] https://web.stanford.edu/class/archive/cs/cs107/cs107.1194/a...
Vừa học các môn khác song song, thời gian có thể dành cho một lỗi có lẽ chỉ từ vài giờ đến vài ngày
Zerodium trả 50.000 USD cho leo thang đặc quyền cục bộ Linux thông thường, và với chi phí của một nhà phát triển exploit lành nghề, số đó tương đương khoảng 200 giờ công
Tức là các chuyên gia dành nhiều thời gian hơn sinh viên mới từ 10 đến 100 lần
Có thể hình dung sự khác biệt giữa người lần đầu vào xưởng mộc và thợ mộc, người mới học gốm và nghệ nhân, họa sĩ năm nhất và nghệ sĩ chuyên nghiệp
Lại còn cộng thêm lượng thời gian nhiều hơn 10–100 lần
[1] https://zerodium.com/program.html
Có những kỹ thuật “cổ điển” để vượt qua phần lớn cơ chế bảo vệ mới, và nếu chưa có thì đôi khi sẽ xuất hiện các cuộc tấn công hoặc cách bypass mới
Ví dụ có thể xem how2heap về bypass bảo vệ heap[0], cũng từng có ví dụ exploit bypass KASLR[1], và exploit lần này có vẻ dùng kỹ thuật dirty pagetable[2]
Đây là một trò mèo vờn chuột liên tục: cơ chế giảm thiểu được thêm vào, rồi các nhà nghiên cứu tìm cách vượt qua
[0] https://github.com/shellphish/how2heap
[1] https://www.willsroot.io/2022/12/entrybleed.html
[2] https://pwning.tech/nftables/#452-the-technique
Để tìm ra những thứ như vậy, chỉ cần một người thuộc nhóm cuối là đủ
Ngày nay việc này thật sự khó, và ngay cả khi tắt các cơ chế giảm thiểu, tìm lỗ hổng rồi viết exploit vẫn không hề dễ
Tuy nhiên, nhiều người tìm các lỗ hổng kiểu này làm việc theo nhóm, song song hóa fuzzing, kết hợp kiến thức với các exploit khác hoặc xâu chuỗi chúng
Chuyên môn và tài năng của một số nhà nghiên cứu đáng kinh ngạc, và trong lĩnh vực này, kinh nghiệm nhiều năm đến hàng chục năm có giá trị cực lớn
Nhưng nếu nghĩ rằng các lỗi lớn hiện nay được tìm ra bởi những nhóm có tài chính dồi dào hoặc các tác nhân cấp nhà nước, thì việc họ có thể đổ rất nhiều nhân lực và tài nguyên để vượt qua các cơ chế giảm thiểu hiện có trở nên dễ hiểu hơn
Theo Ubuntu, tất cả bản phát hành LTS đều bị ảnh hưởng và đã được sửa trong các kernel hiện đã vá: https://ubuntu.com/security/CVE-2024-1086
Focal được sửa ở
5.4.0-174.193, Jammy ở5.15.0-101.111, Mantic ở6.5.0-26.26Nếu dùng hỗ trợ mở rộng thì Xenial và Bionic cũng được bao gồm
Tôi đã thử chạy trên một hệ thống Debian dễ bị tấn công; tuy leo thang đặc quyền không thành công, nhưng ở lần chạy thứ hai toàn bộ hệ thống đã bị treo
Lần chạy đầu tiên chỉ đơn giản là thất bại, nên dù vậy vẫn rất đáng bỏ thời gian để vá lỗi
Có thể kiểm tra cấu hình kernel hiện tại trong các tệp như
/boot/confighoặc/proc/config.gz