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
。
Go 語言的 goroutine (會當當做是簡單版的 thread) 會做伙走。若有一大堆 request 做伙入來,狀況就會變做按呢:
/home/ctf/flag.txt
。伊走 checkPath
的時因為檔案名有 "flag" 佇咧,所以 checkPath
擲一个 error 轉來。這个 error 就寫入去彼个公家的 err
變數。這馬 err
猶毋是 nil
。if err != nil
的時陣,Go 的 scheduler 凡勢想欲予別个 request 做一下仔代誌,就先予 Request A 較停咧。checkPath
,因為檔案名無問題,所以 checkPath
回傳 nil
。這个 nil
就共彼个公共的 err
變數洗掉,變做 nil
。if err != nil
。問題來矣,雖然講頭先是伊造成 error,毋過彼个公家的 err
已經予 Request B 摒清氣做 nil
矣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,是一个咱咧開發的好跤手。
這題 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/