Skip to content

Commit

Permalink
Feedback detectNoSQLInjection
Browse files Browse the repository at this point in the history
  • Loading branch information
hansott committed Feb 14, 2024
1 parent cc12f19 commit b4ab813
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 70 deletions.
4 changes: 2 additions & 2 deletions library/src/integrations/MongoDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class MongoDB implements Integration {
source: result.source,
request: request,
stack: new Error().stack || "",
path: result.path,
path: result.pathToPayload,
metadata: {
db: db,
collection: collection,
Expand All @@ -79,7 +79,7 @@ export class MongoDB implements Integration {

if (agent.shouldBlock()) {
throw new Error(
`Aikido guard has blocked a NoSQL injection: MongoDB.Collection.${operation}(...) originating from ${friendlyName(result.source)} (${result.path})`
`Aikido guard has blocked a NoSQL injection: MongoDB.Collection.${operation}(...) originating from ${friendlyName(result.source)} (${result.pathToPayload})`
);
}
}
Expand Down
38 changes: 19 additions & 19 deletions library/src/vulnerabilities/detectNoSQLInjection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ t.test("using $ne in query parameter", async (t) => {
title: { $ne: null },
}
),
{ injection: true, source: "query", path: ".title" }
{ injection: true, source: "query", pathToPayload: ".title" }
);
});

Expand Down Expand Up @@ -122,7 +122,7 @@ t.test("using $ne in body", async (t) => {
title: { $ne: null },
}
),
{ injection: true, source: "body", path: ".title" }
{ injection: true, source: "body", pathToPayload: ".title" }
);
});

Expand All @@ -136,7 +136,7 @@ t.test("using $ne in body (different name)", async (t) => {
myTitle: { $ne: null },
}
),
{ injection: true, source: "body", path: ".title" }
{ injection: true, source: "body", pathToPayload: ".title" }
);
});

Expand All @@ -150,7 +150,7 @@ t.test("using $ne in headers with different name", async (t) => {
someField: { $ne: null },
}
),
{ injection: true, source: "body", path: ".title" }
{ injection: true, source: "body", pathToPayload: ".title" }
);
});

Expand All @@ -171,7 +171,7 @@ t.test("using $ne inside $and", async (t) => {
],
}
),
{ injection: true, source: "body", path: ".title" }
{ injection: true, source: "body", pathToPayload: ".title" }
);
});

Expand All @@ -192,7 +192,7 @@ t.test("using $ne inside $or", async (t) => {
],
}
),
{ injection: true, source: "body", path: ".title" }
{ injection: true, source: "body", pathToPayload: ".title" }
);
});

Expand All @@ -213,7 +213,7 @@ t.test("using $ne inside $nor", async (t) => {
],
}
),
{ injection: true, source: "body", path: ".title" }
{ injection: true, source: "body", pathToPayload: ".title" }
);
});

Expand All @@ -229,7 +229,7 @@ t.test("using $ne inside $not", async (t) => {
},
}
),
{ injection: true, source: "body", path: ".title" }
{ injection: true, source: "body", pathToPayload: ".title" }
);
});

Expand All @@ -248,7 +248,7 @@ t.test("using $ne nested in body", async (t) => {
{
injection: true,
source: "body",
path: ".nested.nested",
pathToPayload: ".nested.nested",
}
);
});
Expand Down Expand Up @@ -279,7 +279,7 @@ t.test("using $ne in JWT in headers", async (t) => {
{
injection: true,
source: "headers",
path: ".Authorization<jwt>.username",
pathToPayload: ".Authorization<jwt>.username",
}
);
});
Expand Down Expand Up @@ -310,7 +310,7 @@ t.test("using $ne in JWT in bearer header", async (t) => {
{
injection: true,
source: "headers",
path: ".Authorization<jwt>.username",
pathToPayload: ".Authorization<jwt>.username",
}
);
});
Expand Down Expand Up @@ -341,7 +341,7 @@ t.test("using $ne in JWT in cookies", async (t) => {
{
injection: true,
source: "cookies",
path: ".session<jwt>.username",
pathToPayload: ".session<jwt>.username",
}
);
});
Expand Down Expand Up @@ -375,7 +375,7 @@ t.test("using $gt in query parameter", async (t) => {
age: { $gt: "21" },
}
),
{ injection: true, source: "query", path: ".age" }
{ injection: true, source: "query", pathToPayload: ".age" }
);
});

Expand All @@ -389,7 +389,7 @@ t.test("using $gt and $lt in query parameter", async (t) => {
age: { $gt: "21", $lt: "100" },
}
),
{ injection: true, source: "body", path: ".age" }
{ injection: true, source: "body", pathToPayload: ".age" }
);
});

Expand All @@ -403,7 +403,7 @@ t.test("using $gt and $lt in query parameter (different name)", async (t) => {
myAge: { $gt: "21", $lt: "100" },
}
),
{ injection: true, source: "body", path: ".age" }
{ injection: true, source: "body", pathToPayload: ".age" }
);
});

Expand All @@ -425,7 +425,7 @@ t.test("using $gt and $lt in query parameter (nested)", async (t) => {
],
}
),
{ injection: true, source: "body", path: ".nested.nested.age" }
{ injection: true, source: "body", pathToPayload: ".nested.nested.age" }
);
});

Expand All @@ -449,7 +449,7 @@ t.test("using $gt and $lt in query parameter (root)", async (t) => {
],
}
),
{ injection: true, source: "body", path: "." }
{ injection: true, source: "body", pathToPayload: "." }
);
});

Expand All @@ -473,7 +473,7 @@ t.test("$where", async (t) => {
],
}
),
{ injection: true, source: "body", path: "." }
{ injection: true, source: "body", pathToPayload: "." }
);
});

Expand All @@ -495,7 +495,7 @@ t.test("array body", async (t) => {
],
}
),
{ injection: true, source: "body", path: ".[0]" }
{ injection: true, source: "body", pathToPayload: ".[0]" }
);
});

Expand Down
97 changes: 48 additions & 49 deletions library/src/vulnerabilities/detectNoSQLInjection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,53 +5,60 @@ import { Context } from "../agent/Context";
import { Source } from "../agent/Source";

type DetectionResult =
| { injection: true; source: Source; path: string }
| { injection: true; source: Source; pathToPayload: string }
| { injection: false };

function matchFilterPartInUser(
user: unknown,
userInput: unknown,
filterPart: Record<string, unknown>,
path = ""
): string | null {
if (typeof user === "string") {
const jwt = tryDecodeAsJWT(user);
pathToPayload = "."
): { match: false } | { match: true; pathToPayload: string } {
if (typeof userInput === "string") {
const jwt = tryDecodeAsJWT(userInput);
if (jwt.jwt) {
return matchFilterPartInUser(jwt.object, filterPart, `${path}<jwt>`);
return matchFilterPartInUser(
jwt.object,
filterPart,
`${pathToPayload.length > 1 ? pathToPayload.slice(0, -1) : pathToPayload}<jwt>.`
);
}
}

if (isDeepStrictEqual(user, filterPart)) {
return path;
if (isDeepStrictEqual(userInput, filterPart)) {
return { match: true, pathToPayload: pathToPayload };
}

if (isPlainObject(user)) {
for (const key in user) {
if (isPlainObject(userInput)) {
for (const key in userInput) {
const match = matchFilterPartInUser(
user[key],
userInput[key],
filterPart,
`${path}.${key}`
`${pathToPayload}${key}.`
);

if (match) {
if (match.match) {
return match;
}
}
}

if (Array.isArray(user)) {
for (let index = 0; index < user.length; index++) {
if (Array.isArray(userInput)) {
for (let index = 0; index < userInput.length; index++) {
const match = matchFilterPartInUser(
user[index],
userInput[index],
filterPart,
`${path}.[${index}]`
`${pathToPayload}[${index}].`
);
if (match) {

if (match.match) {
return match;
}
}
}

return null;
return {
match: false,
};
}

function getObjectWithOperators(
Expand All @@ -67,36 +74,39 @@ function getObjectWithOperators(
}

function findFilterPartWithOperators(
user: unknown,
userInput: unknown,
partOfFilter: unknown
): string | null {
): { found: false } | { found: true; pathToPayload: string } {
if (isPlainObject(partOfFilter)) {
const object = getObjectWithOperators(partOfFilter);
if (Object.keys(object).length > 0) {
const path = matchFilterPartInUser(user, object);
if (path) {
return path;
const result = matchFilterPartInUser(userInput, object);

if (result.match) {
return { found: true, pathToPayload: result.pathToPayload };
}
}

for (const key in partOfFilter) {
const path = findFilterPartWithOperators(user, partOfFilter[key]);
if (path) {
return path;
const result = findFilterPartWithOperators(userInput, partOfFilter[key]);

if (result.found) {
return { found: true, pathToPayload: result.pathToPayload };
}
}
}

if (Array.isArray(partOfFilter)) {
for (const value of partOfFilter) {
const path = findFilterPartWithOperators(user, value);
if (path) {
return path;
const result = findFilterPartWithOperators(userInput, value);

if (result.found) {
return { found: true, pathToPayload: result.pathToPayload };
}
}
}

return null;
return { found: false };
}

export function detectNoSQLInjection(
Expand All @@ -108,28 +118,17 @@ export function detectNoSQLInjection(
}

for (const source of ["body", "query", "headers", "cookies"] as Source[]) {
if (source === "body" && isPlainObject(request[source])) {
const object = getObjectWithOperators(filter);
if (
Object.keys(object).length > 0 &&
isDeepStrictEqual(request[source], object)
) {
return {
injection: true,
source: source,
path: ".",
};
}
}

if (request[source]) {
const path = findFilterPartWithOperators(request[source], filter);
const result = findFilterPartWithOperators(request[source], filter);

if (path) {
if (result.found) {
return {
injection: true,
source: source,
path: path,
pathToPayload:
result.pathToPayload === "."
? "."
: result.pathToPayload.slice(0, -1),
};
}
}
Expand Down

0 comments on commit b4ab813

Please sign in to comment.