HAERIN-L/POC_CVE-2026-42880

GitHub: HAERIN-L/POC_CVE-2026-42880

Stars: 0 | Forks: 0

# CVE-2026-42880 — ArgoCD Secret Exposure via ServerSideDiff A lab environment for reproducing and detecting **CVE-2026-42880**, a critical vulnerability in Argo CD where the `ServerSideDiff` gRPC handler exposes Kubernetes Secret data to read-only users. ## Vulnerability Overview | Field | Details | |-------|---------| | CVE ID | CVE-2026-42880 | | GHSA | [GHSA-3v3m-wc6v-x4x3](https://github.com/advisories/GHSA-3v3m-wc6v-x4x3) | | CVSS | 9.6 (Critical) — `AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N` | | Affected versions | ArgoCD 3.2.0–3.2.10, 3.3.0–3.3.8 | | Patched versions | 3.2.11, 3.3.9+ | | CWE | CWE-200, CWE-212 | ### Root Cause `serverSideDiff()` in ArgoCD's gRPC handler calls the Kubernetes SSA dry-run and returns `predictedLive` **without calling `hideSecretData()`**, exposing base64-encoded Secret values in the response. Vulnerable path (v3.2.0): argocd app diff --server-side-diff → gRPC ServerSideDiff handler → Kubernetes SSA dry-run (merges ALL field managers) ← predictedLive returned (includes external-controller's data) ❌ hideSecretData() NOT called → real Secret values exposed Patched path (v3.2.11): ...same SSA dry-run... ✅ HideSecretData() called → values replaced with ++++ ### Attack Prerequisites All three conditions must be met simultaneously: | # | Condition | Details | |---|-----------|---------| | 1 | Vulnerable ArgoCD version | 3.2.0–3.2.10 or 3.3.0–3.3.8 | | 2 | Application annotation | `argocd.argoproj.io/compare-options: ServerSideDiff=true,IncludeMutationWebhook=true` | | 3 | External field manager on Secret data | Secret `data` fields owned by a non-ArgoCD manager (e.g., External Secrets Operator, Helm, kubectl) | Only `role:readonly` is required — no write permissions needed. ## Lab Architecture Host Machine ├── localhost:30080 ──→ Kind Cluster: cve-vuln (ArgoCD v3.2.0 ⚠ VULNERABLE) │ └── ns: production │ ├── Secret: db-credentials │ │ metadata → argocd-controller (synced from Git) │ │ data.* → external-controller ⚠ (injected separately) │ └── Secret: api-credentials (same setup) │ ├── localhost:30081 ──→ Kind Cluster: cve-patched (ArgoCD v3.2.11 ✓ PATCHED) │ └── (identical config — only ArgoCD version differs) │ └── localhost:3010 ──→ Docker Container: cve-lab-gitea └── repo: gitadmin/manifests.git └── secret.yaml (no data field — CVE prerequisite) ### Why the field manager split matters db-credentials Secret (namespace: production) ┌──────────────────────────────────────────────────────────────┐ │ metadata.* → argocd-controller (ArgoCD syncs from Git) │ │ data.* → external-controller (injected by setup script)│ └──────────────────────────────────────────────────────────────┘ SSA dry-run: Kubernetes merges both managers' fields into predictedLive → ArgoCD does NOT own data → data is not masked by ArgoCD → v3.2.0 returns predictedLive without hideSecretData() → EXPOSED ## Prerequisites | Tool | Install | |------|---------| | [kind](https://kind.sigs.k8s.io/) | `brew install kind` | | [kubectl](https://kubernetes.io/docs/tasks/tools/) | `brew install kubectl` | | [Docker Desktop](https://www.docker.com/) | docker.com | | [argocd CLI](https://argo-cd.readthedocs.io/en/stable/cli_installation/) | `brew install argocd` | | [nuclei](https://github.com/projectdiscovery/nuclei) | `brew install nuclei` | | curl, jq, git | pre-installed on macOS or `brew install jq` | **Resource requirements:** 8GB+ free RAM, 15GB+ free disk, ports 30080 / 30081 / 3010 available. ## How to Run ### Step 1 — Set up vulnerable environment (ArgoCD v3.2.0) bash scripts/01-setup-vuln.sh # or: make setup-vuln Takes ~10 minutes. When done: ══════════════════════════════════════════════════════ Vulnerable ArgoCD lab ready! ══════════════════════════════════════════════════════ ArgoCD UI : http://localhost:30080 Admin pass : Viewer pass : viewerpass123 Token file : .vuln-viewer-token ══════════════════════════════════════════════════════ ### Step 2 — Set up patched environment for comparison (optional) bash scripts/02-setup-patched.sh # or: make setup-patched ### Step 3 — Trigger the CVE bash scripts/03-trigger-cve.sh # or: make trigger **Expected output — vulnerable (v3.2.0):** ===== /Secret production/db-credentials ====== < db_password: ++++++++ ← masked live state --- > db_password: U3VwM3JTM2NyM3REQiFQYXNzIzIwMjY= ← EXPOSED predictedLive! [EXPOSED] decoded: Sup3rS3cr3tDB!Pass#2026 ⚠ RESULT: SECRET DATA EXPOSED — VULNERABLE **Expected output — patched (v3.2.11):** > db_password: ++++++++ ← masked ✓ RESULT: no unmasked data in predictedLive — PATCHED ### Step 4 — Nuclei detection # Vulnerable cluster → should produce a [critical] finding nuclei -t nuclei/CVE-2026-42880.yaml \ -u http://localhost:30080 \ -var username=viewer \ -var password=viewerpass123 # Patched cluster → should produce no findings nuclei -t nuclei/CVE-2026-42880.yaml \ -u http://localhost:30081 \ -var username=viewer \ -var password=viewerpass123 ### Step 5 — Teardown bash scripts/99-teardown.sh # or: make teardown ## Directory Structure argocd-cve-2026-42880-lab2/ ├── README.md ├── LAB_SETUP_GUIDE.md # Lab setup guide + troubleshooting (English) ├── VULNERABILITY_ANALYSIS.md # Code-level vulnerability analysis (English) ├── Nuclei_Template_Report.md # Nuclei template design and test results (English) ├── Makefile │ ├── REPORT/ # Korean reports │ ├── LAB_REPORT_KR.md │ ├── Nuclei_Template_Report_KR.md │ └── Vulnerability_Analysis_KR.md │ ├── kind/ │ ├── cluster-vuln.yaml # Kind cluster: cve-vuln (port 30080) │ └── cluster-patched.yaml # Kind cluster: cve-patched (port 30081) │ ├── git-manifests/ │ └── secret.yaml # Secret without data field (CVE prerequisite) │ ├── manifests/ │ ├── application.yaml # ArgoCD Application with vulnerable annotation │ ├── argocd-cm-patch.yaml # ConfigMap: TLS off, viewer account, ServerSideDiff │ ├── argocd-rbac-patch.yaml # RBAC: viewer → role:readonly │ ├── argocd-nodeport.yaml # NodePort 30080 (vuln cluster) │ └── argocd-nodeport-patched.yaml# NodePort 30081 (patched cluster) │ ├── nuclei/ │ └── CVE-2026-42880.yaml # Nuclei detection template │ └── scripts/ ├── 01-setup-vuln.sh # Full automated setup: vulnerable env ├── 02-setup-patched.sh # Full automated setup: patched env ├── 03-trigger-cve.sh # Trigger CVE + compare both clusters └── 99-teardown.sh # Remove all lab resources ## Nuclei Template Detection Logic The template uses a 4-step HTTP chain to verify all CVE prerequisites without triggering actual Secret extraction: Step 1 GET /api/version → extract argocd_version (no auth required) Step 2 POST /api/v1/session → authenticate as viewer (role:readonly), extract token Step 3 GET /api/v1/applications → find app with ServerSideDiff=true,IncludeMutationWebhook=true Step 4 GET /api/v1/applications/{app}/managed-resources → verify: version in range + Secret present + f:data owned by external manager → FINDING reported only if all 5 matchers pass (AND condition) ## References - [NVD — CVE-2026-42880](https://nvd.nist.gov/vuln/detail/CVE-2026-42880) - [GHSA-3v3m-wc6v-x4x3](https://github.com/advisories/GHSA-3v3m-wc6v-x4x3) - [Patch PR #27598](https://github.com/argoproj/argo-cd/pull/27598) - [ArgoCD Server-Side Diff docs](https://argo-cd.readthedocs.io/en/stable/user-guide/diff-strategies/#server-side-diff) - [Kubernetes Server-Side Apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/)