@aikidosec/firewall
Version:
Zen by Aikido is an embedded Web Application Firewall that autonomously protects Node.js apps against common and critical attacks
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 };
}