Back to Blog
critical SEVERITY5 min read

How Server-Side Request Forgery (SSRF) happens in JavaScript and how to fix it

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in playground.html where the `__forEachRdfMessageChunkFromUrl` function fetched user-controlled URLs without validating against private IP ranges or internal network addresses. The fix introduces a comprehensive `__isBlockedFetchUrl` validation function that blocks requests to localhost, private IP ranges, and link-local addresses before any fetch occurs.

O
By Orbis AppSec
Published September 3, 2026Reviewed September 3, 2026

Answer Summary

This is a Server-Side Request Forgery (SSRF) vulnerability (CWE-918) in JavaScript/HTML where the `fetch()` API was called with user-controlled URLs without validating against private IP ranges. Attackers could redirect requests to internal services like AWS metadata endpoints (169.254.169.254) or internal admin panels. The fix adds a `__isBlockedFetchUrl()` function that validates URLs against blocklists for localhost, RFC 1918 private ranges, and link-local addresses before making any fetch request, and changes `redirect: 'follow'` to `redirect: 'error'` to prevent redirect-based bypasses.

Vulnerability at a Glance

cweCWE-918
fixAdded __isBlockedFetchUrl() validation function and disabled automatic redirect following
riskAttackers can access internal services, cloud metadata, and bypass network security controls
languageJavaScript/HTML
root causeUnvalidated user-controlled URL passed directly to fetch() with redirect following enabled
vulnerabilityServer-Side Request Forgery (SSRF)

Introduction

In the playground.html file, we discovered a critical SSRF vulnerability in the __forEachRdfMessageChunkFromUrl function at line 1843. This function handles streaming RDF message data from external URLs, but a flaw in how it processed the sourceUrl parameter created a serious security risk that could allow attackers to access internal network resources.

The vulnerable code accepted any URL provided by users and passed it directly to the fetch() API with redirect: 'follow' enabled—a dangerous combination that opened the door to Server-Side Request Forgery attacks targeting internal services, cloud metadata endpoints, and administrative interfaces.

The Vulnerability Explained

Server-Side Request Forgery (SSRF) occurs when an application fetches resources from user-supplied URLs without properly validating the destination. In this case, the vulnerable code looked like this:

async function __forEachRdfMessageChunkFromUrl(url, onMessage) {
  const sourceUrl = String(url || "").trim();
  if (!sourceUrl) throw new Error("--stream-messages needs an RDF Message Log URL...");
  const res = await fetch(sourceUrl, { cache: "no-store", referrerPolicy: "no-referrer", redirect: "follow" });
  // ...
}

Why This Code Was Dangerous

The function had two critical flaws:

  1. No URL validation: The sourceUrl parameter was only checked for emptiness, not for dangerous destinations
  2. Redirect following enabled: The redirect: 'follow' option meant that even if initial URL validation existed, attackers could bypass it using HTTP redirects

Attack Scenario

An attacker controlling the sourceUrl parameter (via URL parameters, user input, or the __EYELING_URL variable) could exploit this vulnerability in several ways:

Scenario 1: AWS Metadata Access

sourceUrl = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"

This would retrieve AWS IAM credentials from the instance metadata service, potentially compromising the entire cloud infrastructure.

Scenario 2: Internal Service Scanning

sourceUrl = "http://10.0.0.1/admin"
sourceUrl = "http://192.168.1.1/api/users"

Attackers could scan and access internal services that are not exposed to the public internet.

Scenario 3: Redirect Bypass
Even with basic URL validation, an attacker could host a redirect on their server:

sourceUrl = "https://attacker.com/redirect" 
// Server responds with: HTTP 302 Location: http://169.254.169.254/...

The redirect: 'follow' option would automatically follow this redirect to the internal target.

The Fix

The fix introduces a comprehensive URL validation function called __isBlockedFetchUrl that checks URLs against multiple blocklists before any fetch occurs:

Before (Vulnerable)

async function __forEachRdfMessageChunkFromUrl(url, onMessage) {
  const sourceUrl = String(url || "").trim();
  if (!sourceUrl) throw new Error("--stream-messages needs an RDF Message Log URL...");
  const res = await fetch(sourceUrl, { cache: "no-store", referrerPolicy: "no-referrer", redirect: "follow" });

After (Fixed)

function __isBlockedFetchUrl(u) {
  let parsed;
  try { parsed = new URL(u, location.href); } catch { return true; }
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return true;
  const host = parsed.hostname.toLowerCase();
  if (!host || host === "localhost" || host.endsWith(".localhost")) return true;
  if (/^(?:127\.|10\.|192\.168\.|169\.254\.|0\.)/.test(host)) return true;
  if (/^172\.(1[6-9]|2\d|3[0-1])\./.test(host)) return true;
  if (host === "::1" || /^f[cd][0-9a-f]{0,2}:/i.test(host) || /^fe80:/i.test(host)) return true;
  return false;
}

async function __forEachRdfMessageChunkFromUrl(url, onMessage) {
  const sourceUrl = String(url || "").trim();
  if (!sourceUrl) throw new Error("--stream-messages needs an RDF Message Log URL...");
  if (__isBlockedFetchUrl(sourceUrl)) throw new Error("--stream-messages URL is not allowed: only public http(s) URLs are permitted, not private/internal/loopback addresses.");
  const res = await fetch(sourceUrl, { cache: "no-store", referrerPolicy: "no-referrer", redirect: "error" });

What the Fix Does

The __isBlockedFetchUrl function implements defense-in-depth by checking:

Check Purpose
protocol !== "http:" && protocol !== "https:" Blocks file://, javascript:, data: URLs
host === "localhost" Blocks localhost access
/^(?:127\.\|10\.\|192\.168\.\|169\.254\.\|0\.)/ Blocks loopback, Class A private, Class C private, link-local, and null ranges
/^172\.(1[6-9]\|2\d\|3[0-1])\./ Blocks Class B private range (172.16.0.0 - 172.31.255.255)
host === "::1" and IPv6 patterns Blocks IPv6 loopback and private addresses

Additionally, changing redirect: "follow" to redirect: "error" ensures that any redirect attempt will throw an error rather than being followed, preventing redirect-based bypass attacks.

Prevention & Best Practices

1. Always Validate URLs Before Fetching

Never pass user-controlled URLs directly to fetch(), axios, or similar HTTP clients without validation:

// Good: Validate before fetching
if (isAllowedUrl(userUrl)) {
  const response = await fetch(userUrl);
}

// Bad: Direct fetch without validation
const response = await fetch(userUrl);

2. Use Allowlists Over Blocklists When Possible

While blocklists are necessary for IP ranges, consider allowlisting specific domains if your use case permits:

const ALLOWED_DOMAINS = ['api.example.com', 'cdn.example.com'];

function isAllowedDomain(url) {
  const parsed = new URL(url);
  return ALLOWED_DOMAINS.includes(parsed.hostname);
}

3. Disable Automatic Redirects

Always use redirect: 'error' or redirect: 'manual' when fetching user-controlled URLs:

const response = await fetch(url, { redirect: 'error' });

4. Consider DNS Rebinding Attacks

Advanced attackers may use DNS rebinding to bypass hostname-based validation. For high-security applications, resolve the hostname and validate the IP address before making the request.

Key Takeaways

  • The __forEachRdfMessageChunkFromUrl function in playground.html was vulnerable because it fetched user-controlled URLs without any destination validation
  • The redirect: 'follow' option created a bypass vector even if basic URL validation had been present
  • Comprehensive IP range blocking must cover IPv4 private ranges (10.x, 172.16-31.x, 192.168.x), loopback (127.x), link-local (169.254.x), and IPv6 equivalents
  • Always disable automatic redirect following when handling user-supplied URLs to prevent redirect-based SSRF bypasses
  • URL validation should happen immediately after receiving user input, before any processing or fetch operations

How Orbis AppSec Detected This

  • Source: User-controlled URL from the sourceUrl parameter (derived from URL parameters or __EYELING_URL)
  • Sink: fetch(sourceUrl, { redirect: 'follow' }) in playground.html:1843
  • Missing control: No validation of URL destination against private IP ranges or internal network addresses
  • CWE: CWE-918 (Server-Side Request Forgery)
  • Fix: Added __isBlockedFetchUrl() validation function that blocks private IP ranges, localhost, and link-local addresses, and changed redirect policy from 'follow' to 'error'

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This SSRF vulnerability in playground.html demonstrates how easily attackers can abuse unvalidated URL handling to access internal resources. The combination of accepting arbitrary URLs and following redirects created a significant security risk that could have led to cloud credential theft, internal service compromise, or data exfiltration.

The fix shows that proper SSRF protection requires multiple layers: protocol validation, hostname blocklisting for private ranges, and disabling automatic redirect following. By implementing these controls in the __isBlockedFetchUrl function, the application now safely handles user-provided URLs while maintaining its core functionality.

When building applications that fetch external resources, always assume user input is malicious and validate thoroughly before making any network requests.

References

Frequently Asked Questions

What is Server-Side Request Forgery (SSRF)?

SSRF is a vulnerability where an attacker can make a server-side application send HTTP requests to arbitrary destinations, potentially accessing internal services, cloud metadata endpoints, or other resources that should not be publicly accessible.

How do you prevent SSRF in JavaScript?

Validate all user-supplied URLs against an allowlist of permitted domains, block private IP ranges (10.x.x.x, 172.16-31.x.x, 192.168.x.x), localhost, and link-local addresses (169.254.x.x), and disable automatic redirect following to prevent bypass attacks.

What CWE is SSRF?

SSRF is classified as CWE-918: Server-Side Request Forgery, which describes vulnerabilities where an attacker can abuse functionality to make requests to unintended locations.

Is URL parsing enough to prevent SSRF?

No, URL parsing alone is insufficient. You must also validate the resolved hostname against blocklists for private IP ranges, implement DNS rebinding protections, and handle redirects carefully since attackers can use redirects to bypass initial URL validation.

Can static analysis detect SSRF?

Yes, static analysis tools can detect SSRF patterns by tracing data flow from user input sources to HTTP request sinks like fetch(), axios, or XMLHttpRequest, flagging cases where URLs are not validated before use.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #38

Related Articles

critical

How SSRF via Vulnerable Dependency Versions Happens in Node.js and How to Fix It

A permissive semver range in `package.json` allowed npm to install axios versions vulnerable to SSRF (CVE-2024-39338). By bumping the minimum version from `^1.6.0` to `^1.7.4`, all downstream consumers of this SDK are now protected from server-side request forgery attacks. This critical fix required changing just one line in the dependency manifest.

critical

How Server-Side Request Forgery happens in Python FastAPI and how to fix it

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in app.py where the `/parse` and `/parse-video` endpoints accepted user-supplied URLs with only substring validation. The application checked if 'doubao.com' appeared anywhere in the URL string, allowing attackers to bypass this check and access internal services, cloud metadata endpoints, or scan the internal network. The fix implemented proper hostname parsing with an allowlist of legitimate domains.

critical

How Server-Side Request Forgery happens in Node.js maintenance scripts and how to fix it

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in `maintenance/getImages.js`, where the `getImage()` function passed database-sourced URLs directly to `axios.get()` without any validation. An attacker who could modify the elements database could redirect these requests to internal network resources — including AWS cloud metadata endpoints — potentially exposing IAM credentials and other sensitive infrastructure data. The fix introduces a strict URL allowlist that limi

high

How SSRF via inconsistent IP address parsing happens in Node.js dependencies and how to fix it

A high-severity flaw (CVE-2026-69192) in the widely-used `ip-address` npm package meant that IP strings could be parsed inconsistently compared to the OS resolver and Node's own networking stack — letting an attacker slip a private/loopback address past an allowlist that used `Address4`/`Address6` for validation. This PR pins and upgrades `ip-address` from `10.1.0` to `10.3.1` in both `package.json` (via `overrides`) and `package-lock.json`, eliminating the parser divergence across the whole dep

high

How Octal IP Address Parsing Leads to SSRF in Node.js and How to Fix It

CVE-2026-69192 reveals a critical inconsistency in the `ip-address` library where Address4 decodes leading-zero octets as decimal while DNS resolvers interpret them as octal, creating a dangerous parsing divergence. This mismatch allows attackers to bypass IP-based access controls and perform Server-Side Request Forgery (SSRF) attacks. The fix upgrades `ip-address` from 9.0.5 to 10.3.1, aligning parsing behavior with standard resolver implementations.

critical

How Path Traversal happens in Node.js Express servers and how to fix it

A path traversal vulnerability in `src/server.js` allowed attackers to escape the intended wiki directory by sending encoded traversal sequences through the `/api/pages/:slug(*)` wildcard endpoint. The flawed `startsWith` boundary check could be bypassed after `decodeURIComponent` processing, potentially exposing arbitrary files on the server. The fix replaces the inline filesystem logic with a dedicated `readWikiPage()` function that enforces proper path validation.