UNPKG

@aikidosec/firewall

Version:

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

102 lines (101 loc) 4.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Undici = void 0; const dns_1 = require("dns"); const AgentSingleton_1 = require("../agent/AgentSingleton"); const Context_1 = require("../agent/Context"); const getNodeVersion_1 = require("../helpers/getNodeVersion"); const isVersionGreaterOrEqual_1 = require("../helpers/isVersionGreaterOrEqual"); const checkContextForSSRF_1 = require("../vulnerabilities/ssrf/checkContextForSSRF"); const inspectDNSLookupCalls_1 = require("../vulnerabilities/ssrf/inspectDNSLookupCalls"); const wrapDispatch_1 = require("./undici/wrapDispatch"); const wrapExport_1 = require("../agent/hooks/wrapExport"); const getHostnameAndPortFromArgs_1 = require("./undici/getHostnameAndPortFromArgs"); const methods = [ "request", "stream", "pipeline", "connect", "fetch", "upgrade", ]; class Undici { inspectHostname(agent, hostname, port, method) { // 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(hostname, port); } const context = (0, Context_1.getContext)(); if (!context) { return undefined; } return (0, checkContextForSSRF_1.checkContextForSSRF)({ hostname: hostname, operation: `undici.${method}`, context, port, }); } inspect(args, agent, method) { const hostnameAndPort = (0, getHostnameAndPortFromArgs_1.getHostnameAndPortFromArgs)(args); if (hostnameAndPort) { const attack = this.inspectHostname(agent, hostnameAndPort.hostname, hostnameAndPort.port, method); if (attack) { return attack; } } return undefined; } patchGlobalDispatcher(agent, undiciModule) { const dispatcher = new undiciModule.Agent({ connect: { lookup: (0, inspectDNSLookupCalls_1.inspectDNSLookupCalls)(dns_1.lookup, agent, "undici", // We don't know the method here, so we just use "undici.[method]" "undici.[method]"), }, }); dispatcher.dispatch = (0, wrapDispatch_1.wrapDispatch)(dispatcher.dispatch, agent); // We'll set a global dispatcher that will inspect the resolved IP address (and thus preventing TOCTOU attacks) undiciModule.setGlobalDispatcher(dispatcher); } wrap(hooks) { if (!(0, isVersionGreaterOrEqual_1.isVersionGreaterOrEqual)("16.8.0", (0, getNodeVersion_1.getSemverNodeVersion)())) { // Undici requires Node.js 16.8+ (due to web streams) return; } hooks .addPackage("undici") .withVersion("^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0") .onRequire((exports, pkgInfo) => { const agent = (0, AgentSingleton_1.getInstance)(); if (!agent) { // No agent, we can't do anything return; } // Immediately patch the global dispatcher before returning the module // The global dispatcher might be overwritten by the user // But at least they have a reference to our dispatcher instead of the original one // (In case the user has a custom dispatcher that conditionally calls the original dispatcher) this.patchGlobalDispatcher(agent, exports); // Print a warning that we can't provide protection if setGlobalDispatcher is called (0, wrapExport_1.wrapExport)(exports, "setGlobalDispatcher", pkgInfo, { kind: undefined, inspectArgs: (_, agent) => { agent.log(`undici.setGlobalDispatcher(..) was called, we can't guarantee protection!`); }, }); // Wrap all methods that can make requests 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) => { return this.inspect(args, agent, method); }, }); } }); } } exports.Undici = Undici;