UNPKG

@aikidosec/firewall

Version:

Zen by Aikido is an embedded Application Firewall that autonomously protects Node.js apps against common and critical attacks, provides rate limiting, detects malicious traffic (including bots), and more.

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); } }