Skip to content

Multiple repositories are vulnerable to Poisoned Pipeline Execution (PPE)

Basic information

Project name: eclipse-jdt/eclipse.jdt.core, eclipse-jdt/eclipse.jdt.ui, and eclipse-jdt/eclipse.jdt.debug GitHub repositories

Project id:

What are the affected versions?

Latest commit at the time of reporting.

Summary

eclipse-jdt/eclipse.jdt.core, eclipse-jdt/eclipse.jdt.ui, and eclipse-jdt/eclipse.jdt.debug repositories are vulnerable to Poisoned Pipeline Execution (PPE) via Code Injection allowing a malicious actor to exfiltrate the JDT_BOT_PAT Personal Access Token with organization write permission.

Details

Issue 1: Code Injection via PR branch name in publishVersionCheckResults.yml (GHSL-2024-320)

The version-increments.yml workflow present in the eclipse-jdt/eclipse.jdt.core, eclipse-jdt/eclipse.jdt.ui, and eclipse-jdt/eclipse.jdt.debug repositories get executed when the Pull-Request Checks workflow completes:

name: Publish Version Check Results

on:
  workflow_run:
    workflows: [ 'Pull-Request Checks' ]
    types: [ completed ]

It then calls the publishVersionCheckResults.yml reusable workflow from the eclipse-platform/eclipse.platform.releng.aggregator repository and passes the JDT_BOT_PAT secret to it:

jobs:
  publish-version-check-results:
    uses: eclipse-platform/eclipse.platform.releng.aggregator/.github/workflows/publishVersionCheckResults.yml@master
    with:
      botGithubId: eclipse-jdt-bot
    secrets:
      githubBotPAT: ${{ secrets.JDT_BOT_PAT }}

The publishVersionCheckResults.yml workflow will check if an artifact called versions-git-patch is available from the triggering workflow:

    - name: Search version increment git patch
      uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
      id: search-patch
      with:
        script: |
          let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
             run_id: context.payload.workflow_run.id,
             ...context.repo
          })
          let artifact = allArtifacts.data.artifacts.find(artifact => artifact.name == 'versions-git-patch')
          return artifact?.id

If it exists, it will checkout the code of the triggering Pull request, download the artifact and create a patch to apply to the Pull Request origin repository:

    - name: Apply and push version increment
      id: git-commit
      if: steps.search-patch.outputs.result
      run: |
          set -x
          # Set initial placeholder name/mail and read it from the patch later
          git config --global user.email 'foo@bar'
          git config --global user.name 'Foo Bar'
          git am version_increments.patch
          # Read the author's name+mail from the just applied patch and recommit it with both set as committer
          botMail=$(git log -1 --pretty=format:'%ae')
          botName=$(git log -1 --pretty=format:'%an')
          git config --global user.email "${botMail}"
          git config --global user.name "${botName}"
          git commit --amend --no-edit
          fileList=$(git diff-tree --no-commit-id --name-only HEAD -r)
          echo "file-list<<EOF" >> $GITHUB_OUTPUT
          echo "$fileList" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT
          git push \
            "https://oauth2:${BOT_PA_TOKEN}@github.com/${{ github.event.workflow_run.head_repository.full_name }}.git" \
            'HEAD:refs/heads/${{ github.event.workflow_run.head_branch }}'

In the last line, the attacker-controlled Pull Request branch name gets interpolated in the bash code. Since branch names can contain backticks and dollar symbols, it is possible for an attacker to create a Pull Request from a branch called foo`CMD$IFSARG1$IFSARG2`bar which will run the CMD ARG1 ARG2 command, allowing the attacker to run arbitrary commands on the GitHub runner and exfiltrate the JDT_BOT_PAT by dumping the runner's memory.

Impact

This issue may lead to the exfiltration of the JDT_BOT_PAT Personal Access Token. Since this PAT is used in three different repositories, it seems like its a token defined at the organization level and that could have write access at the org level.

Remediation

Pass the branch name as an environment variable:

    - name: Apply and push version increment
      id: git-commit
      if: steps.search-patch.outputs.result
      env:
        BRANCH_NAME: ${{ github.event.workflow_run.head_branch }}
        REPO_NAME: ${{ github.event.workflow_run.head_repository.full_name }}
      run: |
...
          git push \
            "https://oauth2:${BOT_PA_TOKEN}@github.com/${REPO_NAME}.git" \
            'HEAD:refs/heads/${BRANCH_NAME}}'

Issue 2: Code Injection via PR changed file names in publishVersionCheckResults.yml (GHSL-2024-321)

Similarly, the publishVersionCheckResults.yml workflow extracts a list of files changed by the Pull Request:

          fileList=$(git diff-tree --no-commit-id --name-only HEAD -r)
          echo "file-list<<EOF" >> $GITHUB_OUTPUT
          echo "$fileList" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

This list of attacker-controlled names is assigned to the steps.git-commit.outputs.file-list output variable and later interpolated into a JS script allowing an attacker to gain arbitrary code execution:

    - name: Add or update information comment
      uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
      if: always()
      with:
        github-token: ${{ secrets.githubBotPAT }}
        script: |
          const fs = require('fs')
          const fileList = `${{ steps.git-commit.outputs.file-list }}`
          if (fileList) { // if list is empty, no versions were changed
...

Impact

This issue may lead to the exfiltration of the JDT_BOT_PAT Personal Access Token. Since this PAT is used in three different repositories, it seems like its a token defined at the organization level and that could have write access at the org level.

Remediation

Pass the steps.git-commit.outputs.file-list variable as an environment variable to the JS script to prevent code injection:

    - name: Add or update information comment
      uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
      if: always()
      env:
        FILELIST: ${{ steps.git-commit.outputs.file-list }}
      with:
        github-token: ${{ secrets.githubBotPAT }}
        script: |
          const fs = require('fs')
          const fileList = process.env.FILELIST
          if (fileList) { // if list is empty, no versions were changed
...

GitHub Security Advisories

We recommend you create a private GitHub Security Advisory for these findings. This also allows you to invite the GHSL team to collaborate and further discuss these findings in private before they are published.

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

Disclosure Policy

This report is subject to a 90-day disclosure deadline, as described in more detail in our coordinated disclosure policy.

To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information