Aug 1, 2026·8 min read·16 visits
Unauthenticated remote attackers can exploit a Host header injection flaw in ApostropheCMS to perform Server-Side Request Forgery (SSRF), mapping internal networks and scanning ports behind firewalls.
An unauthenticated Server-Side Request Forgery (SSRF) vulnerability exists in ApostropheCMS versions up to and including 4.30.0. When the prettyUrls option is enabled in the @apostrophecms/file module, the server constructs internal self-requests using the client-provided HTTP Host header, allowing remote attackers to coerce the server into initiating outbound requests to arbitrary internal or external hosts.
ApostropheCMS is a Node.js-based content management system that relies on a modular architecture. The @apostrophecms/file module manages user uploads and asset hosting. By default, assets uploaded to the system are managed via uploadfs, which can store files either locally on the server's file system or remotely using an object storage provider like Amazon S3. To optimize Search Engine Optimization (SEO), ApostropheCMS includes a prettyUrls feature. When enabled, this feature presents files via clean, human-readable URLs instead of raw path patterns.
The implementation of the clean URL handler registers a public-facing route to process asset requests. If the files are hosted locally, the server must act as a reverse proxy to stream the file contents from the local file system to the remote client. This mechanism exposes an attack surface because the application needs to determine its own network address to fetch the raw file internally.
This analysis examines CVE-2026-53607, an unauthenticated Server-Side Request Forgery (SSRF) vulnerability. The flaw allows external, unauthenticated attackers to manipulate the host resolution step of this proxy process. By supplying a modified HTTP Host header, attackers can force the server to execute outbound HTTP connections to arbitrary addresses.
The exposure is exacerbated in environments where the web server operates inside a trusted local network. Because the request originates from the application's own context, it bypasses firewall perimeters that restrict incoming traffic from the internet. This provides an entry point for internal network scanning.
The root cause of CVE-2026-53607 lies in the insecure handling of the HTTP Host header during internal URL resolution. Under RFC 7230, the Host header is an mandatory field that specifies the target host of the request. Since the Host header is supplied entirely by the client, it must always be treated as untrusted input. The @apostrophecms/file module, however, relied on req.get('host') to reconstruct the server's own origin when resolving relative asset paths.
When the application processes a request for a clean URL, it checks if the underlying asset path starts with a forward slash, which indicates a local relative path. To perform the internal proxy fetch, the server attempts to convert this relative path into an absolute URL. The vulnerable code constructs this URL by directly interpolating the client-provided protocol and Host header.
By modifying the Host header in the incoming HTTP request, an attacker controls the destination authority of the absolute URL. While the path portion of the URL is strictly appended using the static file name retrieved from the database, the target host, port, and scheme are determined entirely by the attacker's payload. This allows an attacker to pivot requests to other network devices.
This design represents a fundamental failure in input boundary validation. Instead of resolving relative URLs against a trusted, hardcoded configuration parameter, the codebase dynamically trusted the header provided in the current TCP session. This is a classic pattern of Host Header Injection leading directly to Server-Side Request Forgery.
The following code block highlights the vulnerable implementation within packages/apostrophe/modules/@apostrophecms/file/index.js:
// VULNERABLE CODE
const uglyUrl = self.apos.attachment.url(file.attachment, {
prettyUrl: false
});
// For relative URLs (local uploadfs, not CDN), resolve
// against the current server's origin so the proxy can
// make the self-request.
const proxyUrl = uglyUrl.startsWith('/')
? `${req.protocol}://${req.get('host')}${uglyUrl}`
: uglyUrl;
return await streamProxy(req, proxyUrl, { error: self.apos.util.error });In this implementation, req.get('host') pulls the Host header value directly from the incoming request. If an attacker passes 169.254.169.254 as the Host, proxyUrl becomes http://169.254.169.254/uploads/attachments/... and is passed directly to the streamProxy utility.
To resolve this vulnerability, the vendor refactored the route handler to eliminate the unsafe string interpolation. The patched code in commit 5a88e9630cbbdde33154ef8abe7557ddf7be418b implements the following changes:
// PATCHED CODE
const uglyUrl = self.apos.attachment.url(file.attachment, {
prettyUrl: false
});
// Relative URLs are now passed directly without Host header reconstruction
return await streamProxy(req, uglyUrl, { error: self.apos.util.error });Under this updated architecture, the relative uglyUrl is passed directly to streamProxy. The streamProxy utility is responsible for safely resolving the relative path against req.baseUrl. This variable refers to a trusted, statically configured origin configured by the system administrator, falling back to the request host only when no secure default exists. This ensures that arbitrary Host header spoofing does not influence outbound socket routing.
To exploit CVE-2026-53607, an attacker must satisfy specific requirements. The target server must be running an affected version of ApostropheCMS, have local file storage active, and have the prettyUrls option explicitly enabled. The attacker does not need any authentication credentials or session tokens to trigger the vulnerability.
The attack begins by locating an existing clean asset path. Because asset paths are public, an attacker can simply browse the target website to find a valid image or file path. The attacker then crafts an HTTP GET request to this path but overrides the standard Host header. For example, to target an internal HTTP server running on 10.0.0.5 at port 8080, the attacker sets the Host header to 10.0.0.5:8080:
GET /uploads/attachments/clxxxxxxx-some-file-slug.jpg HTTP/1.1
Host: 10.0.0.5:8080
User-Agent: Mozilla/5.0
Accept: */*
Connection: closeWhen the request is processed, the ApostropheCMS server makes an outbound connection to http://10.0.0.5:8080/uploads/attachments/clxxxxxxx-some-file-slug.jpg. The internal server at 10.0.0.5 receives this request. If the target server is active, it will respond. Even if the target path does not exist on the internal server, the response timing, HTTP status codes, and connection behaviors are sent back to the attacker.
This behavior allows an attacker to conduct internal port scanning and host discovery. A rapid connection refusal (TCP RST) indicates a closed port on an active host. A connection timeout indicates an inactive host or a firewall rule. A structured response indicates an active service. This reconnaissance allows an attacker to map out internal network infrastructure behind firewalls.
The impact of this SSRF is restricted by the static suffix path. Because the outbound request always appends /uploads/attachments/<filename>, the attacker cannot easily execute arbitrary REST API calls or exploit complex vulnerabilities on internal services that require specific path prefixes. However, significant security risks remain in modern environments.
In cloud environments like Amazon Web Services (AWS) or Google Cloud Platform (GCP), local metadata endpoints (such as 169.254.169.254) expose sensitive information. Under AWS IMDSv1, a GET request to the metadata service can expose host credentials and configuration data. While the hardcoded suffix limits direct access to /latest/meta-data/, vulnerabilities can still arise if internal routing or local DNS servers resolve the path to specific assets.
Furthermore, the SSRF can be used to target internal web applications, databases, and microservices that do not require authentication for local network traffic. The disclosure of headers, response bodies, or timing data can leak proprietary structural details. The CVSS v3.1 score of 3.7 reflects the high complexity due to configuration prerequisites and path restrictions, but the issue remains a serious vector for internal network exposure.
Additionally, the outbound request can be used to perform Distributed Denial of Service (DDoS) reflection attacks. An attacker can leverage multiple vulnerable ApostropheCMS instances to proxy high-volume requests to a targeted external target, hiding the attacker's true origin behind trusted web application servers.
Remediation of CVE-2026-53607 requires upgrading ApostropheCMS to version 4.31.0 or higher. This update removes the insecure string interpolation of the Host header and uses structured base URL resolution. Developers should audit their configurations to ensure that the baseUrl parameter is defined in their environment settings, providing a hardcoded origin for all internal routing tasks.
If an immediate upgrade is not possible, developers must apply temporary workarounds. The most effective workaround is to disable the prettyUrls option in the @apostrophecms/file module configuration. Setting this option to false removes the vulnerable route registration entirely. This change will return asset URLs to their default format but will eliminate the attack vector.
// Disabling prettyUrls configuration workaround
modules: {
'@apostrophecms/file': {
options: {
prettyUrls: false
}
}
}At the network level, security teams should implement egress filtering rules on the application servers. The application hosting ApostropheCMS should be prevented from establishing outbound connections to the local loopback interface, private subnet ranges, and the link-local metadata address 169.254.169.254. These firewall controls prevent SSRF payloads from successfully reaching internal targets.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
apostrophe ApostropheCMS | <= 4.30.0 | 4.31.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-918 |
| Attack Vector | Network (AV:N) |
| CVSS Score | 3.7 |
| Exploit Status | Proof-of-Concept |
| CISA KEV Status | Not Listed |
| EPSS Score | 0.00226 |
The web server receives the address of an retrieve-from resource from an untrusted source and retrieves it without validation.
Vikunja versions 2.3.0 through 2.6.0 contain an insufficient session expiration vulnerability (CWE-613) within the WebSocket authentication handler. Although Vikunja enforces server-side session tracking and revocation for REST API routes, the WebSocket handshake handler validates cryptographic JWT signatures without querying the database session state. Consequently, revoked JWT tokens can establish new real-time WebSocket connections, and existing connections persist after session revocation.
A cross-tenant boundary breach vulnerability in Vikunja allows an authenticated user to trigger global task position recalculations across all tenant instances by creating a saved filter with an empty filter string payload.
An access revocation flaw in Vikunja allows removed collaborators to retain outbound webhooks and link shares created prior to revocation, enabling persistent exfiltration of sensitive task data.
Vikunja v2.6.0 contains a permission inheritance regression in pkg/models/project_access.go where explicit down-restrictions on sub-projects are overridden by higher parent project permissions due to MAX aggregation across project tree nodes.
An authorization bypass vulnerability in Vikunja's link share deletion handlers allows project members with Write privileges to delete Admin-tier link shares. The handler passes an unpopulated struct to the authorization check, causing the permission evaluation to fall back to default Write permissions instead of requiring Admin privileges.
Vikunja versions 2.2.0 through 2.6.0 contain a Cross-Origin Resource Sharing (CORS) misconfiguration flaw in `code.vikunja.io/api`. Default configurations permit wildcard origins for localhost (`http://127.0.0.1:*` and `http://localhost:*`) with credentialed requests (`Access-Control-Allow-Credentials: true`). Because configuring a public service URL appends to this default list rather than overriding it, production environments inadvertently trust all local origins. A local page or application on a user's machine can execute a credentialed cross-origin request to the token refresh endpoint, extract the returned JWT access token, and achieve complete account takeover.