79 行
5.6 KiB
JSON
79 行
5.6 KiB
JSON
{
|
|
"canonical_id": "astro--CVE-2025-65019",
|
|
"system_id": "astro",
|
|
"display_name": "Astro",
|
|
"category": "frameworks",
|
|
"advisory_mode": "core",
|
|
"title": "Astro Cloudflare adapter has Stored Cross-site Scripting vulnerability in /_image endpoint",
|
|
"summary": "**Summary** \nA Cross-Site Scripting (XSS) vulnerability exists in Astro when using the **@astrojs/cloudflare** adapter with `output: 'server'`. The built-in image optimization endpoint (`/_image`) uses `isRemoteAllowed()` from Astro\u2019s internal helpers, which **unconditionally allows `data:` URLs**. When the endpoint receives a valid `data:` URL pointing to a malicious SVG containing JavaScript, and the Cloudflare-specific implementation performs a **302 redirect back to the original `data:` URL**, the browser directly executes the embedded JavaScript. This completely bypasses any domain allow-listing (`image.domains` / `image.remotePatterns`) and typical Content Security Policy mitigations.\n\n**Affected Versions** \n- `@astrojs/cloudflare` \u2264 12.6.10 (and likely all previous versions) \n- Astro \u2265 4.x when used with `output: 'server'` and the Cloudflare adapter\n\n**Root Cause \u2013 Vulnerable Code** \nFile: `node_modules/@astrojs/internal-helpers/src/remote.ts`\n\n```ts\nexport function isRemoteAllowed(src: string, ...): boolean {\n if (!URL.canParse(src)) {\n return false;\n }\n const url = new URL(src);\n\n // Data URLs are always allowed \n if (url.protocol === 'data:') {\n return true;\n }\n\n // Non-http(s) protocols are never allowed\n if (!['http:', 'https:'].includes(url.protocol)) {\n return false;\n }\n // ... further http/https allow-list checks\n}\n```\n\nIn the **Cloudflare adapter**, the `/_image` endpoint contains logic similar to:\n\n```ts\n\tconst href = ctx.url.searchParams.get('href');\n\tif (!href) {\n\t\t// return error \n\t}\n\n\tif (isRemotePath(href)) {\n\t\tif (isRemoteAllowed(href, imageConfig) === false) {\n\t\t\t// return error\n\t\t} else {\n //redirect to return the image \n\t\t\treturn Response.redirect(href, 302);\n\t\t}\n\t}\n```\n\nBecause `data:` URLs are considered \u201callowed\u201d, a request such as: \n`https://example.com/_image?href=data:image/svg+xml;base64,PHN2Zy... (base64-encoded malicious SVG)` \n\ntriggers a **302 redirect directly to the `data:` URL**, causing the browser to render and execute the malicious JavaScript inside the SVG.\n\n**Proof of Concept (PoC)** \n\n1. Create a minimal Astro project with Cloudflare adapter (`output: 'server'`).\n2. Deploy to Cloudflare Pages or Workers.\n3. Request the image endpoint with the following payload:\n\n```\nhttps://yoursite.com/_image?href=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxzY3JpcHQ+YWxlcnQoJ3pvbWFzZWMnKTwvc2NyaXB0Pjwvc3ZnPg==\n```\n\n (Base64 decodes to: `<svg xmlns=\"http://www.w3.org/2000/svg\"><script>alert('zomasec')</script></svg>`)\n\n4. The endpoint returns a **302 redirect** to the `data:` URL \u2192 browser executes the `<script>` \u2192 `alert()` fires.\n\n**Impact** \n- Reflected/Strored XSS (depending on application usage) \n- Session hijacking (access to cookies, localStorage, etc.) \n- Account takeover when combined with CSRF \n- Data exfiltration to attacker-controlled servers \n- Bypasses `image.domains` / `image.remotePatterns` configuration entirely \n\n**Safe vs Vulnerable Behavior** \nOther Astro adapters (Node, Vercel, etc.) typically **proxy and rasterize** SVGs, stripping JavaScript. The **Cloudflare adapter** currently **redirects** to remote resources (including `data:` URLs), making it uniquely vulnerable.\n\n**References** \n- Vulnerable function: https://github.com/withastro/astro/blob/main/packages/internal-helpers/src/remote.ts \n- Similar `data:` URL bypass in WordPress: [CVE-2025-2575 ](https://feedly.com/cve/CVE-2025-2575)",
|
|
"published_at": "2025-11-19T20:09:12Z",
|
|
"updated_at": "2025-11-27T08:33:26.119485Z",
|
|
"severity": "low",
|
|
"cvss_score": 3.1,
|
|
"exploit_status": "unknown",
|
|
"source_confidence": "official",
|
|
"official_source_url": "https://github.com/withastro/astro/security/advisories/GHSA-fvmw-cj7j-j39q",
|
|
"secondary_source_urls": [
|
|
"https://nvd.nist.gov/vuln/detail/CVE-2025-65019",
|
|
"https://github.com/withastro/astro/commit/9e9c528191b6f5e06db9daf6ad26b8f68016e533",
|
|
"https://github.com/withastro/astro"
|
|
],
|
|
"aliases": [
|
|
"CVE-2025-65019",
|
|
"GHSA-fvmw-cj7j-j39q"
|
|
],
|
|
"cve_ids": [
|
|
"CVE-2025-65019"
|
|
],
|
|
"ghsa_ids": [
|
|
"GHSA-fvmw-cj7j-j39q"
|
|
],
|
|
"osv_ids": [
|
|
"GHSA-fvmw-cj7j-j39q"
|
|
],
|
|
"affected_versions": [
|
|
"introduced=0, fixed<5.15.9"
|
|
],
|
|
"fixed_versions": [
|
|
"5.15.9"
|
|
],
|
|
"package_name": "astro",
|
|
"render_markdown": true,
|
|
"case_path": "07-framework-security/frameworks/astro/cases/astro-cve-2025-65019.md",
|
|
"secure_code_topics": [
|
|
"authz-server-side-recheck",
|
|
"csp-trusted-types",
|
|
"xss-output-encoding",
|
|
"token-cookie-storage",
|
|
"plugin-extension-trust-policy",
|
|
"dependency-upgrade-policy",
|
|
"proxy-trust-boundary"
|
|
],
|
|
"status": "generated",
|
|
"triage_reasons": [],
|
|
"verification_status": "triage-manual",
|
|
"verification_mode": "synthetic",
|
|
"last_verified_at": null,
|
|
"last_run_id": null,
|
|
"evidence_bundle": null,
|
|
"historical_status": null,
|
|
"latest_status": null,
|
|
"browser_evidence": {
|
|
"required": false,
|
|
"present": false,
|
|
"refs": []
|
|
},
|
|
"repro_profile_id": "xss-generic",
|
|
"artifact_mode": "synthetic",
|
|
"blocked_reason": null,
|
|
"metadata": {
|
|
"source_names": [
|
|
"OSV Astro"
|
|
],
|
|
"source_kinds": [
|
|
"osv-batch"
|
|
],
|
|
"candidate_count": 1
|
|
}
|
|
}
|