The way build artifacts are stored by the GitHub Actions platform could enable attackers to inject malicious code into software projects with CI/CD (continuous integration and continuous delivery) workflows that don’t perform sufficient filtering when downloading artifacts.
Cyber security researchers have identified several popular artifacts download scripts used by thousands of repositories that are vulnerable to this issue.
“We have discovered that when transferring artifacts between different workflows, there is a major risk for artifact poisoning — a technique in which attackers replace the content of a legitimate artifact with a modified malicious one and thereby initiate a supply chain attack,” researchers from supply chain security firm Legit Security said in an analysis of the issue.
To attack a vulnerable project’s CI/CD pipeline that downloads and uses artifacts generated by other workflows, attackers only need to fork the repositories containing those workflows, modify them in their local copies so they produce rogue artifacts and then make pull requests back to the original repositories without those requests having to be accepted.
A logic flaw in artifact storage APIs
GitHub Actions is a CI/CD platform for automating the building and testing of software code. The service is free for public repositories and includes free minutes of worker run time and storage space for private repositories. It’s widely adopted by projects that use GitHub to host and manage their source code repositories.
GitHub Actions workflows are automated processes defined in .yml files using YAML syntax that get executed when certain triggers or events occur, such as when new code gets committed to the repository. Build artifacts are compiled binaries, logs and other files that result from the execution of a workflow and its individual jobs.
These artifacts are saved inside storage buckets with each workflow run being assigned a particular bucket where it can upload files and later download them from.
The reference “action” (script) for downloading artifacts that’s provided by GitHub doesn’t support cross-workflow artifact downloads, but reusing artifacts generated by different workflows as input for follow-up build steps are common use cases for software projects.
That’s why developers have created their own custom scripts that rely on the GitHub Actions API to download artifacts using more complex filtering, such as artifacts created by a specific workflow file, a specific user, a specific branch and so on.
The problem that Legit Security found is that the API doesn’t differentiate between artifacts uploaded by forked repositories and base repositories, so if a download script filters artifacts generated by a particular workflow file from a particular repository, the API will serve the latest version of the artifact generated by that file, but this could be a malicious version generated automatically via a pull request action from a forked version of the repository.
“To put it simply: in a vulnerable workflow, any GitHub user can create a fork that builds an artifact,” the researchers said. “Then inject this artifact into the original repository build process and modify its output. This is another form of a software supply chain attack, where the build output is modified by an attacker.
The researchers found four custom actions developed by the community for downloading artifacts that were all vulnerable. One of them was listed as a dependency for over 12,000 repositories.
The Rust example
One of the repositories that used such a custom script in one of its workflows was the official repository for the Rust programming language.
The vulnerable workflow, called ci.yml was responsible for building and testing the repository’s code and used the custom action to download an artifact called libgccjit.so — a Linux library file — that was generated by a workflow in a third-party repository.
All attackers had to do was fork the third-party repository, modify the workflow from that repository to generate a malicious version of the library and issue a pull request to the original repository to generate the artifact. If Rust’s workflow would have then pulled in the poisoned version of the library it would have provided the attackers with the ability to execute malicious code within the Rust repository with the workflow’s privileges.
“Upon exploitation, the attacker could modify the repository branches, pull requests, issues, releases, and all of the entities that are available for the workflow token permissions,” the researchers said.
Users need to enforce stricter filtering for artifact downloads
GitHub responded to Legit’s report by adding more filtering capabilities to the API which developers can use to better identify artifacts created by a specific run instance of the workflow (workflow run id). However, this change cannot be forced onto existing implementations without breaking workflows, so it’s up to users to update their workflows with stricter filtering in order to be protected.
Another mitigation is to filter the downloaded artifacts by the hash value of the commits that generated them or by excluding artifacts created by pull-request entirely using the exclude_pull_requests option. Legit Security also contacted the authors of the vulnerable custom artifact download scripts they found.
“In supply chain security, the focus has been on preventing people from contributing malicious code, so every time you do a change in a repository, create a pull request or do a change request, GitHub has a lot of built-in verification controls,” Liav Caspi, CTO of Legit Security tells CSO.
“Somebody has to approve your code, somebody has to merge it, so there's a person involved. What we’ve been trying to find are techniques that exploit a logic problem that any person could influence without review and I think this is one of them. If someone would have known about it, they could have injected the artifact without any approval.”
Typically, CI pipelines have workflows that run automatically on pull requests to test the code before it’s manually reviewed and if the pull request contains any artifact that needs to be built, the workflow will build it, Caspi said.
A sophisticated attacker could create the pull request to get the artifact built and then delete the request by closing the submission and chances are with all the activity noise that exists in source code repositories today, it would go unnoticed, he said.