Skip to content

[LEGIT] Fix - js/request-forgery#103

Open
liorn-test-app[bot] wants to merge 1 commit into
masterfrom
legit-security-2ec552
Open

[LEGIT] Fix - js/request-forgery#103
liorn-test-app[bot] wants to merge 1 commit into
masterfrom
legit-security-2ec552

Conversation

@liorn-test-app

@liorn-test-app liorn-test-app Bot commented Jul 5, 2026

Copy link
Copy Markdown

🔍 The problem

Server-side request forgery
See issue in Legit

🔒 Fix Details

Server-Side Request Forgery (SSRF) Fix

The original code constructed a URL from user-controlled query parameters (req.query.url and req.query.symbol) and passed it directly to needle.get() without validation. This allowed attackers to make the server send HTTP requests to arbitrary destinations, including internal services, cloud metadata endpoints (e.g., AWS EC2 metadata at 169.254.169.254), or localhost resources.

Fix applied: Added comprehensive URL validation before the outbound request:

  • Parse and validate URL format
  • Enforce HTTPS-only protocol to prevent protocol smuggling attacks
  • Implement an allowlist of permitted stock API hostnames
  • Block requests to localhost, loopback addresses, and private IP ranges (10.x.x.x, 172.16-31.x.x, 192.168.x.x, 169.254.x.x)

Note: The allowedHosts array contains placeholder domains and must be configured with actual legitimate stock data API domains before deployment.

--- a/app/routes/research.js
+++ b/app/routes/research.js
@@ -1,5 +1,6 @@
 const ResearchDAO = require("../data/research-dao").ResearchDAO;
 const needle = require('needle');
+const { URL } = require('url');
 
 function ResearchHandler (db) {
     "use strict";
@@ -10,6 +11,44 @@
         
         if (req.query.symbol) {
             const url = req.query.url+req.query.symbol; 
+            
+            // Validate URL to prevent SSRF
+            let parsedUrl;
+            try {
+                parsedUrl = new URL(url);
+            } catch (e) {
+                return res.status(400).send('Invalid URL format');
+            }
+
+            // Allowlist of permitted stock data API hosts
+            const allowedHosts = [
+                'api.example-stock-service.com',
+                'stockdata.example.com'
+                // Add actual legitimate stock API domains here
+            ];
+
+            // Check protocol is HTTPS only
+            if (parsedUrl.protocol !== 'https:') {
+                return res.status(400).send('Only HTTPS URLs are allowed');
+            }
+
+            // Check hostname is in allowlist
+            if (!allowedHosts.includes(parsedUrl.hostname)) {
+                return res.status(400).send('URL host not permitted');
+            }
+
+            // Block private IP ranges and localhost
+            const hostname = parsedUrl.hostname;
+            if (hostname === 'localhost' || 
+                hostname === '127.0.0.1' || 
+                hostname === '::1' ||
+                hostname.match(/^10\./) ||
+                hostname.match(/^172\.(1[6-9]|2[0-9]|3[0-1])\./) ||
+                hostname.match(/^192\.168\./) ||
+                hostname.match(/^169\.254\./)) {
+                return res.status(400).send('Private IP addresses not allowed');
+            }
+            
             return needle.get(url, (error, newResponse) => {
                 if (!error && newResponse.statusCode == 200)
                     res.writeHead(200, {'Content-Type': 'text/html'});

@sonarqubecloud

sonarqubecloud Bot commented Jul 5, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
B Maintainability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants