@aikidosec/firewall
Version:
Zen by Aikido is an embedded Web Application Firewall that autonomously protects Node.js apps against common and critical attacks
92 lines (91 loc) • 2.93 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildPathToPayload = buildPathToPayload;
exports.getPathsToPayload = getPathsToPayload;
const isPlainObject_1 = require("./isPlainObject");
const tryDecodeAsJWT_1 = require("./tryDecodeAsJWT");
// Default match count to return
const DEFAULT_MATCH_COUNT = 1;
// Maximum depth to traverse
const MAX_DEPTH = 30;
// Maximum array length to traverse
const MAX_ARRAY_LENGTH = 100;
function buildPathToPayload(pathToPayload) {
if (pathToPayload.length === 0) {
return ".";
}
return pathToPayload.reduce((acc, part) => {
if (part.type === "jwt") {
return `${acc}<jwt>`;
}
if (part.type === "object") {
return `${acc}.${part.key}`;
}
if (part.type === "array") {
return `${acc}.[${part.index}]`;
}
/* c8 ignore next */
return acc;
}, "");
}
class Matches {
constructor(max) {
this.max = max;
this.matches = [];
if (max < 1) {
throw new Error("Max must be greater than 0");
}
}
add(path) {
this.matches.push(buildPathToPayload(path));
}
getMatches() {
return this.matches;
}
found() {
return this.matches.length >= this.max;
}
}
function getPathsToPayload(attackPayload, obj, matchCount = DEFAULT_MATCH_COUNT) {
const matches = new Matches(matchCount);
const attackPayloadLowercase = attackPayload.toLowerCase();
const traverse = (value, path = [], depth = 0) => {
if (matches.found() || depth > MAX_DEPTH) {
return;
}
if (typeof value === "string") {
if (value.toLowerCase() === attackPayloadLowercase) {
matches.add(path);
return;
}
const jwt = (0, tryDecodeAsJWT_1.tryDecodeAsJWT)(value);
if (jwt.jwt) {
traverse(jwt.object, path.concat({ type: "jwt" }), depth + 1);
}
}
if (Array.isArray(value)) {
if (value.length > 1 &&
value.length < MAX_ARRAY_LENGTH &&
value.join().toLowerCase() === attackPayloadLowercase) {
matches.add(path);
return;
}
for (const [index, item] of value.entries()) {
if (matches.found() || index > MAX_ARRAY_LENGTH) {
break;
}
traverse(item, path.concat({ type: "array", index }), depth);
}
}
if ((0, isPlainObject_1.isPlainObject)(value)) {
for (const key in value) {
if (matches.found()) {
break;
}
traverse(value[key], path.concat({ type: "object", key }), depth + 1);
}
}
};
traverse(obj);
return matches.getMatches();
}