UNPKG

@aikidosec/firewall

Version:

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

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;