Summary
A File Traversal vulnerability was found in a GitHub Action that is used to download and extract artifacts.
Depending on a repository's workflow configuration, attackers could gain access to GitHub repository secrets, commit source code as arbitrary authors, create releases, and obtain Open ID credentials,
Exploitation for privilege escalation within a user's workflow required the following:
A low privileged job runs code from a Pull Request
A low privileged job uploads an artifact archive
A high privileged job extracts the archive, created by the low privileged job
Severity
High - Attackers were previously able to leverage this vulnerability to craft and upload malicious GitHub Artifacts, causing arbitrary file writes when extracted
Proof of Concept
GitHub Actions allow repository owners to configure arbitrary tasks to run on triggers. Tasks are grouped together in jobs, and jobs have permissions tied to them. Jobs perform actions, can be written in any language, and are executed by referencing them in yaml workflow files.
When performing privileged GitHub operations against a Pull Request (such as running code from a PR, then leaving a comment), GitHub recommends creating a low-privileged and high-privileged job. Permissions of jobs are defined within workflows, and can't be modified by unaccepted pull requests. To communicate between the isolated tasks, GitHub recommends using artifacts.
Arbitrary File Writes via Path Traversal zero-day in unzip-stream
GitHub's download artifact library relies on the unzip-stream nodejs library to extract files . The unzip-stream library before 0.3.2 was vulnerable to files containing path traversal characters. By unzipping a malicious crafted artifact, arbitrary files can be overwritten, such as python files used to execute tasks.
For example, when unzip-stream opens an archive, and unzips a file within it named x/../../../../../../../../../../../../../home/runner/somefile, it will be stored under /home/runner/somefile.
This vulnerability broke security isolation between unprivileged and privileged jobs, allowing privilege escalation via code execution.
Uploading Arbitrary Artifacts by Patching GitHub Actions Code at Runtime
The details of how artifact uploads work is not officially documented (although the artifact action code is open source and published on GitHub). A public comment on the repository from a GitHub developer mentions the library/tasks makes use of a special upload token not available to other tasks.
By patching the upload artifact code when executing code in a Pull Request, an attacker could bypass upload validation logic. The attacker could then uploaded construct and upload an archive with file traversal characters, to exploit bug #1.
Patching of the library is achievable as it's downloaded into a container shared by other tasks within the same job. The artifact upload library is located at /home/runner/work/_actions/actions/upload-artifact/v4/dist/upload/index.js, and is owned by runner, which is also the linux user executing tasks.
whoami
ls -lah /home/runner/work/_actions/actions/upload-artifact/v4/dist/upload/index.js
shell: /user/bin/bash -e (0)
runner
-rw-r--r-- 1 runner docker 4.7M Feb 5 21:19 /note/runner/work/_actions/actions/upload-artifact/v4/dist/upload/index.js
Default settings for public repositories don't run workflows against Pull Requests for new contributors to the repository. This constraint can be bypassed by making a spelling change in a file, and having a non-malicious PR accepted. Workflows are auto executed for subsequent PRs by the same contributor.
Further Analysis
The following workflow demonstrates a minimized vulnerable configuration. High privileged jobs triggered by pull_request also are vulnerable.
name: elevate
on:
- pull_request_target
jobs:
jobOne:
runs-on: ubuntu-latest
permissions:
contents: read # Only allow read access to repository in jobOne
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
# Code from Pull Request executed here. A malicious PR would overwrite upload-artifact code,
# to upload a malicious zip file.
- run: bash /home/runner/work/test/test/[target.sh](http://target.sh/)
- uses: actions/upload-artifact@v4
with:
name: lint-log
path:/tmp/GOOGLEflag
retention-days: 1
jobTwo:
runs-on: ubuntu-latest
needs: jobOne
permissions:
contents: write # This would allow writing to the repository. Normally this is okay
# as we aren't executing PR code.
steps:
- uses: actions/download-artifact@v4 #The high-privileged job is compromised here, as
with: # the unzip vuln can overwrite any file owned
name: lint-log # by user "runner". Attacker gains RCE in this job.
- run: python [hello.py](http://hello.py/) # Any action which is passed a sensitive token
env: # or has executes with high privileges can be used
# for priv esc
GH_TOKEN: {{secrets.GITHUB_TOKEN}}
Note, repositories created before 2023 have a default GitHub token with read/write capabilities, likely increasing the number of vulnerable configs, if permissions aren't downscoped .
Based on GitHub Documentation for the pull_request_target trigger and Github's default token permissions blog update, it's unclear if new GitHub access tokens default to read/write when operating on pull requests. We only were testing the proof of concept using forks against a private repository to prevent leaking the finding. Permissions seemed to default to read only.
Timeline
Date reported: 02/20/24
Date fixed: 02/26/24
Date disclosed: 06/07/24
Summary
A File Traversal vulnerability was found in a GitHub Action that is used to download and extract artifacts.
Depending on a repository's workflow configuration, attackers could gain access to GitHub repository secrets, commit source code as arbitrary authors, create releases, and obtain Open ID credentials,
Exploitation for privilege escalation within a user's workflow required the following:
A low privileged job runs code from a Pull Request
A low privileged job uploads an artifact archive
A high privileged job extracts the archive, created by the low privileged job
Severity
High - Attackers were previously able to leverage this vulnerability to craft and upload malicious GitHub Artifacts, causing arbitrary file writes when extracted
Proof of Concept
GitHub Actions allow repository owners to configure arbitrary tasks to run on triggers. Tasks are grouped together in jobs, and jobs have permissions tied to them. Jobs perform actions, can be written in any language, and are executed by referencing them in yaml workflow files.
When performing privileged GitHub operations against a Pull Request (such as running code from a PR, then leaving a comment), GitHub recommends creating a low-privileged and high-privileged job. Permissions of jobs are defined within workflows, and can't be modified by unaccepted pull requests. To communicate between the isolated tasks, GitHub recommends using artifacts.
Arbitrary File Writes via Path Traversal zero-day in unzip-stream
GitHub's download artifact library relies on the unzip-stream nodejs library to extract files . The unzip-stream library before 0.3.2 was vulnerable to files containing path traversal characters. By unzipping a malicious crafted artifact, arbitrary files can be overwritten, such as python files used to execute tasks.
For example, when unzip-stream opens an archive, and unzips a file within it named x/../../../../../../../../../../../../../home/runner/somefile, it will be stored under /home/runner/somefile.
This vulnerability broke security isolation between unprivileged and privileged jobs, allowing privilege escalation via code execution.
Uploading Arbitrary Artifacts by Patching GitHub Actions Code at Runtime
The details of how artifact uploads work is not officially documented (although the artifact action code is open source and published on GitHub). A public comment on the repository from a GitHub developer mentions the library/tasks makes use of a special upload token not available to other tasks.
By patching the upload artifact code when executing code in a Pull Request, an attacker could bypass upload validation logic. The attacker could then uploaded construct and upload an archive with file traversal characters, to exploit bug #1.
Patching of the library is achievable as it's downloaded into a container shared by other tasks within the same job. The artifact upload library is located at /home/runner/work/_actions/actions/upload-artifact/v4/dist/upload/index.js, and is owned by runner, which is also the linux user executing tasks.
Default settings for public repositories don't run workflows against Pull Requests for new contributors to the repository. This constraint can be bypassed by making a spelling change in a file, and having a non-malicious PR accepted. Workflows are auto executed for subsequent PRs by the same contributor.
Further Analysis
The following workflow demonstrates a minimized vulnerable configuration. High privileged jobs triggered by pull_request also are vulnerable.
Note, repositories created before 2023 have a default GitHub token with read/write capabilities, likely increasing the number of vulnerable configs, if permissions aren't downscoped .
Based on GitHub Documentation for the pull_request_target trigger and Github's default token permissions blog update, it's unclear if new GitHub access tokens default to read/write when operating on pull requests. We only were testing the proof of concept using forks against a private repository to prevent leaking the finding. Permissions seemed to default to read only.
Timeline
Date reported: 02/20/24
Date fixed: 02/26/24
Date disclosed: 06/07/24