GhostAction 新增 Git 歷史掃描,作者實測找回已刪除的 30 組 AWS 憑證及 30 個 GitHub token
GhostAction now scans your git history for secrets. I deleted 30 keys and recovered all 30.
作者以自建測試檢查 Git 歷史掃描效果,從已刪除的提交中找回全部 30 組 AWS 憑證及 30 個 GitHub token。使用 git log -p --all 掃描 52 次提交,輸出需時約 9 ms;以 AWS key ID 前後兩行配對 secret access key,則只成功配對 30 組中的 15 組。文章指出,刪除提交不會清除歷史中的憑證,應先輪換密鑰再刪除。
作者以自建憑證測試呈現 Git 歷史掃描的找回率與配對邊界,並解釋為何只刪除提交不足以處理已外洩憑證。
新一波 GhostAction——這場供應鏈攻擊行動會利用遭竊的維護者憑證,將虛假的「安全審核」GitHub Actions 工作流程植入儲存庫——新增了一項功能,足以改變你對自己曾經提交過的每項機密資料的看法。
GitGuardian 對 10 月這波攻擊的報告,具體描述了載荷的演變。新載荷第一版加入了 Git 歷史記錄中的機密資料掃描:使用正規表示式掃描歷史記錄,尋找 AWS 金鑰 ID、GitHub 和 GitLab 權杖、AI 供應商金鑰及其他雲端憑證。第二版加入了上下文擷取功能——擷取每個 AWS 金鑰 ID 前後約兩行,讓金鑰 ID 可以與相應的秘密存取金鑰配對。第三版則把這種上下文擷取功能也加入歷史記錄掃描。
根據 GitGuardian 經 The Hacker News 報道的資料(10 月 15 日):在 10 月 8–9 日的攻擊高峯期間,約 1,900 個 GitHub 帳戶遭入侵;攻擊者嘗試擷取 3,174 項機密資料;載荷先部署至 160 個先前已觀察到的受害者,20 分鐘後再部署至約 28,000 個新儲存庫。
我想了解這種歷史記錄掃描的兩件事。對於大家實際上都會採用的補救方法——「我提交了金鑰,然後把它刪掉」——它能找得多完整?而兩行配對技巧實際上有多有效?
所以我建立了一個儲存庫,當中的實際情況我一清二楚,因為是我親自植入的。
設定
我建立了一個儲存庫,植入 30 組 AWS 憑證和 30 個 GitHub 權杖,全部放在明文設定檔中,並控制金鑰 ID 與對應秘密金鑰之間的行距:相隔 0、1、2、3、5 和 10 行,每種距離各放五組。接着提交 20 次平淡無奇的文件更新,再加入一個放有相似字串的檔案(包括 AKIAIOSFODNN7EXAMPLE,AWS 官方文件中的示例金鑰)。然後是人人信以為真的那次提交:
$ git log --oneline | head -3
... remove leaked keys
... add examples
... docs 19
commits total: 52
config files remaining at HEAD: none
所有存有機密資料的檔案都已刪除。工作目錄乾淨。如果你像大多數人一樣掃描這個儲存庫——只檢查目前的檔案——結果如下:
[HEAD-only scan]
real AWS IDs recovered: 0 / 30
GitHub tokens recovered: 0 / 30
secret keys recovered: 0 / 30
raw ID hits: 2 — both lookalikes in examples.txt, including the AWS docs example
乾淨,但也完全沒有意義。
歷史記錄掃描
載荷所需的整個歷史記錄掃描,只需一條任何 CI 執行器都已能執行的命令,再加一次正規表示式掃描:
git log -p --all
PAT_ID = re.compile(r"AKIA[0-9A-Z]{16}")
PAT_TOK = re.compile(r"ghp_[A-Za-z0-9]{36}")
PAT_SEC = re.compile(r"aws_secret_access_key\s*=\s*([A-Za-z0-9+/]{40})")
掃描我這個「已補救」的儲存庫:
[history scan: git log -p --all]
dump size: 39,406 characters, 1,642 lines, 52 commits
time to dump: ~9 ms
real AWS IDs recovered: 30 / 30
GitHub tokens recovered: 30 / 30
secret keys recovered: 30 / 30
raw regex hits: 62 IDs, 60 tokens, 60 secrets
30 個中找出 30 個。全部找出。原始命中數約為植入數量的兩倍,因為 git log -p 令每項機密資料在這個儲存庫中出現兩次——一次在加入它的提交中,一次在「移除」它的提交差異中。刪除提交並沒有清除歷史記錄中的機密資料;它反而是第二份副本。
成本才是重點。在這部機器上,輸出包含 52 次提交的歷史記錄約需 9 毫秒,而對其執行正規表示式掃描則不足一毫秒。掃描歷史記錄而非 HEAD,並不會令攻擊成本更高;這只是同一種攻擊加上一個較長的命令列選項。將幾毫秒乘以 28,000 個儲存庫,歷史記錄掃描功能對這場攻擊行動而言幾乎毫無成本——想必這就是它存在的原因。
不過,配對問題確實存在
單靠 AWS 金鑰 ID,無法完成任何驗證。攻擊者需要與它配對的秘密存取金鑰,而這正是第二版載荷採用兩行上下文範圍的目的。對我植入的金鑰組而言,這個範圍有明確限制:
planted distance paired within ±2 lines
(ID line → secret)
0 lines (same line) 5 / 5
1 line 5 / 5
2 lines 5 / 5
3 lines 0 / 5
5 lines 0 / 5
10 lines 0 / 5
total paired: 15 / 30
任何秘密值與其 ID 相隔不超過兩行的配對,都成功配對了。距離較遠的配對則沒有成功——即使秘密值就在同一份資料傾印中,按標籤即可找回,只待更聰明的配對器識別。我使用的距離是模擬數據,而真實的 .env 檔案通常會把兩個值放在一起,這對攻擊者更有利。不過,15/30 的分佈比任何報道都更能説明載荷本身的變更記錄:第二版之所以存在,是因為沒有秘密值的 ID 只是清單,並不代表可以存取;第三版之所以存在,是因為配對功能首先必須在歷史記錄中運作,而有價值的資料就在那裏。
這也説明瞭由此得出的防禦方法。格式不能保護你——在我的測試中,一個與其 ID 相隔 10 行的秘密值,就能避過兩行的搜尋範圍;但下一版載荷或人手檢查都能找到它。「我刪除了密鑰」只有在「我刪除了密鑰 並輪替了它」時才有意義。刪除會改變 HEAD 顯示的內容;輪替則會改變歷史記錄的價值。只有其中一項才算補救措施。
再如實交代一點 HEAD 掃描的結果:它標記的兩個 ID 都是假的——一個是我放入的、符合模式的字串,另一個則是 AWS 自行在文件中發布的範例密鑰。正規表示式掃描器無法分辨文件中的範例與有效密鑰。攻擊者也知道這一點。在 28,000 個儲存庫的規模下,他們不需要高精確率;他們需要高召回率,而要分辨真假,是他們之後可以用你的憑證慢慢處理的問題。
如果你要因應 GhostAction 這波攻擊採取一項行動,就做這件事:在別人的工作流程執行掃描前,先用同一個 git log -p 掃描自己的儲存庫。如果找到任何內容,不要只刪除就算。先輪替,再刪除——順序很重要,因為本文中的掃描順序就是載荷使用的順序,而它會先讀取歷史記錄。
資料來源:GitGuardian 對 GhostAction 的研究,據 The Hacker News 報道;報道標題為「GhostAction 入侵約 1,900 個 GitHub 帳戶,針對超過 30,000 個儲存庫」(2026 年 10 月 15 日)——活動數字、載荷版本歷史及擷取上下文的細節均來自該研究。以上所述的儲存庫、植入的憑證及所有掃描結果均由我為本文生成;每個植入的憑證都是隨機字串,從未是真正的密鑰。
來源:DEV Community · dev.to