Coordinated Disclosure Timeline

Summary

Cilium push-chart-ci.yaml workflow is vulnerable to a Poisoned Pipeline Execution (PPE) attack which may lead to the exfiltration of the QUAY_CHARTS_DEV_PASSWORD and QUAY_CHARTS_DEV_USERNAME secrets. Additionally, it is also vulnerable to Cache Poisoning attack which may allow an attacker to gain elevated privileges in a different workflow.

Project

Cilium

Tested Version

Latest commit at the time of reporting.

Details

Issue 1: Secret leak (GHSL-2024-226)

The push-chart-ci.yaml workflow gets triggered when the Image CI Build or Hot Fix Image Release Build finish:

on:
  # run after the image build completes
  workflow_run:
    workflows:
      - Image CI Build
      - Hot Fix Image Release Build
    types:
      - completed

When that happens successfully, the push-charts gets executed:

jobs:
  push-charts:
    name: Push Charts
    runs-on: ubuntu-22.04
    # we also check for push events in case someone is testing the workflow by uncommenting the push trigger above.
    if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' || github.event_name == 'push' }}

In this scenario, the get-ref step will load the triggering Pull Request HEAD SHA into the steps.get-ref.outputs.ref variable ([1]):

    - name: Get triggering event ref
      id: get-ref
      run: |
        if [[ "${{ github.event_name }}" == "workflow_dispatch"  ]]; then
          echo ref="${{ inputs.checkout_ref }}" >> $GITHUB_OUTPUT
          echo sha="${{ inputs.checkout_ref }}" >> $GITHUB_OUTPUT
        elif [[ "${{ github.event_name }}" == "workflow_run" ]]; then
          if [[ "${{ github.event.workflow_run.head_repository.fork }}" == "true"  ]]; then
            # use the SHA on forks since the head_branch won't exist in the upstream repository
            echo ref="${{ github.event.workflow_run.head_sha }}" >> $GITHUB_OUTPUT # <--------- [1]
          else
            echo ref="${{ github.event.workflow_run.head_branch }}" >> $GITHUB_OUTPUT
          fi
          echo sha="${{ github.event.workflow_run.head_sha }}" >> $GITHUB_OUTPUT
        elif [[ "${{ github.event_name }}" == "push" ]]; then
          echo ref="${{ github.ref }}" >> $GITHUB_OUTPUT
          echo sha="${{ github.sha }}" >> $GITHUB_OUTPUT
        else
          echo "Invalid event type"
          exit 1
        fi

After that, the workflow will checkout the Pull Request HEAD branch containing untrusted code since an attacker will be able to control its contents:

    - name: Checkout Source Code
      uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
      with:
        persist-credentials: false
        # checkout ref not SHA so we can get useful branch names (see previous comments)
        ref: ${{ steps.get-ref.outputs.ref }}
        # required for git describe
        fetch-depth: 0

Right after checking out the untrusted code into the workflow’s workspace, it will execute the ./contrib/scripts/print-chart-version.sh script which may contain attacker controlled code.

    - name: Get version
      id: get-version
      run: |
        set -o pipefail
        set -e
        if [[ -f ./contrib/scripts/print-chart-version.sh ]]; then
          echo "chart_version=$(./contrib/scripts/print-chart-version.sh)" | tee -a $GITHUB_OUTPUT
        else
          echo "./contrib/scripts/print-chart-version.sh missing. Perhaps it needs to be backported to your target branch?"
          exit 1
        fi

Steps to Reproduce

name: Image CI Build

on:
  pull_request:
jobs:
  ok:
    runs-on: ubuntu-latest
    steps:
       - run: echo "Done"

Impact

The workflow runs with read only permissions, however, it reads the QUAY_CHARTS_DEV_PASSWORD and QUAY_CHARTS_DEV_USERNAME secrets and therefore the attacker-controlled code will be able to dump the runner’s memory and exfiltrate these secrets.

Resources

Issue 2: Cache Poisoning (GHSL-2024-227)

The same code execution described above will allow an attacker to write to the cache in the context of the default branch (since the job is triggered by a workflow_run event). The cache of the default branch is queried by any other branch and therefore, being able to write to this cache will allow an attacker to control the files restored by other workflows.

Impact

This issue may lead to privilege escalation allowing an attacker to move laterally to other more privileged workflows.

Resources

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-226 or GHSL-2024-227 in any communication regarding these issues.