diff --git a/library/vulnerabilities/nosql-injection/detectDbJsInjection.test.ts b/library/vulnerabilities/js-injection/detectDbJsInjection.test.ts similarity index 86% rename from library/vulnerabilities/nosql-injection/detectDbJsInjection.test.ts rename to library/vulnerabilities/js-injection/detectDbJsInjection.test.ts index 848b318bd..7dc6fc4bc 100644 --- a/library/vulnerabilities/nosql-injection/detectDbJsInjection.test.ts +++ b/library/vulnerabilities/js-injection/detectDbJsInjection.test.ts @@ -22,19 +22,20 @@ t.test("detects injection in $where operator", async (t) => { t.ok( detectDbJsInjection("a` && sleep(2000) && `b", { - $where: "this.name === `a` && sleep(2000) && `b'", + $where: "this.name === `a` && sleep(2000) && `b`", }) ); t.ok( detectDbJsInjection("a' && sleep(2000) && 'b", { - $where: "this.name === 'a' && sleep(2000) && 'b'\"", + $where: "this.name === 'a' && sleep(2000) && 'b'", }) ); t.ok( detectDbJsInjection("a' && sleep(2000) && 'b", { - $where: "function() { return this.name === 'a' && sleep(2000) && 'b' }", + $where: + "function test() { return this.name === 'a' && sleep(2000) && 'b' }", }) ); }); @@ -90,7 +91,7 @@ t.test("detects injection in $function operator", async (t) => { t.ok( detectDbJsInjection("a` && sleep(2000) && `b", { $function: { - body: "this.name === `a` && sleep(2000) && `b'", + body: "this.name === `a` && sleep(2000) && `b`", args: [], lang: "js", }, @@ -117,7 +118,7 @@ t.test("detects injection in $accumulator operator", async (t) => { t.ok( detectDbJsInjection("a` && sleep(2000) && `b", { $accumulator: { - init: "this.name === `a` && sleep(2000) && `b'", + init: "this.name === `a` && sleep(2000) && `b`", lang: "js", }, }) @@ -126,7 +127,7 @@ t.test("detects injection in $accumulator operator", async (t) => { t.ok( detectDbJsInjection("a` && sleep(2000) && `b", { $accumulator: { - accumulate: "this.name === `a` && sleep(2000) && `b'", + accumulate: "this.name === `a` && sleep(2000) && `b`", lang: "js", }, }) @@ -135,7 +136,7 @@ t.test("detects injection in $accumulator operator", async (t) => { t.ok( detectDbJsInjection("a` && sleep(2000) && `b", { $accumulator: { - merge: "this.name === `a` && sleep(2000) && `b'", + merge: "this.name === `a` && sleep(2000) && `b`", lang: "js", }, }) @@ -144,7 +145,7 @@ t.test("detects injection in $accumulator operator", async (t) => { t.ok( detectDbJsInjection("a` && sleep(2000) && `b", { $accumulator: { - finalize: "this.name === `a` && sleep(2000) && `b'", + finalize: "this.name === `a` && sleep(2000) && `b`", lang: "js", }, }) @@ -153,9 +154,9 @@ t.test("detects injection in $accumulator operator", async (t) => { t.ok( detectDbJsInjection("a` && sleep(2000) && `b", { $accumulator: { - init: "function() { return true; }", + init: "function a() { return true; }", finalize: - "function() { return this.name === `a` && sleep(2000) && `b' }", + "function b() { return this.name === `a` && sleep(2000) && `b` }", lang: "js", }, }) diff --git a/library/vulnerabilities/nosql-injection/detectDbJsInjection.ts b/library/vulnerabilities/js-injection/detectDbJsInjection.ts similarity index 58% rename from library/vulnerabilities/nosql-injection/detectDbJsInjection.ts rename to library/vulnerabilities/js-injection/detectDbJsInjection.ts index 9d993ab99..46a8e7e93 100644 --- a/library/vulnerabilities/nosql-injection/detectDbJsInjection.ts +++ b/library/vulnerabilities/js-injection/detectDbJsInjection.ts @@ -1,6 +1,7 @@ -import { getCurrentAndNextSegments } from "../../helpers/getCurrentAndNextSegments"; import { isPlainObject } from "../../helpers/isPlainObject"; +import { detectJsInjection } from "./detectJsInjection"; +// Operators accepting server-side JS code const serverSideJsFunctions = ["$where", "$accumulator", "$function"]; /** @@ -12,75 +13,22 @@ export function detectDbJsInjection( userInput: string, filterPart: Record ): boolean { - if (userInput.length < 1) { - // We ignore single characters since they don't pose a big threat. - return false; - } - for (const [key, value] of Object.entries(filterPart)) { if (!serverSideJsFunctions.includes(key)) { continue; } - const strToCheck = extractStringToCheck(key, value); - if (typeof strToCheck !== "string" || strToCheck.length < 1) { - continue; - } - - // We ignore cases where the user input is longer than the command. - // Because the user input can't be part of the command. - if (userInput.length > strToCheck.length) { - continue; - } - - // User input is not part of the command - if (!strToCheck.includes(userInput)) { - continue; - } - - // User input is safely encapsulated - if (isSafelyEncapsulated(strToCheck, userInput)) { + const jsCode = extractStringToCheck(key, value); + if (typeof jsCode !== "string" || jsCode.length < 1) { continue; } - return true; + return detectJsInjection(jsCode, userInput); } return false; } -const escapeChars = ['"', "'", "`"]; - -/** - * Check if the user input is safely encapsulated in the query - */ -function isSafelyEncapsulated(filterString: string, userInput: string) { - return getCurrentAndNextSegments(filterString.split(userInput)).every( - ({ currentSegment, nextSegment }) => { - const charBeforeUserInput = currentSegment.slice(-1); - const charAfterUserInput = nextSegment.slice(0, 1); - - const isEscapeChar = escapeChars.find( - (char) => char === charBeforeUserInput - ); - - if (!isEscapeChar) { - return false; - } - - if (charBeforeUserInput !== charAfterUserInput) { - return false; - } - - if (userInput.includes(charBeforeUserInput)) { - return false; - } - - return true; - } - ); -} - /** * Gets the code string to check for injections from a $where, $function or $accumulator object */ diff --git a/library/vulnerabilities/nosql-injection/detectNoSQLInjection.test.ts b/library/vulnerabilities/nosql-injection/detectNoSQLInjection.test.ts index 377b8b32f..6622addc4 100644 --- a/library/vulnerabilities/nosql-injection/detectNoSQLInjection.test.ts +++ b/library/vulnerabilities/nosql-injection/detectNoSQLInjection.test.ts @@ -788,3 +788,38 @@ t.test("it does not detect", async () => { } ); }); + +t.test("$where js inject sleep", async (t) => { + t.same( + detectNoSQLInjection( + createContext({ + body: { name: "a' && sleep(2000) && 'b" }, + }), + { + $where: "this.name === 'a' && sleep(2000) && 'b'", + } + ), + { + injection: true, + source: "body", + pathToPayload: ".name", + payload: { $where: "this.name === 'a' && sleep(2000) && 'b'" }, + } + ); +}); + +t.test("does not detect if not a string (js injection)", async (t) => { + t.same( + detectNoSQLInjection( + createContext({ + body: { test: 123 }, + }), + { + $where: "this.name === 123", + } + ), + { + injection: false, + } + ); +}); diff --git a/library/vulnerabilities/nosql-injection/detectNoSQLInjection.ts b/library/vulnerabilities/nosql-injection/detectNoSQLInjection.ts index d2b61ac90..5ccfee9dd 100644 --- a/library/vulnerabilities/nosql-injection/detectNoSQLInjection.ts +++ b/library/vulnerabilities/nosql-injection/detectNoSQLInjection.ts @@ -5,7 +5,7 @@ import { Source, SOURCES } from "../../agent/Source"; import { buildPathToPayload, PathPart } from "../../helpers/attackPath"; import { isPlainObject } from "../../helpers/isPlainObject"; import { tryDecodeAsJWT } from "../../helpers/tryDecodeAsJWT"; -import { detectDbJsInjection } from "./detectDbJsInjection"; +import { detectDbJsInjection } from "../js-injection/detectDbJsInjection"; function matchFilterPartInUser( userInput: unknown,