iT邦幫忙

2025 iThome 鐵人賽

DAY 7
0
Security

規組駭了了::你無定落勾去的鑠奅 Web Hacking 步數系列 第 7

0x07 / CTF・Race Condition・Golang 的 = 佮 := 有啥爭差恁敢知

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20250918/201201872G9nVwoTHW.png

Go 語言上出名的特色之一就是伊的 concurrent 設計,予咱程式速度緊甲敢若飛,毋過若無細膩嘛是會犁田的。

咱這擺欲來簡單介紹舊年(2024)佇 38C3 CTF 出的一个挑戰:Fajny Jagazyn Wartości Kluczy

一个變數公家用

這隻程式有一个檢查檔案路徑的 function,號做 checkPath,伊會確認咱欲讀的檔案名敢有 "flag" 抑是 ".",若有就會共你擋咧。程式碼看起來是按呢:

// ...省略(síng lio̍k)...
func main() {
    var err error // err 變數佇外口 main 這層就宣告矣

    // ...省略...

    http.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) {
        name := r.URL.Query().Get("name")
             //  vvvvvvvvv 袂當有 flag OR .
        if err = checkPath(name); err != nil { // ⚠️ 這逝有問題!
            http.Error(w, "checkPath :(", http.StatusInternalServerError)
            return
        }
        // ...讀檔案的khóo...
    })
}

各位咱來斟酌看 if err = checkPath(name); err != nil 這逝,問題著覕佇咧遮。

佇 Go 語言內底,:= 代表「宣告一个新變數兼予伊一个值」,毋過 = 干焦是「共值指派予一个已經佇咧的變數」。

佇咱這个例,err 這个變數早就佇 main function 頭一逝遐就宣告好勢矣。所以,每一个連入來的 request 執行著 err = checkPath(name) 的時,伊袂去創一个歸伊家己的新 err 變數,顛倒是去用彼个佇外口宣告的、逐家攏會當用err

Race Condition 是按怎發生?

Go 語言的 goroutine (會當當做是簡單版的 thread) 會做伙走。若有一大堆 request 做伙入來,狀況就會變做按呢:

  1. Request A (歹):伊欲偷讀 /home/ctf/flag.txt。伊走 checkPath 的時因為檔案名有 "flag" 佇咧,所以 checkPath 擲一个 error 轉來。這个 error 就寫入去彼个公家的 err 變數。這馬 err 猶毋是 nil
  2. Go Scheduler 插隊:Request A 欲進行後一步去檢查 if err != nil 的時陣,Go 的 scheduler 凡勢想欲予別个 request 做一下仔代誌,就先予 Request A 較停咧。
  3. Request B (正常):伊只是欲讀一个叫做 "my_file" 的普通檔案。伊嘛執行 checkPath,因為檔案名無問題,所以 checkPath 回傳 nil。這个 nil共彼个公共的 err 變數洗掉,變做 nil
  4. 著 Request A 的時:Scheduler 閣叫 Request A 繼續走。伊開始檢查 if err != nil。問題來矣,雖然講頭先是伊造成 error,毋過彼个公家的 err 已經予 Request B 摒清氣做 nil
  5. 檢查通過!if err != nil 這个條件變做 false,彼个檢查就無效去矣。程式就會繼續行落去,去讀彼个 /home/ctf/flag.txt,了後駭客就順利提著 flag 矣。

這就是一款標準的 Race Condition。兩个以上的 thread 咧搶一个共用的資源 (佇這例是 err 變數),執行結果的生啥款,就看啥人跤手較緊。

按怎避免這款代誌?

這个問題的解法,其實簡單甲會予人想欲捶心肝。干焦愛共彼个 = 換做 := 就好矣。

if err := checkPath(name); err != nil {
    // ...
}

= 改做 :=,就會當予每一个 request 宣告一个全新的、干焦屬於家己彼條的 err 變數。若照按呢,逐个 request 的 err 攏是獨立的,袂去牽拖著別个 request,自然就無 Race Condition 的問題佇咧。

是講 Go 嘛有內佮一个誠好用的工具,就是 race detector。你干焦愛佇走你的程式的時,加一个 -race 的參數:

go run -race my_program.go

伊就會佇執行的時陣,自動幫你檢查你的khóo敢有覕咧內底的 race condition,是一个咱咧開發的好跤手。

Unintended Solution:有別人攢便便的通抾啦

這題 CTF 閣有一个較趣味的故事。有一隊揣這題的漏縫揣甲欲起痟,想講這題夆標做是「easy」,哪會遮爾僫拍?結果怹就想著一个誠巧的步數。

因為逐个參賽者攏是用仝一台機器,怹就寫一支 script 去捷捷看系統的 /proc directory,特別是 /proc/sys/kernel/ns_last_pid 這个檔案來檢查敢有新的 process 產生。若有別隊的人,甚至是主辦,成功執行 exploit 來讀 flag,一定會生一个有 flag 的 file descriptor 的 process。怹煞趁這个機會,趕緊去讀彼个新 process 的 /proc/[pid]/fd/,直接對內底共 flag 的內容搝出來。

結果,這招竟然真正有效,怹對主辦單位的檢查 script 遐,共 flag 抾轉來。這嘛是趣味趣味啦,我嘛是足佮意這款 CTF 內底的 unintended solution 的!

Write-up: https://msanft.foo/blog/hxp-38c3-web-fajny-jagazyn/

結論

Go 的 concurrency 雖然誠強,毋過有一好無兩好。變數的 scope 若無去注意,一个符號小可毋著,凡勢就造成咱系統蓋大漏縫。希望今仔日的分享有予逐家較知影 goconcurrent 會出現的風險,咱寫程式逐个鋩角攏愛細膩,才袂予人駭去!

Official write-up: https://hxp.io/blog/114/hxp-38C3-CTF-Fajny-Jagazyn-Wartoci-Kluczy/


上一篇
0x06 / CTF.SQL Injection .用 PHP PDO 嘛是會出代誌?
下一篇
0x08 / CTF・PHP・LFI + PHAR 閣有新招
系列文
規組駭了了::你無定落勾去的鑠奅 Web Hacking 步數8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言