CVE-2026-53607: CWE-918: Server-Side Request Forgery (SSRF) in apostrophecms apostrophe
### Summary When `prettyUrls: true` is enabled on `@apostrophecms/file` (a documented SEO feature for serving uploaded files at clean URLs), the public pretty-URL handler builds the upstream URL using the raw `Host` HTTP request header: ```js proxyUrl = `${req.protocol}://${req.get('host')}${uglyUrl}` ``` That URL is then `fetch`'ed and the response body + headers are streamed straight back to the requester. Because `Host` is fully attacker-controlled, an **unauthenticated remote** attacker can pivot the apostrophe process to issue outbound HTTP requests against any host it can reach on the private network. The path component is constrained to `/uploads/attachments/<cuid>-<slug>.<ext>` (built from a local-DB lookup), which keeps the impact narrow: cross-instance data exfiltration is neutralised by cuid uniqueness, but blind-SSRF residuals remain (network-topology mapping via response-code / timing differences and verbose proxy/WAF 404 body disclosure). Verified on `[email protected]` (latest); no fixed release exists. - **Affected:** `apostrophe <= 4.30.0` when `@apostrophecms/file` is configured with `prettyUrls: true` and uploadfs is **local** (the default; S3/CDN deployments produce an absolute `uglyUrl` and are not affected). ### Details `modules/@apostrophecms/file/index.js` (excerpt; the public GET route registered when `prettyUrls: true`): ```js if (!self.options.prettyUrls) return; return { get: { async [`${self.options.prettyUrlDir}/*`](req, res) { const matches = (req.params[0] || '').match(/^([^.]+)\.\w+$/); if (!matches) return res.status(400).send('invalid'); const [ , slug ] = matches; if (slug.includes('..') || slug.includes('/')) { return res.status(403).send('forbidden'); } const file = await self.find(req, { slug: `${self.options.slugPrefix}${slug}` }).toObject(); if (!file) return res.status(404).send('not found'); const uglyUrl = self.apos.attachment.url(file.attachment, { prettyUrl: false }); const proxyUrl = uglyUrl.startsWith('/') ? `${req.protocol}://${req.get('host')}${uglyUrl}` // <-- sink : uglyUrl; return await streamProxy(req, proxyUrl, { error: self.apos.util.error }); } } }; ``` `lib/stream-proxy.js` (excerpt): ```js module.exports = async function(req, url, { error }) { const res = req.res; if (url.startsWith('/')) url = `${req.baseUrl}${url}`; let response; try { response = await fetch(url); } // <-- attacker-steered fetch catch (e) { return send502(e); } for (const header of ['content-type','etag','last-modified','content-disposition','cache-control']) { const v = response.headers.get(header); if (v != null) res.header(header, v); } res.status(response.status); response.body.pipeTo(new WritableStream({ write(c){ res.write(c) }, close(){ res.end() }, ... })); }; ``` `req.get('host')` returns the unvalidated `Host` HTTP header from the request. Express does not validate or restrict it, and apostrophe does not check the constructed `proxyUrl` against an allowlist. The upstream's body and content-type are forwarded verbatim — so any response the targeted host does return at the constrained path will reach the attacker. In practice the path constraint (`/uploads/attachments/<cuid>-<slug>.<ext>`) and cuid uniqueness mean meaningful body exfiltration only occurs against verbose-404 / banner- leaky proxies; against most internal services this degenerates to blind SSRF (response-code + timing side channels). Prerequisites are minimal: `prettyUrls: true` (a documented production SEO option) + at least one file uploaded with a known slug. Slugs are publicly enumerable in normal CMS use (file URLs appear in page content). **Distinct from the only published apostrophe SSRF advisory, GHSA-pr28-mf3q-qpg6** ("Authenticated SSRF in rich-text widget import via @apostrophecms/area validate-widget"), which is authenticated and lives in a completely different module/route. This finding is unauthenticated, in `@apostrophecms/file`, via the `Host` header. ### PoC Three services on an isolated Docker network: `mongo`, `internal` (returns a fake secret, **never exposed to the host**), `apos:3000` (the only port the host can reach). The host attacker proves it cannot reach `internal` directly, then exfiltrates `internal`'s response via one crafted request to `apos`. `app.js` (normal apostrophe site, documented option only): ```js require('apostrophe')({ shortName: 'apos-ssrf-poc', autoBuild: false, modules: { '@apostrophecms/express': { options: { session: { secret: 'x' }, port: 3000 } }, '@apostrophecms/db': { options: { uri: process.env.APOS_MONGODB_URI } }, '@apostrophecms/asset': { options: { autoBuild: false, publicBundle: false, watch: false, hmr: false } }, '@apostrophecms/file': { options: { prettyUrls: true, prettyUrlDir: '/files' } }, 'poc-seed': {} // seeds one file doc on boot (= what an admin does
AI Analysis
Technical Summary
The vulnerability (CVE-2026-53607) arises from the public pretty-URL handler in @apostrophecms/file when prettyUrls is enabled. The handler constructs an upstream URL using the raw Host HTTP header from the request without validation, then fetches the resource and streams the response back to the client. Since the Host header is attacker-controlled, an unauthenticated attacker can induce the server to make HTTP requests to internal network hosts at paths constrained to /uploads/attachments/<cuid>-<slug>.<ext>. The uniqueness of cuid limits meaningful data exfiltration, but blind SSRF remains possible through response codes and timing. This affects apostrophe versions up to 4.30.0 with local uploadfs; S3/CDN deployments are not affected. No fixed version or patch is currently available.
Potential Impact
An unauthenticated remote attacker can exploit this SSRF vulnerability to make the apostrophe server issue HTTP requests to internal network hosts reachable by the server. Although the path is constrained and limits direct data exfiltration, attackers can perform network topology mapping and blind SSRF attacks using response timing and status codes. The impact is limited to information disclosure and reconnaissance; no direct code execution or data modification is indicated. The CVSS score is 3.7 (low) reflecting limited confidentiality impact and high attack complexity.
Mitigation Recommendations
No official fix or patch release is currently available for this vulnerability. Administrators should consider disabling the prettyUrls option in @apostrophecms/file or avoid using local uploadfs storage until a fix is released. Monitor the vendor advisory for updates. Since this vulnerability requires prettyUrls enabled and local storage, changing these configurations mitigates the risk. Avoid exposing the apostrophe server to untrusted networks where attackers can send arbitrary Host headers.
CVE-2026-53607: CWE-918: Server-Side Request Forgery (SSRF) in apostrophecms apostrophe
Description
### Summary When `prettyUrls: true` is enabled on `@apostrophecms/file` (a documented SEO feature for serving uploaded files at clean URLs), the public pretty-URL handler builds the upstream URL using the raw `Host` HTTP request header: ```js proxyUrl = `${req.protocol}://${req.get('host')}${uglyUrl}` ``` That URL is then `fetch`'ed and the response body + headers are streamed straight back to the requester. Because `Host` is fully attacker-controlled, an **unauthenticated remote** attacker can pivot the apostrophe process to issue outbound HTTP requests against any host it can reach on the private network. The path component is constrained to `/uploads/attachments/<cuid>-<slug>.<ext>` (built from a local-DB lookup), which keeps the impact narrow: cross-instance data exfiltration is neutralised by cuid uniqueness, but blind-SSRF residuals remain (network-topology mapping via response-code / timing differences and verbose proxy/WAF 404 body disclosure). Verified on `[email protected]` (latest); no fixed release exists. - **Affected:** `apostrophe <= 4.30.0` when `@apostrophecms/file` is configured with `prettyUrls: true` and uploadfs is **local** (the default; S3/CDN deployments produce an absolute `uglyUrl` and are not affected). ### Details `modules/@apostrophecms/file/index.js` (excerpt; the public GET route registered when `prettyUrls: true`): ```js if (!self.options.prettyUrls) return; return { get: { async [`${self.options.prettyUrlDir}/*`](req, res) { const matches = (req.params[0] || '').match(/^([^.]+)\.\w+$/); if (!matches) return res.status(400).send('invalid'); const [ , slug ] = matches; if (slug.includes('..') || slug.includes('/')) { return res.status(403).send('forbidden'); } const file = await self.find(req, { slug: `${self.options.slugPrefix}${slug}` }).toObject(); if (!file) return res.status(404).send('not found'); const uglyUrl = self.apos.attachment.url(file.attachment, { prettyUrl: false }); const proxyUrl = uglyUrl.startsWith('/') ? `${req.protocol}://${req.get('host')}${uglyUrl}` // <-- sink : uglyUrl; return await streamProxy(req, proxyUrl, { error: self.apos.util.error }); } } }; ``` `lib/stream-proxy.js` (excerpt): ```js module.exports = async function(req, url, { error }) { const res = req.res; if (url.startsWith('/')) url = `${req.baseUrl}${url}`; let response; try { response = await fetch(url); } // <-- attacker-steered fetch catch (e) { return send502(e); } for (const header of ['content-type','etag','last-modified','content-disposition','cache-control']) { const v = response.headers.get(header); if (v != null) res.header(header, v); } res.status(response.status); response.body.pipeTo(new WritableStream({ write(c){ res.write(c) }, close(){ res.end() }, ... })); }; ``` `req.get('host')` returns the unvalidated `Host` HTTP header from the request. Express does not validate or restrict it, and apostrophe does not check the constructed `proxyUrl` against an allowlist. The upstream's body and content-type are forwarded verbatim — so any response the targeted host does return at the constrained path will reach the attacker. In practice the path constraint (`/uploads/attachments/<cuid>-<slug>.<ext>`) and cuid uniqueness mean meaningful body exfiltration only occurs against verbose-404 / banner- leaky proxies; against most internal services this degenerates to blind SSRF (response-code + timing side channels). Prerequisites are minimal: `prettyUrls: true` (a documented production SEO option) + at least one file uploaded with a known slug. Slugs are publicly enumerable in normal CMS use (file URLs appear in page content). **Distinct from the only published apostrophe SSRF advisory, GHSA-pr28-mf3q-qpg6** ("Authenticated SSRF in rich-text widget import via @apostrophecms/area validate-widget"), which is authenticated and lives in a completely different module/route. This finding is unauthenticated, in `@apostrophecms/file`, via the `Host` header. ### PoC Three services on an isolated Docker network: `mongo`, `internal` (returns a fake secret, **never exposed to the host**), `apos:3000` (the only port the host can reach). The host attacker proves it cannot reach `internal` directly, then exfiltrates `internal`'s response via one crafted request to `apos`. `app.js` (normal apostrophe site, documented option only): ```js require('apostrophe')({ shortName: 'apos-ssrf-poc', autoBuild: false, modules: { '@apostrophecms/express': { options: { session: { secret: 'x' }, port: 3000 } }, '@apostrophecms/db': { options: { uri: process.env.APOS_MONGODB_URI } }, '@apostrophecms/asset': { options: { autoBuild: false, publicBundle: false, watch: false, hmr: false } }, '@apostrophecms/file': { options: { prettyUrls: true, prettyUrlDir: '/files' } }, 'poc-seed': {} // seeds one file doc on boot (= what an admin does
CVSS v3.1
Score 3.7low
Affected software
apostrophecms
apostrophe
Weaknesses
AI-Powered Analysis
Machine-generated threat intelligence
Technical Analysis
The vulnerability (CVE-2026-53607) arises from the public pretty-URL handler in @apostrophecms/file when prettyUrls is enabled. The handler constructs an upstream URL using the raw Host HTTP header from the request without validation, then fetches the resource and streams the response back to the client. Since the Host header is attacker-controlled, an unauthenticated attacker can induce the server to make HTTP requests to internal network hosts at paths constrained to /uploads/attachments/<cuid>-<slug>.<ext>. The uniqueness of cuid limits meaningful data exfiltration, but blind SSRF remains possible through response codes and timing. This affects apostrophe versions up to 4.30.0 with local uploadfs; S3/CDN deployments are not affected. No fixed version or patch is currently available.
Potential Impact
An unauthenticated remote attacker can exploit this SSRF vulnerability to make the apostrophe server issue HTTP requests to internal network hosts reachable by the server. Although the path is constrained and limits direct data exfiltration, attackers can perform network topology mapping and blind SSRF attacks using response timing and status codes. The impact is limited to information disclosure and reconnaissance; no direct code execution or data modification is indicated. The CVSS score is 3.7 (low) reflecting limited confidentiality impact and high attack complexity.
Mitigation Recommendations
No official fix or patch release is currently available for this vulnerability. Administrators should consider disabling the prettyUrls option in @apostrophecms/file or avoid using local uploadfs storage until a fix is released. Monitor the vendor advisory for updates. Since this vulnerability requires prettyUrls enabled and local storage, changing these configurations mitigates the risk. Avoid exposing the apostrophe server to untrusted networks where attackers can send arbitrary Host headers.
Technical Details
- Data Version
- 5.2
- Assigner Short Name
- GitHub_M
- Date Reserved
- 2026-06-09T19:39:52.404Z
- Cvss Version
- 3.1
- State
- PUBLISHED
Threat ID: 6a2c758ce617e2d834c30b83
Added to database: 06/12/2026, 21:09:32 UTC
Last enriched: 08/01/2026, 21:29:19 UTC
Last updated: 09/13/2026, 22:01:35 UTC
Views: 120
Community Reviews
0 reviewsCrowdsource mitigation strategies, share intel context, and vote on the most helpful responses. Sign in to add your voice and help keep defenders ahead.
Want to contribute mitigation steps or threat intel context? Sign in or create an account to join the community discussion.
Actions
Updates to AI analysis require Pro Console access. Upgrade inside Console → Billing.
External Links
Need more coverage?
Upgrade to Pro Console for AI refresh and higher limits.
For incident response and remediation, OffSeq services can help resolve threats faster.
Latest Threats
Check if your credentials are on the dark web
Instant breach scanning across billions of leaked records. Free tier available.