Share
## https://sploitus.com/exploit?id=DB66EC5E-FB97-55F4-A9EE-CEF4D6405837
# 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
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
bash scripts/02-setup-patched.sh
# or: make setup-patched
```

### Step 3 โ€” Trigger the CVE

```bash
bash scripts/03-trigger-cve.sh
# or: make trigger
```

**Expected output โ€” vulnerable (v3.2.0):**
```diff
===== /Secret production/db-credentials ======
   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

```bash
# 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
bash scripts/99-teardown.sh
# or: make teardown
```

---

## Directory Structure

```
argocd-cve-2026-42880-lab2/
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ LAB_REPORT.md                   # Full lab setup guide + troubleshooting (10 errors)
โ”œโ”€โ”€ VULNERABILITY_ANALYSIS.md       # Code-level vulnerability analysis
โ”œโ”€โ”€ NUCLEI_TEMPLATE_REPORT.md       # Nuclei template design and test results
โ”œโ”€โ”€ Makefile
โ”‚
โ”œโ”€โ”€ 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
โ”‚   โ””โ”€โ”€ PR_DESCRIPTION.md           # nuclei-templates PR body
โ”‚
โ””โ”€โ”€ 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/)

---

> **Warning:** All credentials in this lab are fake test data for security research purposes only. Never use in production.