From 49975311de2bb0ad7a7660196eefe0d3b3f639ae Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Wed, 18 Dec 2024 11:03:43 +0100 Subject: [PATCH 1/5] Add breaking test --- library/helpers/attackPath.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/helpers/attackPath.test.ts b/library/helpers/attackPath.test.ts index d1b81e05..5e9d62ce 100644 --- a/library/helpers/attackPath.test.ts +++ b/library/helpers/attackPath.test.ts @@ -87,3 +87,7 @@ t.test("respects max depth and array length", async (t) => { t.same(get("100", testArr), [".[100]"]); t.same(get("101", testArr), []); }); + +t.test("first item in array", async (t) => { + t.same(get("id = 1", ["id = 1"]), [".[0]"]); +}); From 17d8b325a48fee8f71894caea7b7d853a48b9483 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Wed, 18 Dec 2024 11:11:41 +0100 Subject: [PATCH 2/5] Add failing tests --- library/helpers/attackPath.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/library/helpers/attackPath.test.ts b/library/helpers/attackPath.test.ts index 5e9d62ce..3b3e90fd 100644 --- a/library/helpers/attackPath.test.ts +++ b/library/helpers/attackPath.test.ts @@ -1,6 +1,11 @@ import * as t from "tap"; import { getPathsToPayload as get } from "./attackPath"; +t.test("it throws error if max matches is less than 1", async (t) => { + t.throws(() => get("payload", {}, 0)); + t.throws(() => get("payload", {}, -1)); +}); + t.test("it gets paths to payload", async (t) => { const testObj1 = { a: { @@ -91,3 +96,15 @@ t.test("respects max depth and array length", async (t) => { t.test("first item in array", async (t) => { t.same(get("id = 1", ["id = 1"]), [".[0]"]); }); + +t.test("it checks max matches when iterating over object props", async (t) => { + const testObj = { + a: ["test"], + b: ["test"], + c: ["test"], + }; + + t.same(get("test", testObj, 1), [".a.[0]"]); + t.same(get("test", testObj, 2), [".a.[0]", ".b.[0]"]); + t.same(get("test", testObj, 3), [".a.[0]", ".b.[0]", ".c.[0]"]); +}); From 8b33e433a64c54ace6c4b8ad798e5a3b6a7ceef7 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Wed, 18 Dec 2024 11:11:54 +0100 Subject: [PATCH 3/5] Fix tests for attackPath --- library/helpers/attackPath.ts | 56 +++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/library/helpers/attackPath.ts b/library/helpers/attackPath.ts index d690127c..b177a120 100644 --- a/library/helpers/attackPath.ts +++ b/library/helpers/attackPath.ts @@ -37,17 +37,38 @@ export function buildPathToPayload(pathToPayload: PathPart[]): string { }, ""); } +class Matches { + private readonly matches: string[] = []; + + constructor(private readonly max: number) { + if (max < 1) { + throw new Error("Max must be greater than 0"); + } + } + + addMatch(path: PathPart[]) { + this.matches.push(buildPathToPayload(path)); + } + + getMatches() { + return this.matches; + } + + reachedMax() { + return this.matches.length >= this.max; + } +} + export function getPathsToPayload( attackPayload: string, obj: unknown, matchCount = DEFAULT_MATCH_COUNT ): string[] { - const matches: string[] = []; - + const matches = new Matches(matchCount); const attackPayloadLowercase = attackPayload.toLowerCase(); const traverse = (value: unknown, path: PathPart[] = [], depth = 0) => { - if (matches.length >= matchCount) { + if (matches.reachedMax()) { return; } @@ -55,10 +76,9 @@ export function getPathsToPayload( return; } - // Handle strings if (typeof value === "string") { if (value.toLowerCase() === attackPayloadLowercase) { - matches.push(buildPathToPayload(path)); + matches.addMatch(path); return; } @@ -71,30 +91,40 @@ export function getPathsToPayload( } if (Array.isArray(value)) { - // Handle arrays + if ( + value.length > 1 && + value.length < MAX_ARRAY_LENGTH && + value.join().toLowerCase() === attackPayloadLowercase + ) { + matches.addMatch(path); + return; + } + for (const [index, item] of value.entries()) { - if (index > MAX_ARRAY_LENGTH) { + if (matches.reachedMax() || index > MAX_ARRAY_LENGTH) { break; } - traverse(item, path.concat({ type: "array", index }), depth); - } - if (value.join().toLowerCase() === attackPayloadLowercase) { - matches.push(buildPathToPayload(path)); + traverse(item, path.concat({ type: "array", index }), depth); } return; } if (isPlainObject(value)) { - // Handle objects for (const key in value) { + if (matches.reachedMax()) { + break; + } + traverse(value[key], path.concat({ type: "object", key }), depth + 1); } + + return; } }; traverse(obj); - return matches; + return matches.getMatches(); } From f1cd8574f2ab013193d1454bf4b10bfa4a10124f Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Wed, 18 Dec 2024 11:13:18 +0100 Subject: [PATCH 4/5] Shorten methods --- library/helpers/attackPath.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/library/helpers/attackPath.ts b/library/helpers/attackPath.ts index b177a120..efc5c89a 100644 --- a/library/helpers/attackPath.ts +++ b/library/helpers/attackPath.ts @@ -46,7 +46,7 @@ class Matches { } } - addMatch(path: PathPart[]) { + add(path: PathPart[]) { this.matches.push(buildPathToPayload(path)); } @@ -54,7 +54,7 @@ class Matches { return this.matches; } - reachedMax() { + found() { return this.matches.length >= this.max; } } @@ -68,7 +68,7 @@ export function getPathsToPayload( const attackPayloadLowercase = attackPayload.toLowerCase(); const traverse = (value: unknown, path: PathPart[] = [], depth = 0) => { - if (matches.reachedMax()) { + if (matches.found()) { return; } @@ -78,7 +78,7 @@ export function getPathsToPayload( if (typeof value === "string") { if (value.toLowerCase() === attackPayloadLowercase) { - matches.addMatch(path); + matches.add(path); return; } @@ -96,12 +96,12 @@ export function getPathsToPayload( value.length < MAX_ARRAY_LENGTH && value.join().toLowerCase() === attackPayloadLowercase ) { - matches.addMatch(path); + matches.add(path); return; } for (const [index, item] of value.entries()) { - if (matches.reachedMax() || index > MAX_ARRAY_LENGTH) { + if (matches.found() || index > MAX_ARRAY_LENGTH) { break; } @@ -113,7 +113,7 @@ export function getPathsToPayload( if (isPlainObject(value)) { for (const key in value) { - if (matches.reachedMax()) { + if (matches.found()) { break; } From d1917f7bebd97942e832e514dee91ba2d96858ac Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Wed, 18 Dec 2024 11:15:52 +0100 Subject: [PATCH 5/5] Shorten function --- library/helpers/attackPath.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/library/helpers/attackPath.ts b/library/helpers/attackPath.ts index efc5c89a..5f045b84 100644 --- a/library/helpers/attackPath.ts +++ b/library/helpers/attackPath.ts @@ -68,11 +68,7 @@ export function getPathsToPayload( const attackPayloadLowercase = attackPayload.toLowerCase(); const traverse = (value: unknown, path: PathPart[] = [], depth = 0) => { - if (matches.found()) { - return; - } - - if (depth > MAX_DEPTH) { + if (matches.found() || depth > MAX_DEPTH) { return; } @@ -86,8 +82,6 @@ export function getPathsToPayload( if (jwt.jwt) { traverse(jwt.object, path.concat({ type: "jwt" }), depth + 1); } - - return; } if (Array.isArray(value)) { @@ -107,8 +101,6 @@ export function getPathsToPayload( traverse(item, path.concat({ type: "array", index }), depth); } - - return; } if (isPlainObject(value)) { @@ -119,8 +111,6 @@ export function getPathsToPayload( traverse(value[key], path.concat({ type: "object", key }), depth + 1); } - - return; } };