@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.
58 lines (57 loc) • 2.53 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractStringsFromUserInput = extractStringsFromUserInput;
const isPlainObject_1 = require("./isPlainObject");
const safeDecodeURIComponent_1 = require("./safeDecodeURIComponent");
const tryDecodeAsJWT_1 = require("./tryDecodeAsJWT");
// Prevent stack overflow from deeply nested objects
// An attacker can include a large nested payload as part of a normal body
// extractStringsFromUserInput will trigger a max call stack size,
// the error will be caught, but it stops our inspection
const MAX_DEPTH = 1024;
function extractStringsFromUserInput(obj, depth = 0) {
const results = new Set();
if (depth >= MAX_DEPTH) {
return results;
}
if ((0, isPlainObject_1.isPlainObject)(obj)) {
for (const key in obj) {
results.add(key);
extractStringsFromUserInput(obj[key], depth + 1).forEach((value) => {
results.add(value);
});
}
}
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
extractStringsFromUserInput(obj[i], depth + 1).forEach((value) => results.add(value));
}
// Add array as string to results
// This prevents bypassing the firewall by HTTP Parameter Pollution
// Example: ?param=value1¶m=value2 will be treated as array by express
// If its used inside a string, it will be converted to a comma separated string
results.add(obj.join());
}
if (typeof obj === "string" && obj.length > 0) {
results.add(obj);
if (obj.includes("%") && obj.length >= 3) {
const r = (0, safeDecodeURIComponent_1.safeDecodeURIComponent)(obj);
if (r && r !== obj) {
// Only add if the decoded value is different from the original, to avoid duplicates in results
// This improves the performance of all injection tests
results.add(r);
}
}
const jwt = (0, tryDecodeAsJWT_1.tryDecodeAsJWT)(obj);
if (jwt.jwt) {
// Do not add the issuer of the JWT as a string because it can contain a domain / url and produce false positives
if (jwt.object && typeof jwt.object === "object" && "iss" in jwt.object) {
delete jwt.object.iss;
}
extractStringsFromUserInput(jwt.object, depth + 1).forEach((value) => {
results.add(value);
});
}
}
return results;
}