@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
JavaScript
;
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;