Skip to content

Commit

Permalink
Use new Zen internals JS parser
Browse files Browse the repository at this point in the history
  • Loading branch information
timokoessler committed Dec 16, 2024
1 parent 36edccb commit 8da3756
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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' }",
})
);
});
Expand Down Expand Up @@ -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",
},
Expand All @@ -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",
},
})
Expand All @@ -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",
},
})
Expand All @@ -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",
},
})
Expand All @@ -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",
},
})
Expand All @@ -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",
},
})
Expand Down
Original file line number Diff line number Diff line change
@@ -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"];

/**
Expand All @@ -12,75 +13,22 @@ export function detectDbJsInjection(
userInput: string,
filterPart: Record<string, unknown>
): 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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 8da3756

Please sign in to comment.