Overview

Recently, we identified several critical Pwn Request vulnerabilities within GitHub Actions used by the Rspack repository. These vulnerabilities could allow an external attacker to submit a malicious pull request, without the requirement of being a prior contributor to the repository, and compromise the following secrets:

  • NPM Deployment Token Compromise: Exploitation of the Pwn Request vulnerability allowed us to compromise the NPM deployment key used to deploy Rspack. This NPM token was used to push new Rspack packages (80,000 weekly downloads).
  • GitHub Personal Access Token (PAT) Compromised: We leveraged persistent access to the self-hosted runner to compromise a privileged GitHub PAT with administrative privileges on the Rspack GitHub repository.

Background: Rspack is a Rust-based JavaScript bundler that implements functionality similar to WebPack for JavaScript bundling and packaging. Rspack aims to differentiate itself by acting as a highly performant version of WebPack and even claims that it is up to five to ten times faster than WebPack. This is primarily because Rspack is written in Rust and designed from the ground up with performance in mind.

Had an attacker exploited these vulnerabilities, they could have leveraged this access to perform a supply chain attack against downstream users of Rspack. This is quite serious as Rspack is often leveraged within CI/CD pipelines and on developer machines. In both cases, these systems are often quite privileged, and we’ve frequently seen that compromise of CI/CD infrastructure allows an attacker to gain privileged access to production environments. After assessing the impact of the vulnerability, we observed that the Rspack NPM package had over eighty thousand weekly downloads.

We decided to release this blog post on the issues we identified in Rspack to raise awareness of Pwn Request vulnerabilities, especially when a repository also uses self-hosted runners. Despite its high impact and prevalence, this particular bug class remains underappreciated within the security and broader engineering and development communities.

As part of our vulnerability disclosure process, we promptly reported these vulnerabilities to ByteDance after the initial discovery and confirmation of the issues. In this case, we reported the vulnerability to the core RSPack team who remediated the problems within an hour of disclosure. ByteDance also performed an incident response, which involved rotating all the secrets associated with impacted repositories and confirming that no prior exploitation of the issues had occurred before the research Praetorian performed.

What is a Pwn Request vulnerability?

Pwn Request vulnerabilities arise from scenarios where an attacker submits malicious input to a target repository and can trigger the execution of attacker-controlled code within a privileged context of the Github action. In this case, we identified two GitHub actions that ran on issue comment events and would checkout and run code from an attacker-controlled branch if a specific keyword was included in a pull request comment.

If you are interested in learning more about these vulnerabilities and GitHub action security, Adnan Khan has previously published an excellent blog post on the Praetorian blog titled Long Live the Pwn Request: Hacking Microsoft GitHub Repositories and More. Jaroslav Lobacevski also published an excellent three-part series on the Github securitylab blog, which deep dives into securing GitHub actions and provides some useful additional context on Pwn Request vulnerabilities.

Identifying the Vulnerable GitHub Actions

The two vulnerable workflows we identified were the “Release Canary” and “Diff Assets” workflows. Both of these workflows functioned in a similar manner by running any time a user commented a keyword of  “!diff” or “!canary” on an open pull request. In both cases, these workflows would checkout and run code from an attacker-controlled branch.

The diff workflow used a privileged PAT and ran on a self-hosted runner, while the canary workflow was passed a privileged NPM deployment secret that could be used to deploy new versions of the Rspack NPM package. First, we will walk through the “Release Canary” workflow exploitation.

Analyzing the Release Canary Workflow

When examining the “Release Canary” workflow, we noticed that the workflow ran on an issue comment and then performed a check that the comment ran on a pull request and contained the keyword “!canary” in the comment body (see Figure 1).

We then observed that the workflow would check out the branch from the submitted pull request and execute a script named “x” with an NPM token secret passed as an environment variable. This execution flow would allow an attacker to modify the “x” script within their malicious branch and exfiltrate the NPM deployment secret and GitHub secret passed to the workflow (see Figure 2).

Figure 1: We observed that the “Release Canary” workflow was configured to run on issue comment and would execute if the issue comment contained the keyword “!canary”.

Figure 1: We observed that the “Release Canary” workflow was configured to run on issue comment and would execute if the issue comment contained the keyword “!canary”.

Figure 2: We observed that the workflow, when executed, would first checkout the code from the attacker’s pull request and run a script named “x” from the attacker branch with the deployment NPM token and a privileged GitHub secret with write access to the repository.

Figure 2: We observed that the workflow, when executed, would first checkout the code from the attacker’s pull request and run a script named “x” from the attacker branch with the deployment NPM token and a privileged GitHub secret with write access to the repository.

Exploiting the “Release Canary” Workflow

At this point, we could submit our own malicious pull request with a modified “x” script and exfiltrate all the environment variables passed to the script within the workflow to a remote Burp collaborator server. We submitted our pull request and commented “!canary” to trigger execution of the workflow. Within a few minutes we obtained the NPM deployment secret and a GITHUB_TOKEN  assigned to the workflow with write access to the repository and the ability to modify releases (see Figure 3). We also demonstrated the ability to backdoor GitHub releases (see Figure 4) and compromise the Rspack NPM package (see Figure 5).

Figure 3: We successfully exfiltrated the NPM deployment token for Rspack by exploiting the Pwn Request vulnerability within the “Release Canary” workflow.

Figure 3: We successfully exfiltrated the NPM deployment token for Rspack by exploiting the Pwn Request vulnerability within the “Release Canary” workflow.

Figure 4: We demonstrated the ability to modify the releases section of the Rspack repository, but then promptly reverted the change to avoid alarming end-users of the software by modifying the release

Figure 4: We demonstrated the ability to modify the releases section of the Rspack repository, but then promptly reverted the change to avoid alarming end-users of the software by modifying the release

Figure 5: We observed that Rspack averaged around seventy to eighty-thousand weekly downloads.

Figure 5: We observed that Rspack averaged around seventy to eighty-thousand weekly downloads.

Exploiting the Diff Assets Workflow

Pwn Request exploitation typically does not require the use of self-hosted runners. However, using self-hosted runners on a repository can increase the impact of a Pwn request vulnerability. To investigate this impact, we compromised a self-hosted runner used by the Diff Assets workflow within the Rspack repository. Ultimately, we could execute the following steps, which led to the compromise of a GitHub Personal Access Token (PAT) with complete administrative privileges over the Rspack repository.

1. Install Persistence

We exploited a Pwn Request vulnerability within the Diff Assets workflow, similar to the Release Canary workflow, to install persistence on the self-hosted runner using the Runner-on-Runner (RoR) C2 technique (see Figure 6).

Figure 6: We achieved code execution on the non-ephemeral self-hosted runner by using our own GitHub self-hosted runner for command and control.

Figure 6: We achieved code execution on the non-ephemeral self-hosted runner by using our own GitHub self-hosted runner for command and control.

2. Investigate Docker Containers

While investigating Docker containers, we noticed several containers on the self-hosted runner processing workflows from other GitHub Actions jobs within the Rspack repository (see Figure 7).

Figure 7: The ec2-linux-* Docker containers were self-hosted runners that executed workflows from the Rspack repository.

Figure 7: The ec2-linux-* Docker containers were self-hosted runners that executed workflows from the Rspack repository.

3. Trigger the “Diff Assets” workflow

The “Diff Assets” workflow executed within a Docker container on the compromised self-hosted runner using the secrets.Rspack_REPORT_ACCESS_TOKEN GitHub secret (see Figure 8).

Figure 8: We found a workflow that was passed a privileged secret and ran on a self-hosted runner, but wasn’t vulnerable to a Pwn Request vulnerability.

Figure 8: We found a workflow that was passed a privileged secret and ran on a self-hosted runner, but wasn’t vulnerable to a Pwn Request vulnerability. 

We couldn’t steal this secret directly with the Pwn request, but we could trigger this workflow and then tamper with the workflow execution during run-time, which would include the GitHub secret. We triggered the workflow with the Issue Comment trigger.

4. Exfiltrate Environment Variables

Using “Docker exec” on the compromised self-hosted runner, we exfiltrated the container’s environment variables by executing the “Diff Assets” workflow. The Rspack_REPORT_ACCESS_TOKEN was included in these environment variables (see Figure 9).

Figure 9: We successfully obtained the GitHub secret associated with the workflow by leveraging our position on the self-hosted runner.

Figure 9: We successfully obtained the GitHub secret associated with the workflow by leveraging our position on the self-hosted runner.

5. Analyzing PAT Access with Gato

The PAT belonged to a user with access to multiple GitHub organizations. Notable access included administrative control over the Rspack repository (see Figure 10) and administrative privileges over entire GitHub organizations, including the Rspack-contrib organization. For detailed explanations of GitHub Actions post-exploitation techniques similar to the ones used during this attack, check out the recent critical TensorFlow and PyTorch attack walkthroughs.

Figure 10: We used Gato to enumerate the PAT’s permissions.

Figure 10: We used Gato to enumerate the PAT’s permissions.

How can I Detect these Issues Automatically?

We have integrated our open-source Gato utility into our Chariot offensive security platform. This allows Chariot to continually perform enumeration of GitHub repositories to identify common configuration issues, such as the usage of self-hosted runners on public repositories. Adnan Khan has also been developing Gato-X, an experimental fork of Gato that can automatically identify Pwn Request vulnerabilities at scale. We plan on integrating Gato-X into the platform once it is released.

Figure 11: An example screenshot where we leveraged Chariot to enumerate public repositories with self-hosted runners configured across a variety of GitHub organizations.

Figure 11: An example screenshot where we leveraged Chariot to enumerate public repositories with self-hosted runners configured across a variety of GitHub organizations.

Remediation

After reaching out to Rspack maintainers, they quickly removed the vulnerable workflows from the Rspack repository. The repository still utilizes self-hosted runners, which could heighten the impact of future vulnerabilities if they are discovered.

Conclusion

In this article, we discussed some of the dangers inherent to Pwn Request vulnerabilities and how self-hosted runners can be exploited to harvest secrets from other workflows that share the self-hosted runner server. We wanted to continue to highlight the risk of these vulnerabilities, given their severity and prevalence combined with a general lack of awareness of these vulnerabilities within the broader developer and security communities. We also provided some additional resources for those looking to learn more about these vulnerabilities and how they can be exploited.