CI-Story: How We Found Critical Vulnerabilities in StoryBook Project

user profile
Head of Security Research

StoryBook is an extremely popular open-source frontend framework that allows the building of isolated UI components and pages. Several factors indicate its popularity:

  • It is one of the most popular projects on GitHub, with 75k stars
  • More than 16 million downloads per month for just its React NPM package
  • More than 1800 contributors to the project
  • According to the StoryBook website, the product is used by major companies, such as GitHub, Airbnb, Mozilla, Salesforce, Lyft, Atlassian, and more.
  • Wide variety of plugins and integrations

Cycode found several vulnerabilities in its GitHub Actions development pipeline that may have allowed any user on the internet to run arbitrary code on its build server. This Remote Code Execution (RCE) capability could have allowed attackers to insert malicious code into the popular project or exfiltrate sensitive tokens.

We consider these vulnerabilities critical due to the following reasons:

  • The project is open-source, allowing any user on the internet to exploit it without any user intervention.
  • As we’ll see, exploiting the vulnerabilities is relatively easy.
  • The project is highly popular, so inserting any malicious code into it can potentially cause a massive supply-chain attack on many victims.

Fortunately, we discovered it only thirteen days after it had been inserted into the main branch, allowing minimal time for potential attackers to exploit these vulnerabilities.

Vulnerability Explained

Like other popular open-source projects, StoryBook is maintained on GitHub and uses several CI/CD systems with many customized development workflows. In their case, it is GitHub Actions and CircleCI.

One of the workflows, trigger-circle-ci-workflow, triggers the CircleCI pipeline from GitHub Actions, so it must have permission to access build secrets and uses the permissive trigger: pull_request_target. As we’ll cover in the post-exploitation, it also extends the threat and exploitation scenario to CircleCI.

The workflow has two vulnerabilities:

  1. The first job in the workflow references the branch name inside the script without sanitizing it. It happens in the following line: export BRANCH=${{ github.event.pull_request.head.ref }}.
    The branch name can be configured by any user and may contain malicious scripts.
  2. Subsequent jobs in the workflow reference the first job’s output through ${{ needs.get-branch.outputs.branch }} without sanitizing it, which may cause the same effect as the first vulnerability.

These two issues aren’t related and could compromise the build server independently.

Attack Flow

First, fork the project. Then, create any change in the project (for example, adding a space to the README file) and commit the change into a new beautiful branch name: alex${IFS};fi;echo${IFS}pwned;

Then push this to the official repository as a forked pull request. That triggers the vulnerable workflow, with elevated permissions and possible access to sensitive secrets.

We didn’t perform this attack on the official repository. Still, we reproduced it in our lab environment to verify that such a pull request would yield the following output in the build logs:

We can see it managed to print the pwned text, just like the script we embedded into the branch name instructed it!

GitHub limits the branch name to 255 chars, which leaves us more than enough room to fetch a malicious script, give it proper permissions, run it, and get complete code execution capabilities on the build server!

The second vulnerability is caused by referencing ${{ needs.get-branch.outputs.branch }} in several places around the workflow. With a similar attack path and a few tweaks in the payload, we could also exploit it. 

Post-Exploitation Consequences

Exfiltrating CircleCI Token

By exploiting the second vulnerability alone, we could easily exfiltrate the CIRCLE_CI_TOKEN. Build secrets are exposed in the step they are used. O