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

critical

How Server-Side Request Forgery (SSRF) happens in Node.js IP address parsing and how to fix it

A critical SSRF vulnerability (CVE-2026-69192) was discovered in the ip-address npm package version 10.2.0, which could allow attackers to bypass IP address validation and access internal services. The fix upgrades the dependency to version 10.3.1, which properly handles edge cases in IP address parsing that previously allowed trust-boundary bypasses.

critical

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

A critical Server-Side Request Forgery (SSRF) vulnerability in the compass-guarded-transfer CLI tool allowed attackers to make HTTP requests to internal services and cloud metadata endpoints. The `normalizeInput` function in `run-transfer.mjs` validated that URLs started with "https://" but failed to prevent requests to private IP ranges like AWS metadata (169.254.169.254) or localhost, enabling potential credential theft and internal network reconnaissance.

high

How Message-Level Raw Option Bypass happens in Node.js Nodemailer and how to fix it

A high-severity vulnerability in Nodemailer (versions before 9.0.0) allowed the `raw` message option to completely bypass `disableFileAccess` and `disableUrlAccess` security controls, enabling attackers to read arbitrary files from the server filesystem and perform full-response Server-Side Request Forgery (SSRF) in delivered email messages. Upgrading from `^8.0.10` to `^9.0.4` in `backend/package-lock.json` closes this exploit primitive by enforcing access restrictions consistently across all m

high

How Octal/Decimal IP Parsing Ambiguity happens in JavaScript and how to fix it

CVE-2026-69192 is a high-severity vulnerability in the `ip-address` npm package (versions before 10.3.1) where IPv4 addresses with leading-zero octets — like `010.0.0.1` — are parsed as decimal by the library but interpreted as octal by OS-level resolvers, creating a dangerous mismatch. This discrepancy can allow attackers to bypass IP-based access controls and trust boundaries, potentially enabling Server-Side Request Forgery (SSRF) attacks. Upgrading to `ip-address@10.3.1` in the SAP BW Query

critical

How unvalidated URL input handling happens in SvelteKit with Tauri and how to fix it

A critical vulnerability in `src/routes/+page.svelte` allowed attackers to supply arbitrary URLs—including `http://` and local file paths—through query parameters and drag-drop events, which were then fetched without validation. The fix restricts input to HTTPS-only URLs and removes the dangerous local file fetch path entirely, eliminating both SSRF and local file disclosure attack vectors.

critical

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

A Server-Side Request Forgery (SSRF) vulnerability was discovered in `server.js` and `worker.js`, where user-supplied `config` URL parameters were passed directly to `fetchWithAuth()` without any validation. This allowed attackers to force the application to make requests to internal network addresses, cloud metadata endpoints like `169.254.169.254`, or `file://` URIs. The fix introduces an `isAllowedUrl()` allowlist function that rejects private IP ranges, loopback addresses, and non-HTTP(S) pr