Skip to content

Commit 90dcaa1

Browse files
1 parent be16364 commit 90dcaa1

5 files changed

Lines changed: 260 additions & 1 deletion

File tree

advisories/github-reviewed/2026/04/GHSA-68jq-c3rv-pcrr/GHSA-68jq-c3rv-pcrr.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
{
22
"schema_version": "1.4.0",
33
"id": "GHSA-68jq-c3rv-pcrr",
4-
"modified": "2026-04-24T20:50:52Z",
4+
"modified": "2026-06-08T23:09:48Z",
55
"published": "2026-04-14T01:05:05Z",
66
"aliases": [
77
"CVE-2026-40476"
88
],
99
"summary": "graphql-php is affected by a Denial of Service via quadratic complexity in OverlappingFieldsCanBeMerged validation",
1010
"details": "The `OverlappingFieldsCanBeMerged` validation rule exhibits quadratic time complexity when processing queries with many repeated fields sharing the same response name. An attacker can send a crafted query like `{ hello hello hello ... }` with thousands of repeated fields, causing excessive CPU usage during validation before execution begins.\n\nThis is not mitigated by existing QueryDepth or QueryComplexity rules.\n\n**Observed impact (tested on v15.31.4):**\n- 1000 fields: ~0.6s\n- 2000 fields: ~2.4s\n- 3000 fields: ~5.3s\n- 5000 fields: request timeout (>20s)\n\n**Root cause:** `collectConflictsWithin()` performs O(n²) pairwise comparisons of all fields with the same response name. For identical repeated fields, every comparison returns \"no conflict\" but the quadratic iteration count causes resource exhaustion.\n\n**Fix:** Deduplicate structurally identical fields before pairwise comparison, reducing the complexity from O(n²) to O(u²) where u is the number of unique field signatures (typically 1 for this attack pattern).\n\n**Credit:** Ashwak N (ashwakn04@gmail.com)",
1111
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
15+
},
1216
{
1317
"type": "CVSS_V4",
1418
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N"
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-273q-qgh5-wrj6",
4+
"modified": "2026-06-08T23:09:21Z",
5+
"published": "2026-06-08T23:09:21Z",
6+
"aliases": [
7+
"CVE-2026-47725"
8+
],
9+
"summary": "nebula-mesh's web UI lacks CSRF tokens on /ui/* mutating endpoints",
10+
"details": "Every `/ui/*` POST / PUT / PATCH / DELETE route processes the request as soon as the session cookie validates. `SameSite=Lax` on the session cookie prevents most cross-site form submits but does not protect:\n\n- top-level form-submit navigations from third-party pages (some browsers still send Lax cookies on top-level POSTs)\n- same-registrable-domain attackers (sibling-subdomain XSS, subdomain takeover)\n- the `GET /ui/logout` route, which a third-party `<img src=\".../ui/logout\">` can force-trigger\n\nThe admin UI signs CA certificates, mints API keys, rotates / retires / deletes CAs, disables operators, and changes server settings. CSRF here is a real privilege escalation, not just annoyance.\n\n## Affected\nAll released versions up to v0.3.2.\n\n## Suggested fix\nDouble-submit cookie: a 32-byte `crypto/rand` token in a non-HttpOnly `_csrf` cookie, echoed in either `X-CSRF-Token` (htmx) or a `_csrf` form field (HTML forms). Compared in constant time. Rotated on every privilege transition (`Login`, OIDC `StartAuthenticatedSession`, `CompleteTwoFactor`, `Logout`) so pre-auth fixation cannot survive promotion. Rejections audit-logged as `ui.csrf.rejected` with reason; response body stays opaque.\n\n`/ui/logout` becomes POST so it is no longer reachable via `<img>` tags.\n\nFix coordinates with the Secure-cookie advisory disclosed concurrently — the `_csrf` cookie inherits the same `Secure`-attribute derivation.\n\n## Reproducer\nWith an authenticated operator session in browser tab A, open the following minimal HTML in any other tab:\n\n```html\n<form action=\"https://nebula.example.com/ui/cas/{ca-id}/delete\" method=\"POST\">\n <button>Click for free puppy</button>\n</form>\n```\n\nClick. The CA is deleted — the server processes the POST because the session cookie is automatically attached and there is no other check. The same trick works for force-rotate, retire, mint API keys, disable operators, etc.\n\nAlternative force-logout: `<img src=\"https://nebula.example.com/ui/logout\">` placed on an attacker's page logs out any visiting authenticated operator. No interaction required.\n\n## Notes\n- Multipart and JSON endpoints don't exist in the current UI surface. Future additions must rely on the header path because `r.PostFormValue` only reads `application/x-www-form-urlencoded` bodies. The middleware's package comment documents this.\n- The patch assumes nebula-mgmt is the sole authority on its registrable domain. A compromised sibling subdomain can still set parent-domain cookies and forge matches; SameSite=Lax does not prevent that. Documented in the patch.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V4",
14+
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:L/VI:H/VA:N/SC:N/SI:N/SA:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Go",
21+
"name": "github.com/juev/nebula-mesh"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "0.3.3"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "<= 0.3.2"
38+
}
39+
}
40+
],
41+
"references": [
42+
{
43+
"type": "WEB",
44+
"url": "https://github.com/juev/nebula-mesh/security/advisories/GHSA-273q-qgh5-wrj6"
45+
},
46+
{
47+
"type": "PACKAGE",
48+
"url": "https://github.com/juev/nebula-mesh"
49+
}
50+
],
51+
"database_specific": {
52+
"cwe_ids": [
53+
"CWE-269",
54+
"CWE-352"
55+
],
56+
"severity": "HIGH",
57+
"github_reviewed": true,
58+
"github_reviewed_at": "2026-06-08T23:09:21Z",
59+
"nvd_published_at": null
60+
}
61+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-598g-h2vc-h5vg",
4+
"modified": "2026-06-08T23:09:02Z",
5+
"published": "2026-06-08T23:09:02Z",
6+
"aliases": [
7+
"CVE-2026-47724"
8+
],
9+
"summary": "nebula-mesh: API endpoints lack ownership checks, enabling cross-operator privilege escalation",
10+
"details": "The `/api/v1/*` route surface trusts the bearer token alone for authorisation on most endpoints. The codebase itself admits this at `internal/api/hosts.go:384`: *\"API trusts the bearer token for authorisation; per-CA ownership is enforced only in the Web layer.\"*\n\nThe Web UI gates state-changing routes through `loadAccessibleCA` (`internal/web/cas.go`); CA-management endpoints in `internal/api/cas.go` ALSO have proper `canAccessCA` gates. **The gap is on the host, network, firewall, mobile-bundle, and most operator endpoints.** Combined with the per-operator CA model from ADR 0002, this gives any non-admin operator API key broad cross-tenant access — instant privilege escalation in the worst case.\n\n## Affected\nAll released versions prior to v0.3.4.\n\n## Exploit chain\n\n### A) Mint admin API key from any operator key (instant privilege escalation)\n`internal/api/operators.go:118` — `handleCreateOperatorAPIKey` does no admin check and no actor/target-operator ownership check. Any operator key can call it for any operator (including admins) and receive a fresh bearer.\n\n```\ncurl -X POST -H \"Authorization: Bearer <low-priv-key>\" \\\n https://server/api/v1/operators/<admin-id>/api-keys \\\n -H 'Content-Type: application/json' -d '{\"name\":\"oops\"}'\n# Returns: {\"key\":\"<32-byte admin bearer>\",\"entry\":{...}}\n```\n\nReuse the returned key for subsequent requests → full admin.\n\n### B) Cross-operator host takeover via reenroll\n`internal/api/hosts.go:321,330` → `mintEnrollmentTokenForHost`. Looks up host by URL param, mints a single-use enrollment token, returns it. No ownership check.\n\n```\ncurl -X POST -H \"Authorization: Bearer <low-priv-key>\" \\\n https://server/api/v1/hosts/<victim-host-id>/reenroll\n# Returns: {\"enrollment_token\":\"<uuid>\",...}\n```\n\nCaller POSTs `/api/v1/enroll` with their own X25519 + Ed25519 keypairs. `enroll.go:175` overwrites `signing_pub_pem`; `SaveCertificateAndEnrollHost` overwrites the cert. Legitimate agent's next signed poll fails `bad_signature`. Attacker now owns the victim's Nebula identity.\n\n### C) Cross-tenant CRUD on hosts, networks, firewall\nThe same gap applies across:\n- `/api/v1/hosts*` — create, list, get, update, delete, block, unblock\n- `/api/v1/networks*` — create, list, get\n- `/api/v1/networks/{id}/firewall` — get, PUT\n- `/api/v1/hosts/{id}/mobile-bundle` (already filed as public issue #119)\n\nAll trust bearer-auth alone. Any operator can read or mutate any other operator's resources.\n\n## Affected operator-management handlers (in addition to A)\nBeyond `handleCreateOperatorAPIKey` (covered by A), `internal/api/operators.go` is missing admin gates on:\n- `handleListOperators` (line 66) — operator roster info disclosure\n- `handleDisableOperator` (line 79) — DoS / sabotage\n- `handleEnableOperator` (line 94) — re-enable disabled operators\n- `handleRevokeOperatorAPIKey` (line 157) — invalidate any operator's API keys\n- `handleListOperatorAPIKeys` (line 173) — API-key metadata disclosure\n\n`handleCreateOperator` (line 26) IS properly gated (`actorIsAdmin` at line 27).\n\n## NOT affected (verified)\n`internal/api/cas.go` properly gates every CA endpoint via `canAccessCA` (calls at lines 70, 176, 216) and admin shortcuts at lines 39, 82. An earlier description draft mistakenly listed `/api/v1/cas/{id}/rotate` as affected — that endpoint is properly protected. CAs are not in this gap.\n\n## Impact\n- Any non-admin operator → admin via one curl (A).\n- Any non-admin operator → ownership of any victim's hosts with cert + identity transfer (B).\n- Mass cross-tenant CRUD including firewall-rule mutation (C).\n- Any operator → disable/enable other operators, revoke their API keys, enumerate the operator roster.\n\nCVSS 3.1: AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H — 9.6.\n\n## Suggested fix\nShared helpers in a new `internal/api/authz.go`, mirroring the Web layer's `loadAccessibleCA`:\n\n```go\nfunc (s *Server) requireAdmin(w http.ResponseWriter, r *http.Request) bool\nfunc (s *Server) requireOperatorAccess(w http.ResponseWriter, r *http.Request, operatorID string) bool\nfunc (s *Server) requireHostAccess(w http.ResponseWriter, r *http.Request, hostID string) (*models.Host, bool)\nfunc (s *Server) requireNetworkAccess(w http.ResponseWriter, r *http.Request, networkID string) (*models.Network, bool)\n```\n\nEach loads the resource, resolves its CA via `*.CAID`, accepts if `actorIsAdmin(ctx)` OR actor owns the CA. Reject `403 forbidden`; audit-log `api.<resource>.forbidden` with the reason.\n\nThe operator-management endpoints take `requireAdmin` instead (operator ownership doesn't map to CA ownership).\n\nApply at the top of every host-, network-, firewall-, mobile-bundle-touching API handler, plus the 5 operator endpoints listed above. The legacy config-key path retains admin (preserves backward compatibility); the broader legacy-fallback question is tracked separately as issue #121.\n\n## Test matrix\n- admin → all operations permitted\n- owning non-admin → operations on owned hosts/networks permitted\n- non-owner non-admin → 403 + audit entry\n- legacy config-key → preserved (admin)\n- unauthenticated → existing 401 from middleware\n\n## Coordinated context\nSubsumes public issue #119 (mobile-bundle authz). Issue #121 (actor.go:40 legacy-admin fallback) is a separate concern tracked independently.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Go",
21+
"name": "github.com/juev/nebula-mesh"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "0.3.4"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/juev/nebula-mesh/security/advisories/GHSA-598g-h2vc-h5vg"
42+
},
43+
{
44+
"type": "WEB",
45+
"url": "https://github.com/forgekeep/nebula-mesh/commit/9d8bcd7667ecd0c2975cc71fb35a02fe131f76f2"
46+
},
47+
{
48+
"type": "PACKAGE",
49+
"url": "https://github.com/juev/nebula-mesh"
50+
}
51+
],
52+
"database_specific": {
53+
"cwe_ids": [
54+
"CWE-862"
55+
],
56+
"severity": "CRITICAL",
57+
"github_reviewed": true,
58+
"github_reviewed_at": "2026-06-08T23:09:02Z",
59+
"nvd_published_at": null
60+
}
61+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-7hp6-g3pq-3pc3",
4+
"modified": "2026-06-08T23:08:15Z",
5+
"published": "2026-06-08T23:08:15Z",
6+
"aliases": [
7+
"CVE-2026-47722"
8+
],
9+
"summary": "nebula-mesh: Host advanced overrides allow YAML injection into agent config.yml",
10+
"details": "`internal/configgen/generator.go:86,108,119` interpolates the operator-supplied `ListenHost` and `TunDevice` fields raw into a `text/template` that produces the agent's `config.yml`. `internal/web/advanced.go:20-35` accepts both with only `strings.TrimSpace` — no character or shape validation.\n\n## Exploit\nAn operator (or attacker with any operator key, given the cross-tenant CRUD advisory) sets `adv_tun_device` to:\n\n```\nnebula0\nlighthouse:\n am_lighthouse: true\n hosts: [\"10.0.0.1\"]\n#\n```\n\nThe agent fetches the rendered config on its next signed poll. On config reload, it loads the injected YAML keys: the host self-promotes to lighthouse, attracts mesh traffic, or sets `am_relay: true` to be selected as a relay. The `ListenHost` field has the same shape.\n\n## Affected\nAll released versions prior to v0.3.2.\n\n## Threat model\n- **Today**: operator can compromise their own host's config (trivially allowed if they own the host, but they can also set lighthouse/relay flags that the operator-create form does NOT expose — privilege uplift within their own tenant).\n- **Combined with the critical /api/v1 authz advisory**: any operator key can mutate ANOTHER tenant's host overrides and inject YAML there.\n- **Post-fix of the authz advisory**: still relevant — the agent unconditionally trusts whatever config the server hands it, so any future operator-impersonation bug re-amplifies this.\n\n## Suggested fix\nTwo options, either acceptable:\n\n1. **Input validation** in `parseAdvancedFromForm` (`internal/web/advanced.go`):\n - `ListenHost`: regex `^[A-Za-z0-9.:\\[\\]_-]+$` (IPv4/IPv6/hostname)\n - `TunDevice`: regex `^[A-Za-z0-9_-]{1,15}$` (Linux IFNAMSIZ caps at 15)\n Reject invalid input with a form-level error; do not write to the host row.\n\n2. **Safer marshalling**: switch `configgen/generator.go` to marshal a typed Go struct via `gopkg.in/yaml.v3` (which escapes correctly) instead of `text/template` string-concat. Larger change, but eliminates this entire injection class.\n\nOption 2 is preferable long-term. Option 1 is the quick fix.\n\nThe `unsafe_routes` advanced field is already `netip.Parse{Prefix,Addr}`-validated at `enroll.go:226-233` — apply the same validation discipline to the other advanced fields.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V4",
14+
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Go",
21+
"name": "github.com/juev/nebula-mesh"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "0.3.2"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/juev/nebula-mesh/security/advisories/GHSA-7hp6-g3pq-3pc3"
42+
},
43+
{
44+
"type": "WEB",
45+
"url": "https://github.com/forgekeep/nebula-mesh/issues/126"
46+
},
47+
{
48+
"type": "WEB",
49+
"url": "https://github.com/forgekeep/nebula-mesh/commit/c1506f7344ab375a145a7449b193af3f19bb41ef"
50+
},
51+
{
52+
"type": "PACKAGE",
53+
"url": "https://github.com/juev/nebula-mesh"
54+
}
55+
],
56+
"database_specific": {
57+
"cwe_ids": [
58+
"CWE-94"
59+
],
60+
"severity": "HIGH",
61+
"github_reviewed": true,
62+
"github_reviewed_at": "2026-06-08T23:08:15Z",
63+
"nvd_published_at": null
64+
}
65+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-w7w5-5gcp-38rw",
4+
"modified": "2026-06-08T23:08:54Z",
5+
"published": "2026-06-08T23:08:54Z",
6+
"aliases": [
7+
"CVE-2026-47723"
8+
],
9+
"summary": "nebula-mesh: Web UI and API responses lack security headers (CSP, X-Frame-Options, HSTS, etc.)",
10+
"details": "None of the response paths in `internal/web/` or `internal/api/` set the standard browser-security headers. `grep` for `Content-Security-Policy`, `X-Frame-Options`, `Strict-Transport-Security`, `X-Content-Type-Options`, `Referrer-Policy` returns zero matches across the codebase.\n\n## Impact\nThe admin UI signs CA certificates, mints API keys (returned inline once per page), displays TOTP QR codes, and exposes operator-management forms. Missing `X-Frame-Options: DENY` / `frame-ancestors 'none'` is a real clickjacking lever against an admin browsing `/ui/operators/*` or `/ui/cas/*`. Missing `X-Content-Type-Options: nosniff` allows MIME confusion on any user-supplied content surface. Missing HSTS on TLS deployments leaves a downgrade window.\n\n## Affected\nAll released versions up to v0.3.0.\n\n## Suggested fix\nA single response-header middleware mounted at the chi router root in both `/ui/*` and `/api/*` paths:\n\n```go\nfunc securityHeadersMiddleware(next http.Handler) http.Handler {\n return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {\n h := rw.Header()\n h.Set(\"Content-Security-Policy\",\n \"default-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'\")\n h.Set(\"X-Content-Type-Options\", \"nosniff\")\n h.Set(\"Referrer-Policy\", \"same-origin\")\n h.Set(\"X-Frame-Options\", \"DENY\") // belt-and-braces; CSP frame-ancestors is the modern path\n if r.TLS != nil {\n h.Set(\"Strict-Transport-Security\", \"max-age=31536000; includeSubDomains\")\n }\n next.ServeHTTP(rw, r)\n })\n}\n```\n\nThe inline `<script>` in `layout.html` for CSRF wiring (added in the CSRF advisory) will need either a nonce, a hash in CSP, or external-file extraction. Easiest path: a nonce per request (`crypto/rand`, base64) injected into both the CSP header and the script's `nonce=\"\"` attribute.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V4",
14+
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:H/VA:N/SC:N/SI:N/SA:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Go",
21+
"name": "github.com/juev/nebula-mesh"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "0.3.1"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "<= 0.3.0"
38+
}
39+
}
40+
],
41+
"references": [
42+
{
43+
"type": "WEB",
44+
"url": "https://github.com/juev/nebula-mesh/security/advisories/GHSA-w7w5-5gcp-38rw"
45+
},
46+
{
47+
"type": "WEB",
48+
"url": "https://github.com/forgekeep/nebula-mesh/commit/b45fda5476c41ffcff1ca23058aef0fb851359c1"
49+
},
50+
{
51+
"type": "WEB",
52+
"url": "https://github.com/forgekeep/nebula-mesh/releases/tag/v0.3.1"
53+
},
54+
{
55+
"type": "PACKAGE",
56+
"url": "https://github.com/juev/nebula-mesh"
57+
}
58+
],
59+
"database_specific": {
60+
"cwe_ids": [
61+
"CWE-1021"
62+
],
63+
"severity": "HIGH",
64+
"github_reviewed": true,
65+
"github_reviewed_at": "2026-06-08T23:08:54Z",
66+
"nvd_published_at": null
67+
}
68+
}

0 commit comments

Comments
 (0)