RSTR-SSRF-002 — Node http/https request with request input
Summary
The same SSRF class as RSTR-SSRF-001, expressed
via Node's lower-level http / https core modules instead of fetch
or axios. An attacker controls the URL, the server makes the request,
and internal or cloud-metadata endpoints become reachable through the
application.
Severity
High.
Languages
JavaScript, TypeScript.
What rastray flags
http.get, http.request, https.get, https.request with the URL
argument taken directly from req.body.*, req.query.*, req.params.*,
req.headers.*, or req.cookies.*:
const http = require('http');
app.get('/proxy', (req, res) => {
http.get(req.query.url, upstream => upstream.pipe(res)); // ← flagged
});
https.request(req.body.target, opts, cb).end(); // ← flagged
What rastray deliberately does not flag
- Literal URL arguments.
- Indirect flow (
const url = req.query.url; http.get(url)) — same conservative scope as every other rastray taint rule. - URLs constructed via
new URL(req.query.url, BASE)whereBASEis a fixed origin that the rule cannot evaluate. Suppress that case if the path component is also validated.
How to fix it
Same playbook as RSTR-SSRF-001:
- Parse the URL with
new URL(...). - Allow-list the hostname against a fixed
Set. - Reject any URL that resolves to a private, loopback, or link-local address before making the upstream request.
const ALLOWED = new Set(['api.example.com', 'cdn.example.com']);
app.get('/proxy', (req, res) => {
let target;
try { target = new URL(req.query.url); }
catch { return res.status(400).end(); }
if (!ALLOWED.has(target.hostname)) return res.status(400).end();
https.get(target, upstream => upstream.pipe(res));
});