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.

123 lines (122 loc) 5.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HTTPRequest = void 0; const dns_1 = require("dns"); const Context_1 = require("../agent/Context"); const getPortFromURL_1 = require("../helpers/getPortFromURL"); const checkContextForSSRF_1 = require("../vulnerabilities/ssrf/checkContextForSSRF"); const inspectDNSLookupCalls_1 = require("../vulnerabilities/ssrf/inspectDNSLookupCalls"); const isRedirectToPrivateIP_1 = require("../vulnerabilities/ssrf/isRedirectToPrivateIP"); const getUrlFromHTTPRequestArgs_1 = require("./http-request/getUrlFromHTTPRequestArgs"); const wrapResponseHandler_1 = require("./http-request/wrapResponseHandler"); const wrapExport_1 = require("../agent/hooks/wrapExport"); const isOptionsObject_1 = require("./http-request/isOptionsObject"); class HTTPRequest { inspectHostname(agent, url, port, module) { // Let the agent know that we are connecting to this hostname // This is to build a list of all hostnames that the application is connecting to if (typeof port === "number" && port > 0) { agent.onConnectHostname(url.hostname, port); } const context = (0, Context_1.getContext)(); if (!context) { return undefined; } // Check if the hostname is inside the context const foundDirectSSRF = (0, checkContextForSSRF_1.checkContextForSSRF)({ hostname: url.hostname, operation: `${module}.request`, context: context, port: port, }); if (foundDirectSSRF) { return foundDirectSSRF; } // Check if the hostname is a private IP and if it's a redirect that was initiated by user input const foundSSRFRedirect = (0, isRedirectToPrivateIP_1.isRedirectToPrivateIP)(url, context); if (foundSSRFRedirect) { return { operation: `${module}.request`, kind: "ssrf", source: foundSSRFRedirect.source, pathsToPayload: foundSSRFRedirect.pathsToPayload, metadata: {}, payload: foundSSRFRedirect.payload, }; } return undefined; } inspectHttpRequest(args, agent, module) { if (args.length <= 0) { return undefined; } const url = (0, getUrlFromHTTPRequestArgs_1.getUrlFromHTTPRequestArgs)(args, module); if (!url) { return undefined; } if (url.hostname.length > 0) { const attack = this.inspectHostname(agent, url, (0, getPortFromURL_1.getPortFromURL)(url), module); if (attack) { return attack; } } return undefined; } monitorDNSLookups(args, agent, module) { const context = (0, Context_1.getContext)(); if (!context) { return args; } const optionObj = args.find((arg) => (0, isOptionsObject_1.isOptionsObject)(arg)); const url = (0, getUrlFromHTTPRequestArgs_1.getUrlFromHTTPRequestArgs)(args, module); const stackTraceCallingLocation = new Error(); if (!optionObj) { const newOpts = { lookup: (0, inspectDNSLookupCalls_1.inspectDNSLookupCalls)(dns_1.lookup, agent, module, `${module}.request`, url, stackTraceCallingLocation), }; // You can also pass on response event handler as a callback as the second argument // But if the options object is added at the third position, it will be ignored if (args.length === 2 && typeof args[1] === "function") { return [args[0], newOpts, args[1]]; } return args.concat(newOpts); } let nativeLookup = dns_1.lookup; if ("lookup" in optionObj && typeof optionObj.lookup === "function") { // If the user has passed a custom lookup function, we'll use that instead nativeLookup = optionObj.lookup; } optionObj.lookup = (0, inspectDNSLookupCalls_1.inspectDNSLookupCalls)(nativeLookup, agent, module, `${module}.request`, url, stackTraceCallingLocation); return args; } wrapResponseHandler(args, module) { if (args.find((arg) => typeof arg === "function")) { return args.map((arg) => { if (typeof arg === "function") { return (0, wrapResponseHandler_1.wrapResponseHandler)(args, module, arg); } return arg; }); } return args.concat([(0, wrapResponseHandler_1.wrapResponseHandler)(args, module, () => { })]); } wrap(hooks) { const modules = ["http", "https"]; const methods = ["request", "get"]; for (const module of modules) { hooks.addBuiltinModule(module).onRequire((exports, pkgInfo) => { for (const method of methods) { (0, wrapExport_1.wrapExport)(exports, method, pkgInfo, { kind: "outgoing_http_op", // Whenever a request is made, we'll check the hostname whether it's a private IP inspectArgs: (args, agent) => this.inspectHttpRequest(args, agent, module), // Whenever a request is made, we'll modify the options to pass a custom lookup function // that will inspect resolved IP address (and thus preventing TOCTOU attacks) modifyArgs: (args, agent) => this.monitorDNSLookups(args, agent, module), }); } }); } } } exports.HTTPRequest = HTTPRequest;