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.

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;