CVE-2025-15104

Nu Html Checker SSRF: When 'Localhost' Isn't the Only Way Home

Alon Barad
Alon Barad
Software Engineer

Jan 18, 2026·6 min read

Executive Summary (TL;DR)

The Nu Html Checker allows users to validate HTML via URL. It attempts to block internal access by banning hostnames like "localhost", but fails to validate the resolved IP address. Attackers can use DNS rebinding or domains resolving to loopback addresses to bypass this filter, tricking the server into connecting to its own internal services or local network infrastructure.

The Nu Html Checker (validator.nu), the engine powering W3C's HTML validation services, contains a Server-Side Request Forgery (SSRF) vulnerability. By relying on a flimsy hostname blocklist instead of robust IP validation, the application allows attackers to bypass protections via DNS rebinding and access internal network resources.

The Hook: Who Watches the Watchmen?

In the web development world, the Nu Html Checker (vnu) is a celebrity. It's the engine under the hood of the W3C Validator, the tool we've all used to compulsively check if we closed our <div> tags correctly. To do its job, it offers a "Check by URL" feature. You give it a link, it fetches the HTML, and it judges your code. Simple, right?

Here is the problem: In security, any feature that takes a user-supplied URL and makes a backend HTTP request is an immediate candidate for Server-Side Request Forgery (SSRF). It’s the "Hello World" of modern web exploitation. If the server fetches google.com for you, can you make it fetch localhost? Can you make it scan the internal network?

The developers knew this. They put up a "Do Not Enter" sign. They implemented a filter to stop you from accessing internal resources. But as we're about to see, they made the classic mistake of checking the ID card (hostname) instead of the person (IP address).

The Flaw: The 'Naughty Word' List

The root cause of CVE-2025-15104 is a text-book example of "Time-of-Check Time-of-Use" (TOCTOU) logic applied to network security. The application attempts to prevent SSRF by checking the hostname provided by the user against a deny-list. They essentially said, "If the user types 'localhost' or '127.0.0.1', stop them."

This is a fundamental misunderstanding of how the internet works. DNS (Domain Name System) is a dynamic pointer. A hostname is just a label. The security check looks at the label, decides it looks safe (e.g., attacker.com), and approves the request. Then, the actual HTTP client resolves that hostname to an IP address to make the connection.

The vulnerability exists in the gap between the check and the connection. The application assumes that if the name doesn't look like localhost, the destination won't be localhost. It fails to validate the actual resolved IP address before establishing the socket connection. This leaves the door wide open for two bypass techniques: using public domains that resolve to private IPs (like local.test.com -> 127.0.0.1) and the more advanced DNS Rebinding.

The Code: A Conceptual Logic Failure

While the exact source diff is often hidden behind "update now" advisories, the logic flaw follows a specific, recognizable anti-pattern in Java network programming. The vulnerable implementation likely resembled this pseudocode logic:

// VULNERABLE LOGIC PATTERN
String url = request.getParameter("url");
URL target = new URL(url);
String host = target.getHost();
 
// The "Blocklist" Approach
if (host.equalsIgnoreCase("localhost") || host.equals("127.0.0.1")) {
    throw new SecurityException("Access Denied");
}
 
// The Fatal Flaw: The check is over, now we connect.
// The system resolves the hostname AGAIN here.
InputStream response = target.openStream();

The fix involves changing the architecture entirely. You cannot trust the hostname. You must resolve the DNS, check the resulting IP against reserved ranges (RFC 1918, loopback), and then—crucially—connect to that specific IP.

// SECURE PATTERN (Mitigation)
InetAddress ip = InetAddress.getByName(target.getHost());
 
// Check if IP is private/loopback
if (ip.isLoopbackAddress() || ip.isSiteLocalAddress()) {
    throw new SecurityException("Internal access forbidden");
}
 
// Connect explicitly to the validated IP, not the hostname
// This prevents DNS Rebinding
HttpURLConnection conn = (HttpURLConnection) target.openConnection();
// (Implementation detail: Custom SocketFactory or Host header manipulation required here)

The patch for CVE-2025-15104 moves from the first pattern to the second, validating the destination IP rather than the string representation of the URL.

The Exploit: The Old Switcheroo (DNS Rebinding)

Since the validator checks the hostname but doesn't pin the IP, we can pull a "bait and switch" using DNS Rebinding. This attack requires the attacker to control a malicious DNS server and a domain (e.g., rebind.attacker.com).

Phase 1: The Bait The attacker sets the DNS record for rebind.attacker.com to a harmless public IP (e.g., 1.2.3.4) with a very short Time-To-Live (TTL), say 0 or 1 second.

Phase 2: The Check The attacker submits http://rebind.attacker.com to the Nu Html Checker. The application resolves the domain to 1.2.3.4. It checks its blacklist. Is 1.2.3.4 on the list? No. Is rebind.attacker.com equal to "localhost"? No. The check passes.

Phase 3: The Switch The application prepares to fetch the content. Because the DNS TTL was so short, the cached DNS entry has already expired. The application performs a second DNS lookup to establish the TCP connection. This time, the malicious DNS server answers with 127.0.0.1.

Phase 4: Execution The validator unknowingly opens a connection to 127.0.0.1 (itself) while believing it is talking to rebind.attacker.com. The attacker can now see the HTML response of whatever is running on the validator's localhost port 80.

The Impact: Why Should We Panic?

You might ask, "So what if I can access localhost?" In modern infrastructure, localhost is often a trusted zone. Services running on 127.0.0.1 frequently lack authentication because the developers assume, "Well, you have to be on the server to access this, so it's safe."

Here is what an attacker can target via this SSRF:

  • Cloud Metadata Services: If the validator is running on AWS, GCP, or Azure, accessing http://169.254.169.254 can leak IAM credentials, allowing the attacker to take over the entire cloud account.
  • Local Databases: Developers often run Redis or Memcached on localhost without passwords. An attacker can use the SSRF to send commands to these services.
  • Internal APIs: Many microservices expose administrative endpoints (e.g., /actuator/health or /metrics) on internal ports that leak environment variables and configuration details.

With a CVSS score of 6.9 (Nice, but actually Medium severity), this isn't an automatic RCE, but in the right environment, it is the first step in a catastrophic chain.

The Fix: Trust Nothing, Verify Everything

Hostname blacklists are dead. If you are a developer, stop using them. The only robust defense against SSRF is strict network-layer validation.

1. IP Validation & Pinning: Resolve the hostname to an IP address. Check that IP against a list of forbidden ranges (127.0.0.0/8, 10.0.0.0/8, 192.168.0.0/16, etc.). If it passes, use that specific IP for the connection. Do not resolve the name again.

2. Disable Unnecessary Features: If your instance of Nu Html Checker is internal-only, or if you don't need the "Check by URL" feature, turn it off. The safest code is the code that never runs.

3. Firewall Egress Rules: Configure the server's firewall (iptables, security groups) to drop all outgoing packets destined for internal RFC 1918 addresses. This is your safety net. If the application code fails, the OS kernel should step in and kill the connection.

Fix Analysis (1)

Technical Appendix

CVSS Score
6.9/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:N/SA:N
EPSS Probability
0.06%
Top 80% most exploited

Affected Systems

Nu Html Checker (vnu)validator.nuW3C Validator implementations using vnu engine

Affected Versions Detail

Product
Affected Versions
Fixed Version
Nu Html Checker
validator.nu
< 23f090a (Jan 16 2026)Post-Jan 16 2026 builds
AttributeDetail
Vulnerability TypeSSRF (Server-Side Request Forgery)
CWE IDCWE-918
CVSS v4.06.9 (Medium)
Attack VectorNetwork
Attack ComplexityLow
Exploit StatusNo public PoC, but technique is standard
EPSS Score0.06%
CWE-918
Server-Side Request Forgery (SSRF)

The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.

Vulnerability Timeline

Vulnerability Disclosed by Fluid Attacks
2026-01-16
CVE-2025-15104 Assigned
2026-01-16

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.