Coordinated Disclosure Timeline

Summary

Multiple Eclipse repositories are vulnerable to Poisoned Pipeline Execution (PPE) via Code Injection allowing a malicious actor to exfiltrate the Eclipse’s Personal Access Token with organization write permission.

Project

Eclipse JDT, Eclipse Platform, Eclipse Platform UI, Eclipse PDE, Eclipse Equinox P2 and Eclipse-JDTLS

Tested Version

Latest commit at the time of reporting.

Details

The following repositories have worflows manifesting the same Vulnerability. The following report focuses on the Eclipse JDT ones but they apply to:

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.

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

The publishVersionCheckResults.yml workflow checks outs untrusted code from the triggering Pull Request:

- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
  if: steps.search-patch.outputs.result
  with:
    ref: "${{ github.event.workflow_run.head_sha }}"
    persist-credentials: false #Opt out from persisting the default Github-token authentication in order to enable use of the bot's PAT when pushing below

Since an attacker can upload the version_increments.patch file, they will be able to control the file list returned by the git diff-tree command:

          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

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.

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.