@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.
114 lines (113 loc) • 4.33 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.detectNoSQLInjection = detectNoSQLInjection;
/* eslint-disable max-lines-per-function */
const util_1 = require("util");
const Source_1 = require("../../agent/Source");
const attackPath_1 = require("../../helpers/attackPath");
const isPlainObject_1 = require("../../helpers/isPlainObject");
const tryDecodeAsJWT_1 = require("../../helpers/tryDecodeAsJWT");
const detectDbJsInjection_1 = require("../js-injection/detectDbJsInjection");
function matchFilterPartInUser(userInput, filterPart, pathToPayload = []) {
if (typeof userInput === "string") {
// Check for js injection in $where
if ((0, detectDbJsInjection_1.detectDbJsInjection)(userInput, filterPart)) {
return {
match: true,
pathToPayload: (0, attackPath_1.buildPathToPayload)(pathToPayload),
};
}
const jwt = (0, tryDecodeAsJWT_1.tryDecodeAsJWT)(userInput);
if (jwt.jwt) {
return matchFilterPartInUser(jwt.object, filterPart, pathToPayload.concat([{ type: "jwt" }]));
}
}
if ((0, isPlainObject_1.isPlainObject)(userInput)) {
const filteredInput = removeKeysThatDontStartWithDollarSign(userInput);
if ((0, util_1.isDeepStrictEqual)(filteredInput, filterPart)) {
return { match: true, pathToPayload: (0, attackPath_1.buildPathToPayload)(pathToPayload) };
}
for (const key in userInput) {
const match = matchFilterPartInUser(userInput[key], filterPart, pathToPayload.concat([{ type: "object", key: key }]));
if (match.match) {
return match;
}
}
}
if (Array.isArray(userInput)) {
for (let index = 0; index < userInput.length; index++) {
const match = matchFilterPartInUser(userInput[index], filterPart, pathToPayload.concat([{ type: "array", index: index }]));
if (match.match) {
return match;
}
}
}
return {
match: false,
};
}
function removeKeysThatDontStartWithDollarSign(filter) {
return Object.keys(filter).reduce((acc, key) => {
if (key.startsWith("$")) {
return { ...acc, [key]: filter[key] };
}
return acc;
}, {});
}
function findFilterPartWithOperators(userInput, partOfFilter) {
if ((0, isPlainObject_1.isPlainObject)(partOfFilter)) {
const object = removeKeysThatDontStartWithDollarSign(partOfFilter);
if (Object.keys(object).length > 0) {
const result = matchFilterPartInUser(userInput, object);
if (result.match) {
return {
found: true,
pathToPayload: result.pathToPayload,
payload: object,
};
}
}
for (const key in partOfFilter) {
const result = findFilterPartWithOperators(userInput, partOfFilter[key]);
if (result.found) {
return {
found: true,
pathToPayload: result.pathToPayload,
payload: result.payload,
};
}
}
}
if (Array.isArray(partOfFilter)) {
for (const value of partOfFilter) {
const result = findFilterPartWithOperators(userInput, value);
if (result.found) {
return {
found: true,
pathToPayload: result.pathToPayload,
payload: result.payload,
};
}
}
}
return { found: false };
}
function detectNoSQLInjection(request, filter) {
if (!(0, isPlainObject_1.isPlainObject)(filter) && !Array.isArray(filter)) {
return { injection: false };
}
for (const source of Source_1.SOURCES) {
if (request[source]) {
const result = findFilterPartWithOperators(request[source], filter);
if (result.found) {
return {
injection: true,
source: source,
pathsToPayload: [result.pathToPayload],
payload: result.payload,
};
}
}
}
return { injection: false };
}