UrsaKit 稽核 Git hooks 憑證竊取風險的指南封面

hook 是 Git 在工作流程固定時點執行的程式,而 hooks 就住在 .git 裡。這就是為什麼正常 clone 安全,而共享文件夾不安全:把一個含 .git 文件夾的工作目錄交給別人,你就把「對方一 checkout 分支就執行程式碼」所需的一切都交出去了。

這個攻擊已經在真實世界出現。一位開發者收到看似合理的項目封存檔,被要求切到「NDA 分支」,結果發現一個真實的 post-checkout hook:它從命令與控制端點抓取對應作業系統的執行檔、標記為可執行、執行、再刪除自己。稽核 hooks 與設定大約只要三十秒。

01post-checkout hook 如何變成程式碼執行

post-checkout 會在 checkout 完成後執行。Git 以三個參數呼叫它——先前的 HEAD、新的 HEAD,以及指出是分支或檔案 checkout 的旗標——而 hook 的結束碼不會阻擋操作,所以它會無聲執行。

因為它在轉換時觸發,攻擊者不需要你主動執行指令碼。叫你 git checkout nda 就夠了。接著 hook 擁有你的完整用戶權限:它能讀 ~/.ssh、~/.aws、macOS 鑰匙圈、環境變數,以及機器上任何憑證助手。

在不受信任的倉庫裡,checkout 不是唯讀動作

若工作目錄含有一個不是你建立的 .git 文件夾,請把執行任何 git 指令都當成執行作者的程式碼。這也包含 git status,因為其他設定鍵同樣會執行程式。

02既然不會被 clone,hooks 為何還是出現了

一般的 clone 路徑確實安全:git clone <url> 不會複製遠端 hooks,而 clone 後 Git 可能執行的 post-checkout 來自你的樣板目錄,不是那個倉庫。攻擊用三種方式繞過:

  • 直接交給你工作目錄。 含 .git 的封存檔、Dropbox 文件夾或複製的目錄。這就是回報事件中的情況。
  • 透過 core.hooksPath 指向被追蹤的 hooks 目錄。 用這種方式管理 hooks 的框架,會讓 hook 指令碼成為一般已提交檔案,安裝相依套件就會默默啟用它們。
  • filter driver 搭配 .gitattributes。 倉庫提交引用某個 filter 的 .gitattributes,而該 filter 的指令住在 .git/config,checkout 時就會執行它。

只有第一種不需要安裝任何相依套件,也最容易上當,因為那個文件夾看起來就是普通項目。

03執行 git 前先稽核不受信任的倉庫

從 hooks 目錄與本機設定開始。以下每個指令都已在 Git 2.50.1 上驗證。

# 這個倉庫把 hooks 放在哪裡?
git rev-parse --git-path hooks/post-checkout
# .git/hooks/post-checkout

# 列出所有不是 .sample 的檔案。
ls -la "$(git rev-parse --git-path hooks)"

hooks 目錄裡只有 *.sample 是預設狀態。真實的非 sample 檔案就是會在觸發點執行的程式;執行 Git 前請完整讀過它。

# 任何一項都代表有 hook、filter 或 helper 會執行程式碼。
git config --local --list
git config --get core.hooksPath      # 結束碼 1 代表未設定
git config --get core.fsmonitor
git config --get core.sshCommand

請注意 filter.*、core.fsmonitor、core.sshCommand、core.pager、core.editor,以及任何以 ! 開頭的別名。每一項都可能讓一般操作產生執行指令的副作用。

# 檢查 hook 的典型特徵:下載、chmod、執行、刪除。
grep -nE 'curl|wget|chmod|base64 -d|/tmp/|rm -f|exec' .git/hooks/post-checkout

讀 hook 是安全的;危險的是讓 Git 指令觸發它。若 hook 存在而你只需要看被追蹤的檔案,請用編輯器或 tar 打開,而不是用 Git。

04在可疑的 checkout 上安全執行 Git

當你必須在不受信任的倉庫裡執行 Git,請在該次指令停用 hooks,並讓工作階段保持惰性:

# 已驗證:空 hooks 路徑只會為這次指令停用所有 hooks。
git -c core.hooksPath=$(mktemp -d) status
git -c core.hooksPath=$(mktemp -d) checkout nda

# 絕不讓不受信任的倉庫提示輸入憑證。
GIT_TERMINAL_PROMPT=0 git -c core.hooksPath=$(mktemp -d) log --oneline -5

若要做的不只是看一眼,請把被追蹤的檔案複製到新目錄,重新初始化一個倉庫,而不是使用對方提供的 .git。整個攻擊都依賴那個文件夾,丟掉它就移除了攻擊途徑。

從 URL clone,不要從文件夾複製

若同事要分享項目,請向他要 Git URL 或裸 bundle,而不是含 .git 的工作目錄。git clone 不會執行遠端 hooks;複製文件夾則會。

05如果 hook 已經執行過

請假設機器上的憑證已外洩。先斷網,再盤點 checkout 期間可觸及的內容:SSH 金鑰與其通關密語、雲端 CLI 工作階段、瀏覽器工作階段、macOS 鑰匙圈,以及 shell 環境或 .env 檔裡的任何權杖。請從另一台機器輪替這些憑證,並檢查 Git 代管與雲端稽核紀錄中是否有陌生 IP 或 user agent 的活動。刪掉 hook 並不能撤銷那次執行。

06常見錯誤與修正

錯誤為何失效修正
在別人傳來的文件夾直接跑 git statushooks 與設定可能執行程式碼先檢查 .git,或使用 core.hooksPath
因為沒有 post-checkout 就信任倉庫其他 hooks 與設定鍵也會觸發讀完整個 hooks 目錄與本機設定
假設 clone 永遠安全只有 hooks 不在樹裡時才安全從 URL clone,絕不採用別人給的 .git
只看 core.hooksPathfilter.* 與 core.fsmonitor 也會執行檢查每個能執行程式的設定鍵
事後才刪掉 hook載荷已經跑過輪替憑證並檢視稽核紀錄

真正重要的習慣是:不是你建立的 .git 文件夾就是可執行內容。在你打下 git 之前先看進去,並且優先從 URL 重新 clone,這樣一開始就沒有可疑的東西可看。

常見問題

Git hooks 會從遠端被 clone 下來嗎?

不會。一般的 `git clone` 只複製被追蹤的檔案,不會複製遠端的 `.git/hooks`。即使從 URL clone 一個惡意倉庫,也不會執行攻擊者提交的 hooks,因為 hooks 不屬於被追蹤的樹。

那 post-checkout hook 是怎麼進到我機器的?

有人交給你一個已經含 `.git` 文件夾的工作目錄——放在壓縮檔、共享文件夾或封存檔裡。那個 `.git` 帶著它的 hooks,所以在裡面執行 `git checkout` 就會觸發攻擊者指令碼。

post-checkout hook 會在什麼時候執行?

Git 會在 `git checkout` 之後、`git clone` 完成初次 checkout 之後,以及 `git switch` 之後執行 `post-checkout`。它會收到新舊 HEAD 與一個指出是分支或檔案 checkout 的旗標。

我能在不受信任的倉庫裡執行 git 而不觸發 hooks 嗎?

可以。把 `core.hooksPath` 指向空目錄即可:`git -c core.hooksPath=$(mktemp -d) status`。經本機驗證,這會在不改動倉庫設定的情況下,於該次指令停用所有 hooks。

還有哪些 Git 設定鍵會執行程式碼?

`core.fsmonitor`、`core.sshCommand`、`core.pager`、`core.editor`、搭配已提交 `.gitattributes` 的 `filter.<driver>.*`,以及 `!command` 別名都能執行程式。對於不是自己 clone 的倉庫,務必檢查 `.git/config`。

返回文章列表

相關文章

相關工具