Skip to content

GitHub - Workflow Privilege Escalation via Artifacts Upload

High
rcorrea35 published GHSA-cj34-9v6h-grxm Jun 7, 2024

Package

actions GitHub Workflow (GitHub Actions)

Affected versions

< 0.3.2

Patched versions

None

Description

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

Severity

High

CVE ID

No known CVE

Weaknesses

No CWEs

Credits