Aug 5, 2026·7 min read·20 visits
A DNS Rebinding vulnerability in Ghost CMS allows attackers to bypass private IP blocklists and execute SSRF requests against local networks due to a TOCTOU race condition between request validation and socket establishment.
Ghost CMS is vulnerable to Server-Side Request Forgery (SSRF) in versions 6.0.9 through 6.21.1. Due to a Time-of-Check to Time-of-Use (TOCTOU) race condition in its outbound fetch validation logic, an attacker can bypass IP blocklists via DNS Rebinding. This allows unauthorized interaction with private networks and local services.
Ghost CMS utilizes an external HTTP client wrapper helper called externalRequest to handle outbound calls. These outbound calls support features such as webhook dispatching, Unsplash media imports, and external integration integrations. To prevent Server-Side Request Forgery (SSRF) attacks, the application implements restrictions to block outbound connections targeting private IP blocks, local subnets, and cloud instance metadata services.
In vulnerable versions of Ghost CMS (ranging from 6.0.9 up to but not including 6.21.1), these filters were implemented within high-level pre-request hooks provided by the got library. Specifically, these hooks executed DNS lookup operations on target hostnames to verify that the resolved IP addresses did not belong to private networks. If a domain was verified as public, the HTTP client proceeded to execute the connection.
This architecture introduced a classic Time-of-Check to Time-of-Use (TOCTOU) vulnerability. Because the validation check and the actual TCP socket connection occurred as separate sequential events, the target domain IP address could change between the two operations. Attackers could exploit this gap using DNS Rebinding techniques to force Ghost to connect to restricted network interfaces.
The root cause of this vulnerability lies in the separation of DNS resolution during the security validation phase and the socket connection phase. Ghost's hook-based validation relied on errorIfHostnameResolvesToPrivateIp executing dns.lookup before allowing the client library to proceed. However, the got client does not lock or persist the resolved IP address for the downstream network call handled by Node's native HTTP module.
Under normal circumstances, the operating system caches DNS responses, making the second resolution identical to the first. When an attacker sets up a custom domain name pointed to an authoritative nameserver under their control, they can configure the nameserver to return a Time-To-Live (TTL) value of zero. This instructs downstream DNS caches to discard the resolution records immediately after use.
When Ghost resolves the domain during the validation hook phase, the attacker's server responds with a benign public IP address (such as 8.8.8.8). The hook accepts this IP and validates the request. Because the TTL is zero, the subsequent connection setup inside Node's native networking layer must perform another DNS query. On this second lookup, the attacker's nameserver returns a loopback or private IP address (such as 127.0.0.1 or 169.254.169.254). Node's native socket connection module establishes a TCP channel directly to the private target, bypassing all security logic.
The execution flow of a successful DNS rebinding exploit follows a synchronized lifecycle. The application first performs validation checks on a hostname that resolves to a safe public IP address. Immediately after passing the validation check, the application establishes a socket connection to a newly resolved private target address.
This diagram demonstrates how the two lookup actions function independently. The security hook runs in the user-space runtime environment, while the final connection resolution occurs inside Node's networking layer. The separation of these operations allows the validation boundaries to be bypassed.
The vulnerable code path utilized high-level validation hooks that ran asynchronously before the connection phase. This meant that the application had no control over the IP address selected when the native runtime performed the socket connection. To address this, the patch introduced the installSafeDnsLookup configuration helper to intercept DNS queries directly at the runtime's execution layer.
function installSafeDnsLookup(options) {
if (config.get('env') === 'development') {
return;
}
const siteUrl = new URL(config.get('url'));
if (options.url.host === siteUrl.host) {
return;
}
const requestHref = options.url.href;
// Injecting options.lookup forces validation during socket creation
options.lookup = (hostname, dnsOpts, callback) => {
if (typeof dnsOpts === 'function') {
callback = dnsOpts;
dnsOpts = {};
}
dns.lookup(hostname, dnsOpts, (err, addressOrResult, family) => {
if (err) {
return callback(err, addressOrResult, family);
}
if (dnsOpts && dnsOpts.all) {
const results = addressOrResult;
for (const entry of results) {
if (isPrivateIp(entry.address)) {
return callback(new errors.InternalServerError({
message: 'URL resolves to a non-permitted private IP block',
code: 'URL_PRIVATE_INVALID',
context: requestHref
}));
}
}
return callback(null, results);
}
if (isPrivateIp(addressOrResult)) {
return callback(new errors.InternalServerError({
message: 'URL resolves to a non-permitted private IP block',
code: 'URL_PRIVATE_INVALID',
context: requestHref
}));
}
callback(null, addressOrResult, family);
});
};
}By injecting the custom options.lookup handler directly into Node's internal http.request() configurations, the patch enforces validation at the lowest connection level. Because Node uses the IP address returned by this specific lookup callback to open the TCP socket, the IP validated inside isPrivateIp is identical to the one targeted for the connection, closing the TOCTOU gap.
Review of this fix reveals that the protection is bypassed when config.get('env') === 'development'. Production instances incorrectly configured to execute in development mode remain vulnerable. Additionally, connections targeting the host matching the configured site URL bypass this protection entirely, which could introduce risks if host headers can be manipulated.
To exploit this vulnerability, an attacker must register a domain name and deploy a custom authoritative DNS server. The DNS server must dynamically return different IP addresses for the target domain depending on the query index. When a lookup query is made, the DNS server alternates between a public IP address and an internal IP address.
Once the custom DNS infrastructure is operational, the attacker triggers an action in Ghost CMS that initiates an outbound request. This can be achieved by submitting the custom domain to a integration endpoint, adding custom webhooks, or utilizing the Unsplash media import interface. Ghost initiates the validation hook, which query the domain and receives the safe public IP, allowing the request to proceed.
Immediately following validation, Node's network module executes the second DNS query to open the connection. The DNS server responds with the private IP block (such as 169.254.169.254 for AWS instance metadata). Ghost establishes the connection to the internal service, allowing the attacker to interact with local APIs, access internal metrics, or extract cloud credentials.
To resolve the vulnerability, self-hosted administrators must upgrade Ghost CMS to version 6.21.1 or later. The update implements the low-level custom DNS resolution callback to ensure that the IP address checked for private range restrictions is the same address used for network connections.
When immediate upgrades are not possible, administrators should implement host-level or network-level firewall rules. Applying rules to block outbound traffic originating from the Ghost process or container targeting local loopback subnets (127.0.0.0/8, ::1) or private subnets (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) prevents outbound requests from reaching restricted endpoints. Link-local metadata interfaces (169.254.169.254) must also be blocked.
Organizations should also verify that their environment configurations are secure. Ensure that NODE_ENV is explicitly configured to production across all deployment manifests, as the SSRF validation logic is completely disabled in development mode.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Ghost TryGhost | >= 6.0.9, < 6.21.1 | 6.21.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-367 (TOCTOU), CWE-918 (SSRF) |
| Attack Vector | Network (Unauthenticated SSRF) |
| CVSS Score | 4.0 (Medium) |
| EPSS Score | 0.00140 (0.14%) |
| Exploit Status | Proof of Concept / Technical Analysis available |
| CISA KEV Status | Not Listed |
A Time-of-Check to Time-of-Use (TOCTOU) condition occurs when a security check is executed on a resource, but the resource changes before it is used by the application.
A technical analysis of CVE-2026-107397, a stored Cross-Site Scripting (XSS) vulnerability in Indico's collaborative notes editor and custom link generation fields. Prior to version 3.3.13, Marshmallow serialization schemas omitted HTML sanitization during conflict resolution, and form validators failed to enforce strict URI schemes, enabling authenticated low-privilege attackers to execute arbitrary JavaScript.
An authorization bypass vulnerability exists in the legacy session export API of Indico, an open-source event management system developed at CERN. Due to a missing object-level access check, authenticated users can bypass configuration-level restrictions to extract private session metadata (including session titles, descriptions, and list of conveners) from events that they are otherwise authorized to view.
An incomplete Server-Side Request Forgery (SSRF) validation check in Indico prior to version 3.3.13 allows authenticated event organizers to bypass outbound network restrictions. By utilizing backslash characters within crafted URLs, attackers can exploit a parser differential between the application's validator and the downstream HTTP client library to access internal network resources.
CVE-2026-107717 represents a critical prompt boundary bypass and chat role injection vulnerability in the Banks Python package (versions prior to 2.5.0). The library parses generated template outputs line-by-line, attempting to validate each segment as a JSON-serialized ChatMessage object without validating the source boundaries of the text. If an application integrates user input directly into a prompt template, a remote, unauthenticated attacker can supply multi-line inputs with structured JSON payloads. This input is then parsed as high-privilege system instructions or tool execution responses, completely hijacking downstream Large Language Model behavior.
Improper pathname limitation and link resolution (CWE-22 and CWE-59) in the banks library prior to version 2.5.1 allow local attackers to read or write arbitrary files via crafted symbolic links in the prompt directory registry.
Improper validation of dynamic class resolution within Hazelcast's Zero Config Compact Serialization allows unauthenticated clients to trigger reflective class instantiation. This flaw can be exploited to read arbitrary JVM heap or off-heap memory, crash cluster nodes, or achieve arbitrary code execution under specific classpath conditions. This issue is resolved in Hazelcast versions 5.4.5, 5.5.10, 5.6.1, and 5.7.0.