Back to Blog
critical SEVERITY8 min read

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

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in `hubcmdui/routes/httpProxy.js` where the `/proxy/test` endpoint passed a user-controlled `testUrl` parameter directly to `axios.get()` without any validation. An attacker could exploit this to probe internal infrastructure — including AWS metadata endpoints, Redis instances, and private network ranges. The fix introduces a strict URL validation function that blocks private IP ranges, loopback addresses, and non-HTTP(S)

O
By Orbis AppSec
Published July 24, 2026Reviewed July 24, 2026

Answer Summary

This is a Server-Side Request Forgery (SSRF) vulnerability (CWE-918) in Node.js, found in `hubcmdui/routes/httpProxy.js` at line 130. The `/proxy/test` route accepted a `testUrl` parameter from the request body and passed it directly to `axios.get()` without validation, allowing attackers to make the server issue requests to internal network addresses. The fix adds an `isAllowedUrl()` function that validates the URL protocol and blocks all private/loopback IP ranges (10.x, 172.16–31.x, 192.168.x, localhost, 127.x) before the request is executed.

Vulnerability at a Glance

cweCWE-918
fixAdded `isAllowedUrl()` function that enforces HTTP/HTTPS-only protocols and blocks all RFC-1918 private IP ranges and loopback addresses
riskAttacker can probe or attack internal infrastructure, steal cloud metadata credentials, or pivot to internal services
languageJavaScript (Node.js)
root causeUser-controlled `testUrl` parameter passed directly to `axios.get()` in the `/proxy/test` route handler without URL validation
vulnerabilityServer-Side Request Forgery (SSRF)

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

The Vulnerability at a Glance

Field Detail
Vulnerability Server-Side Request Forgery (SSRF)
CWE CWE-918
Language JavaScript (Node.js)
Risk Attacker can probe internal infrastructure, steal cloud credentials, or pivot to internal services
Root Cause User-controlled testUrl passed directly to axios.get() without validation
Fix isAllowedUrl() function blocking private IPs and non-HTTP(S) protocols

Introduction

The hubcmdui/routes/httpProxy.js file handles proxy configuration and connection testing for the application. But a flaw in its /proxy/test route handler — specifically, the way it consumed the testUrl parameter from the request body — created a textbook Server-Side Request Forgery (SSRF) vulnerability. An authenticated attacker could supply any URL they wanted, and the server would dutifully fetch it, potentially exposing your entire internal network to enumeration and attack.

This kind of vulnerability is deceptively simple: one missing validation check on a single variable turns a helpful "test my proxy connection" feature into a weapon for internal network reconnaissance.


The Vulnerability Explained

What Was Happening

At line 130 of hubcmdui/routes/httpProxy.js, the /proxy/test POST endpoint extracted a testUrl parameter directly from the request body and passed it to axios.get() with zero validation:

// VULNERABLE CODE (before fix)
router.post('/proxy/test', requireLogin, async (req, res) => {
  try {
    const { testUrl = 'http://httpbin.org/ip' } = req.body;
    const axios = require('axios');
    const status = httpProxyService.getStatus();
    // axios.get(testUrl) called with no URL validation

The default value of 'http://httpbin.org/ip' gives a false sense of safety — it implies a known-good URL will be used. But because testUrl comes from req.body, any authenticated user can override it with any URL they choose.

How an Attacker Would Exploit This

The attack is straightforward. An authenticated user sends a POST request to /api/proxy/test with a malicious testUrl:

Scenario 1 — AWS Cloud Metadata Theft:

POST /api/proxy/test HTTP/1.1
Content-Type: application/json

{
  "testUrl": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
}

The server responds with the instance's IAM role name. A follow-up request to http://169.254.169.254/latest/meta-data/iam/security-credentials/my-role returns temporary AWS access keys — full credential compromise from a single HTTP request.

Scenario 2 — Internal Service Enumeration:

POST /api/proxy/test HTTP/1.1
Content-Type: application/json

{
  "testUrl": "http://192.168.1.1/"
}

The server probes the internal router admin panel. The attacker can iterate through 192.168.1.x addresses to map the internal network, or target http://localhost:6379/ to interact with an unauthenticated Redis instance.

Scenario 3 — Non-HTTP Protocol Abuse:
Without protocol validation, an attacker might attempt file:///etc/passwd or ftp://internal-server/ depending on how axios handles non-HTTP schemes.

Why requireLogin Isn't Enough

Notice that the route already uses requireLogin middleware. This is a common misconception: authentication does not prevent SSRF. Any legitimate authenticated user — or an attacker who has compromised any account — can trigger this. In multi-tenant or SaaS environments, this is especially dangerous.

Real-World Impact for This Application

This proxy testing feature exists to let users verify their proxy configuration. But the server making that request sits inside the network perimeter. It can reach services that are firewalled from the outside world. The attacker doesn't need to bypass the firewall — they use the application server itself as a proxy.


The Fix

What Changed

The fix introduces a dedicated isAllowedUrl() validation function and calls it before axios.get() is ever invoked. Here's the complete before/after comparison:

Before (vulnerable):

router.post('/proxy/test', requireLogin, async (req, res) => {
  try {
    const { testUrl = 'http://httpbin.org/ip' } = req.body;
    const axios = require('axios');
    const status = httpProxyService.getStatus();
    // No validation — axios.get(testUrl) proceeds with any URL

After (fixed):

// 验证URL是否为允许的外部地址(防止SSRF)
function isAllowedUrl(urlString) {
  try {
    const parsed = new URL(urlString);
    // Only allow HTTP and HTTPS
    if (!['http:', 'https:'].includes(parsed.protocol)) return false;
    const hostname = parsed.hostname.toLowerCase();
    // Block loopback addresses
    if (/^(localhost|127\.|0\.0\.0\.0|::1$)/.test(hostname)) return false;
    // Block RFC-1918 Class A private range
    if (/^10\./.test(hostname)) return false;
    // Block RFC-1918 Class B private range (172.16.0.0 – 172.31.255.255)
    if (/^172\.(1[6-9]|2\d|3[01])\./.test(hostname)) return false;
    // Block RFC-1918 Class C private range
    if (/^192\.168\./.test(hostname)) return false;
    return true;
  } catch (e) {
    return false;
  }
}

router.post('/proxy/test', requireLogin, async (req, res) => {
  try {
    const { testUrl = 'http://httpbin.org/ip' } = req.body;
    if (!isAllowedUrl(testUrl)) {
      return res.status(400).json({ error: '无效或不允许的测试URL' });
    }
    const axios = require('axios');
    const status = httpProxyService.getStatus();

Breaking Down the Fix

1. Protocol enforcement!['http:', 'https:'].includes(parsed.protocol) ensures only legitimate web protocols are accepted. This blocks file://, ftp://, gopher://, and other protocol schemes that could be abused.

2. Loopback blocking — The regex /^(localhost|127\.|0\.0\.0\.0|::1$)/ catches all common loopback representations: the hostname localhost, any 127.x.x.x address, the unspecified address 0.0.0.0, and the IPv6 loopback ::1.

3. RFC-1918 private range blocking — Three separate regex patterns cover all three private IP ranges defined in RFC 1918:
- 10.0.0.0/8 → blocked by /^10\./
- 172.16.0.0/12 → blocked by /^172\.(1[6-9]|2\d|3[01])\./
- 192.168.0.0/16 → blocked by /^192\.168\./

4. Fail-safe error handling — The try/catch block means a malformed URL that throws in new URL() returns false, causing the request to be rejected rather than crashing or proceeding.

5. Early return with clear errorreturn res.status(400).json({ error: '无效或不允许的测试URL' }) stops execution immediately and gives the client a meaningful error without leaking internal details.


Prevention & Best Practices

Defense in Depth for SSRF

Validate at the earliest possible point. The fix correctly places isAllowedUrl() check before require('axios') is even called. Don't let tainted data travel further than necessary.

Consider an allowlist over a blocklist. The current fix uses a blocklist of private ranges, which is good. An even stronger approach is an allowlist: if you only need to test connectivity to a known set of proxy servers, enumerate those explicitly. Blocklists can be bypassed by creative encoding (e.g., decimal IP notation 2130706433 for 127.0.0.1) — though the new URL() parser mitigates many of these.

Beware DNS rebinding. A sophisticated attacker could register a domain that resolves to a public IP at validation time but a private IP when the actual request is made. For high-security environments, consider resolving the hostname to an IP address at validation time and checking that IP.

Use a dedicated HTTP client with SSRF protection. Libraries like ssrf-req-filter for Node.js wrap axios/node-fetch with built-in SSRF protection.

Network-level controls. Complement application-level validation with egress firewall rules that prevent the application server from reaching internal metadata endpoints and private ranges.

Detecting SSRF in Your Codebase

Look for these patterns in Node.js code:

// Dangerous patterns — user input flowing into HTTP client calls
axios.get(req.body.url)
axios.get(req.query.target)
fetch(req.params.endpoint)
http.get(userInput)

Use Semgrep to find these automatically:

rules:
  - id: ssrf-axios-user-input
    patterns:
      - pattern: axios.get($URL, ...)
      - pattern-not: axios.get("...", ...)
    message: Potential SSRF — validate $URL before use
    languages: [javascript]
    severity: ERROR

Relevant Standards

  • OWASP Top 10 2021: A10 — Server-Side Request Forgery — SSRF earned its own category in the 2021 update, reflecting its growing prevalence and severity.
  • CWE-918: Server-Side Request Forgery — The canonical weakness classification for this vulnerability type.
  • OWASP SSRF Prevention Cheat Sheet — Comprehensive guidance on SSRF mitigations.

Key Takeaways

  • The /proxy/test endpoint's testUrl parameter was a direct SSRF entry point — any field that accepts a URL and triggers a server-side fetch must be validated, regardless of authentication guards.
  • requireLogin does not protect against SSRF — authentication controls who can reach an endpoint, not what they can do with it once authenticated.
  • All three RFC-1918 private IP ranges must be blocked explicitly — blocking only 192.168.x.x while forgetting 10.x.x.x or 172.16-31.x.x leaves significant attack surface open.
  • The new URL() parser is your friend for SSRF validation — it normalizes URLs and handles encoding edge cases better than manual string parsing or regex alone.
  • Default parameter values don't prevent injectiontestUrl = 'http://httpbin.org/ip' only applies when the field is absent; a supplied value always wins.

How Orbis AppSec Detected This

  • Source: The testUrl parameter extracted from req.body in the POST /proxy/test route handler in hubcmdui/routes/httpProxy.js:130
  • Sink: axios.get(testUrl) — the HTTP client call that executes the server-side request with the unvalidated user-supplied URL
  • Missing control: No URL validation, protocol restriction, or IP range filtering was present between the source and sink; the requireLogin middleware was the only gate
  • CWE: CWE-918 — Server-Side Request Forgery (SSRF)
  • Fix: Added isAllowedUrl() function that enforces HTTP/HTTPS-only protocols and rejects all RFC-1918 private IP ranges and loopback addresses before any HTTP request is made

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

SSRF vulnerabilities are particularly dangerous because they weaponize the server's privileged network position. The /proxy/test endpoint in httpProxy.js is a perfect example: a feature designed to help users test connectivity became a mechanism for internal network reconnaissance and potential credential theft from cloud metadata services.

The fix is elegant in its simplicity — a single isAllowedUrl() function with clear, maintainable regex patterns covering every private IP range. But the broader lesson is architectural: any endpoint that accepts a URL and makes a server-side request is a potential SSRF vector. Treat URL parameters with the same skepticism you'd give to SQL query parameters or shell command arguments.

When building similar proxy-testing or URL-fetching features, make URL validation the first line of code in your handler — not an afterthought.


References

Frequently Asked Questions

What is Server-Side Request Forgery (SSRF)?

SSRF is a vulnerability where an attacker tricks a server into making HTTP requests to unintended destinations — including internal services, cloud metadata endpoints, or private network hosts — by supplying a malicious URL that the server fetches on their behalf.

How do you prevent SSRF in Node.js?

Validate all user-supplied URLs before passing them to HTTP client libraries like axios. Use an allowlist or blocklist of permitted hostnames/IP ranges, enforce HTTP/HTTPS-only protocols, and reject private IP ranges (10.x, 172.16-31.x, 192.168.x) and loopback addresses (localhost, 127.x).

What CWE is Server-Side Request Forgery?

SSRF is classified as CWE-918: Server-Side Request Forgery. It appears in the OWASP Top 10 2021 as A10:2021.

Is authentication enough to prevent SSRF?

No. The `/proxy/test` endpoint in this case already required authentication via `requireLogin`, but an authenticated attacker could still exploit the SSRF. Input validation on the URL itself is the essential control.

Can static analysis detect SSRF?

Yes. Tools like Semgrep, CodeQL, and multi-agent AI scanners can trace tainted data from HTTP request parameters to HTTP client calls (like `axios.get()`) and flag unvalidated URL sinks. This vulnerability was detected automatically by Orbis AppSec's multi-agent AI scanner.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #93

Related Articles

high

How SSRF and Credential Leakage via Absolute URLs happens in axios and how to fix it

A high-severity vulnerability (CVE-2025-27152) in axios versions prior to 1.8.2 allowed Server-Side Request Forgery (SSRF) attacks and credential leakage when making HTTP requests with absolute URLs. This vulnerability was fixed by upgrading from axios 1.7.4 to 1.8.2 in both package.json and package-lock.json, eliminating the attack vector that could have exposed authentication tokens and enabled unauthorized server-side requests.

high

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

A high-severity Server-Side Request Forgery (SSRF) vulnerability was discovered in `webui/backend/main.py` at line 6597 of the Posterizarr project. User-controlled `request.media_type` was interpolated directly into a URL used for server-side HTTP requests to The Movie Database (TMDB) API, allowing attackers to manipulate the destination of outbound requests. The fix introduces a strict allowlist that only permits `"movie"` or `"tv"` as valid media types.

critical

How Server-Side Request Forgery (SSRF) happens in Python requests.get() and how to fix it

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in `models/common.py` where `requests.get()` fetched images from arbitrary URLs without validating whether the target resolved to internal infrastructure. An attacker could supply URLs targeting AWS metadata endpoints (169.254.169.254), private networks, or localhost services through the Flask REST API. The fix introduces DNS-resolution-based validation using Python's `socket.getaddrinfo()` and `ipaddress` module to block

critical

How arbitrary code execution via injected protobuf definition type fields happens in Node.js and how to fix it

A critical arbitrary code execution vulnerability (CVE-2026-41242) was discovered in protobufjs versions prior to 7.5.5 and 8.0.1, allowing attackers to inject malicious code through crafted protobuf definition type fields. The fix upgrades the dependency from the vulnerable version 6.11.4 to 7.5.5, which properly sanitizes type field inputs during protobuf parsing. This vulnerability is especially dangerous because protobufjs is widely used in Node.js applications for serialization, meaning a s

critical

How WebSocket protocol length header abuse happens in Node.js and how to fix it

A critical vulnerability (CVE-2026-54466) in websocket-driver versions prior to 0.7.5 allows attackers to corrupt WebSocket messages by manipulating protocol length headers. This can lead to data integrity issues, denial of service, or potentially arbitrary code execution in affected Node.js applications. The fix involves upgrading the websocket-driver dependency to version 0.7.5.

critical

How unsafe token deserialization happens in Node.js Temml parser and how to fix it

A critical vulnerability in the Temml math library's parser allowed unsafe token deserialization that could lead to remote code execution when processing user-supplied mathematical expressions. The fix adds strict type validation on fetched token properties before use, preventing exploitation of malformed or crafted payloads.