From 275f247003b2735a6f55289df23a04f31857a985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6ssler?= Date: Mon, 9 Dec 2024 17:04:32 +0100 Subject: [PATCH] Add max depth and array size --- library/helpers/attackPath.test.ts | 27 +++++++++++++++++++++++++++ library/helpers/attackPath.ts | 25 +++++++++++++++++++------ 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/library/helpers/attackPath.test.ts b/library/helpers/attackPath.test.ts index b4e3ac54..1fbf9eba 100644 --- a/library/helpers/attackPath.test.ts +++ b/library/helpers/attackPath.test.ts @@ -60,3 +60,30 @@ t.test("set max count", async (t) => { t.same(get("test", testArr, 5), [".[0]", ".[1]", ".[2]", ".[3]", ".[4]"]); }); + +t.test("respects max depth and array length", async (t) => { + const generateTestObjectWithDepth = (depth: number): object | string => { + if (depth === 0) { + return "testValue"; + } + + const obj = { + prop: generateTestObjectWithDepth(depth - 1), + }; + + return obj; + }; + + t.same(get("testValue", generateTestObjectWithDepth(100)), []); + t.same(get("testValue", generateTestObjectWithDepth(31)), []); + t.same(get("testValue", generateTestObjectWithDepth(30)), [ + ".prop".repeat(30), + ]); + + const testArr = Array.from({ length: 101 }, (_, i) => i.toString()); + + t.same(get("50", testArr), [".[50]"]); + t.same(get("99", testArr), [".[99]"]); + t.same(get("100", testArr), [".[100]"]); + t.same(get("101", testArr), []); +}); diff --git a/library/helpers/attackPath.ts b/library/helpers/attackPath.ts index 03214dcc..8107e013 100644 --- a/library/helpers/attackPath.ts +++ b/library/helpers/attackPath.ts @@ -4,6 +4,12 @@ import { tryDecodeAsJWT } from "./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; + export type PathPart = | { type: "jwt" } | { type: "object"; key: string } @@ -40,11 +46,15 @@ export function getPathsToPayload( const attackPayloadLowercase = attackPayload.toLowerCase(); - const traverse = (value: unknown, path: PathPart[] = []) => { + const traverse = (value: unknown, path: PathPart[] = [], depth = 0) => { if (matches.length >= matchCount) { return; } + if (depth > MAX_DEPTH) { + return; + } + // Handle strings if (typeof value === "string") { if (value.toLowerCase() === attackPayloadLowercase) { @@ -54,7 +64,7 @@ export function getPathsToPayload( const jwt = tryDecodeAsJWT(value); if (jwt.jwt) { - traverse(jwt.object, path.concat({ type: "jwt" })); + traverse(jwt.object, path.concat({ type: "jwt" }), depth + 1); } return; @@ -62,9 +72,12 @@ export function getPathsToPayload( if (Array.isArray(value)) { // Handle arrays - value.forEach((item, index) => { - traverse(item, path.concat({ type: "array", index })); - }); + for (const [index, item] of value.entries()) { + if (index > MAX_ARRAY_LENGTH) { + break; + } + traverse(item, path.concat({ type: "array", index }), depth + 1); + } if (value.join().toLowerCase() === attackPayloadLowercase) { matches.push(buildPathToPayload(path)); @@ -76,7 +89,7 @@ export function getPathsToPayload( if (isPlainObject(value)) { // Handle objects for (const key in value) { - traverse(value[key], path.concat({ type: "object", key })); + traverse(value[key], path.concat({ type: "object", key }), depth + 1); } } };