UNPKG

@aikidosec/firewall

Version:

Zen by Aikido is an embedded Web Application Firewall that autonomously protects Node.js apps against common and critical attacks

79 lines (78 loc) 3.82 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.wrapResponseHandler = wrapResponseHandler; const Context_1 = require("../../agent/Context"); const getNodeVersion_1 = require("../../helpers/getNodeVersion"); const getPortFromURL_1 = require("../../helpers/getPortFromURL"); const isRedirectStatusCode_1 = require("../../helpers/isRedirectStatusCode"); const tryParseURL_1 = require("../../helpers/tryParseURL"); const findHostnameInContext_1 = require("../../vulnerabilities/ssrf/findHostnameInContext"); const getRedirectOrigin_1 = require("../../vulnerabilities/ssrf/getRedirectOrigin"); const getUrlFromHTTPRequestArgs_1 = require("./getUrlFromHTTPRequestArgs"); /** * We are wrapping the response handler for outgoing HTTP requests to detect redirects. * If the response is a redirect, we will add the redirect to the context to be able to detect SSRF attacks with redirects. */ function wrapResponseHandler(args, module, fn) { return function responseHandler(res) { // If you don't have a response handler, pre node 19, the process will exit // From node 19 onwards, the process will not exit if there is no response handler if ((0, getNodeVersion_1.getMajorNodeVersion)() >= 19) { // Need to attach data & end event handler otherwise the process will not exit // As safety we'll attach the handlers only if there are no listeners if (res.rawListeners("data").length === 0) { res.on("data", () => { }); } if (res.rawListeners("end").length === 0) { res.on("end", () => { }); } } const context = (0, Context_1.getContext)(); if (context) { onHTTPResponse(args, module, res, context); } // eslint-disable-next-line prefer-rest-params fn(...arguments); }; } function onHTTPResponse(args, module, res, context) { if (!res.statusCode || !(0, isRedirectStatusCode_1.isRedirectStatusCode)(res.statusCode)) { return; } if (typeof res.headers.location !== "string") { return; } const destination = (0, tryParseURL_1.tryParseURL)(res.headers.location); if (!destination) { return; } const source = (0, getUrlFromHTTPRequestArgs_1.getUrlFromHTTPRequestArgs)(args, module); if (!source) { return; } addRedirectToContext(source, destination, context); } /** * Adds redirects with user provided hostname / url to the context to prevent SSRF attacks with redirects. */ function addRedirectToContext(source, destination, context) { let redirectOrigin; const sourcePort = (0, getPortFromURL_1.getPortFromURL)(source); // Check if the source hostname is in the context - is true if it's the first redirect in the chain and the user input is the source const found = (0, findHostnameInContext_1.findHostnameInContext)(source.hostname, context, sourcePort); // If the source hostname is not in the context, check if it's a redirect in a already existing chain if (!found && context.outgoingRequestRedirects) { // Get initial source of the redirect chain (first redirect), if url is part of a redirect chain redirectOrigin = (0, getRedirectOrigin_1.getRedirectOrigin)(context.outgoingRequestRedirects, source); } // Get existing redirects or create a new array const outgoingRedirects = context.outgoingRequestRedirects || []; // If it's 1. a initial redirect with user provided url or 2. a redirect in an existing chain, add it to the context if (found || redirectOrigin) { outgoingRedirects.push({ source, destination, }); (0, Context_1.updateContext)(context, "outgoingRequestRedirects", outgoingRedirects); } }