From bef4534b69272214089d5d8498d871fbf9ca665a Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Mon, 11 Mar 2024 09:15:23 +0100 Subject: [PATCH 1/2] Remove unused imports --- .../vulnerabilities/sql-injection/detectSQLInjection.test.ts | 1 - .../src/vulnerabilities/sql-injection/detectSQLInjection.ts | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/library/src/vulnerabilities/sql-injection/detectSQLInjection.test.ts b/library/src/vulnerabilities/sql-injection/detectSQLInjection.test.ts index 4e08973bc..e7df99c33 100644 --- a/library/src/vulnerabilities/sql-injection/detectSQLInjection.test.ts +++ b/library/src/vulnerabilities/sql-injection/detectSQLInjection.test.ts @@ -1,7 +1,6 @@ import { basename, join } from "path"; import * as t from "tap"; import { readFileSync } from "fs"; -import * as path from "path"; import { dangerousCharsInInput } from "./dangerousCharsInInput"; import { detectSQLInjection, diff --git a/library/src/vulnerabilities/sql-injection/detectSQLInjection.ts b/library/src/vulnerabilities/sql-injection/detectSQLInjection.ts index cb8c5e8c4..fc0e13d4e 100644 --- a/library/src/vulnerabilities/sql-injection/detectSQLInjection.ts +++ b/library/src/vulnerabilities/sql-injection/detectSQLInjection.ts @@ -1,7 +1,6 @@ -import { Agent } from "../../agent/Agent"; import { Context } from "../../agent/Context"; import { InterceptorResult } from "../../agent/hooks/MethodInterceptor"; -import { sourceHumanName, Source } from "../../agent/Source"; +import { Source } from "../../agent/Source"; import { extractStringsFromUserInput } from "../../helpers/extractStringsFromUserInput"; import { SQL_STRING_CHARS } from "./config"; import { dangerousCharsInInput } from "./dangerousCharsInInput"; From bf35574b4b88072ab114ad14a249ef89a92a67d8 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Mon, 11 Mar 2024 09:20:50 +0100 Subject: [PATCH 2/2] Move functions to separate files --- library/src/sinks/MySQL.ts | 2 +- library/src/sinks/MySQL2.ts | 2 +- library/src/sinks/Postgres.ts | 2 +- .../checkContextForSqlInjection.ts | 36 ++++++++ .../sql-injection/detectSQLInjection.test.ts | 8 +- .../sql-injection/detectSQLInjection.ts | 82 +------------------ .../sql-injection/queryContainsUserInput.ts | 13 +++ .../userInputOccurrencesSafelyEncapsulated.ts | 31 +++++++ 8 files changed, 88 insertions(+), 88 deletions(-) create mode 100644 library/src/vulnerabilities/sql-injection/checkContextForSqlInjection.ts create mode 100644 library/src/vulnerabilities/sql-injection/queryContainsUserInput.ts create mode 100644 library/src/vulnerabilities/sql-injection/userInputOccurrencesSafelyEncapsulated.ts diff --git a/library/src/sinks/MySQL.ts b/library/src/sinks/MySQL.ts index 5d7b690e7..8ca39b5b4 100644 --- a/library/src/sinks/MySQL.ts +++ b/library/src/sinks/MySQL.ts @@ -3,7 +3,7 @@ import { Context } from "../agent/Context"; import { Hooks } from "../agent/hooks/Hooks"; import { InterceptorResult } from "../agent/hooks/MethodInterceptor"; import { Wrapper } from "../agent/Wrapper"; -import { checkContextForSqlInjection } from "../vulnerabilities/sql-injection/detectSQLInjection"; +import { checkContextForSqlInjection } from "../vulnerabilities/sql-injection/checkContextForSqlInjection"; export class MySQL implements Wrapper { private inspectQuery( diff --git a/library/src/sinks/MySQL2.ts b/library/src/sinks/MySQL2.ts index e1c4fa23f..2387a4d27 100644 --- a/library/src/sinks/MySQL2.ts +++ b/library/src/sinks/MySQL2.ts @@ -3,7 +3,7 @@ import { Context } from "../agent/Context"; import { Hooks } from "../agent/hooks/Hooks"; import { InterceptorResult } from "../agent/hooks/MethodInterceptor"; import { Wrapper } from "../agent/Wrapper"; -import { checkContextForSqlInjection } from "../vulnerabilities/sql-injection/detectSQLInjection"; +import { checkContextForSqlInjection } from "../vulnerabilities/sql-injection/checkContextForSqlInjection"; export class MySQL2 implements Wrapper { private inspectQuery( diff --git a/library/src/sinks/Postgres.ts b/library/src/sinks/Postgres.ts index 79b71f5f2..931b2e3b9 100644 --- a/library/src/sinks/Postgres.ts +++ b/library/src/sinks/Postgres.ts @@ -3,7 +3,7 @@ import { Hooks } from "../agent/hooks/Hooks"; import { InterceptorResult } from "../agent/hooks/MethodInterceptor"; import { Wrapper } from "../agent/Wrapper"; import { Context } from "../agent/Context"; -import { checkContextForSqlInjection } from "../vulnerabilities/sql-injection/detectSQLInjection"; +import { checkContextForSqlInjection } from "../vulnerabilities/sql-injection/checkContextForSqlInjection"; export class Postgres implements Wrapper { private inspectQuery(args: unknown[], context: Context): InterceptorResult { diff --git a/library/src/vulnerabilities/sql-injection/checkContextForSqlInjection.ts b/library/src/vulnerabilities/sql-injection/checkContextForSqlInjection.ts new file mode 100644 index 000000000..ee0fb4e9a --- /dev/null +++ b/library/src/vulnerabilities/sql-injection/checkContextForSqlInjection.ts @@ -0,0 +1,36 @@ +import { Context } from "../../agent/Context"; +import { InterceptorResult } from "../../agent/hooks/MethodInterceptor"; +import { Source } from "../../agent/Source"; +import { extractStringsFromUserInput } from "../../helpers/extractStringsFromUserInput"; +import { detectSQLInjection } from "./detectSQLInjection"; + +/** + * This function goes over all the different input types in the context and checks + * if it's a possible SQL Injection, if so the function returns an InterceptorResult + */ +export function checkContextForSqlInjection({ + sql, + operation, + context, +}: { + sql: string; + operation: string; + context: Context; +}): InterceptorResult { + for (const source of ["body", "query", "headers", "cookies"] as Source[]) { + if (context[source]) { + const userInput = extractStringsFromUserInput(context[source]); + for (const str of userInput) { + if (detectSQLInjection(sql, str)) { + return { + operation: operation, + kind: "sql_injection", + source: source, + pathToPayload: "UNKOWN", + metadata: {}, + }; + } + } + } + } +} diff --git a/library/src/vulnerabilities/sql-injection/detectSQLInjection.test.ts b/library/src/vulnerabilities/sql-injection/detectSQLInjection.test.ts index e7df99c33..21e0013ab 100644 --- a/library/src/vulnerabilities/sql-injection/detectSQLInjection.test.ts +++ b/library/src/vulnerabilities/sql-injection/detectSQLInjection.test.ts @@ -2,11 +2,9 @@ import { basename, join } from "path"; import * as t from "tap"; import { readFileSync } from "fs"; import { dangerousCharsInInput } from "./dangerousCharsInInput"; -import { - detectSQLInjection, - userInputOccurrencesSafelyEncapsulated, - queryContainsUserInput, -} from "./detectSQLInjection"; +import { detectSQLInjection } from "./detectSQLInjection"; +import { queryContainsUserInput } from "./queryContainsUserInput"; +import { userInputOccurrencesSafelyEncapsulated } from "./userInputOccurrencesSafelyEncapsulated"; const BAD_SQL_COMMANDS = [ // Check for SQL Commands like : INSERT or DROP diff --git a/library/src/vulnerabilities/sql-injection/detectSQLInjection.ts b/library/src/vulnerabilities/sql-injection/detectSQLInjection.ts index fc0e13d4e..29f25afdb 100644 --- a/library/src/vulnerabilities/sql-injection/detectSQLInjection.ts +++ b/library/src/vulnerabilities/sql-injection/detectSQLInjection.ts @@ -1,10 +1,7 @@ -import { Context } from "../../agent/Context"; -import { InterceptorResult } from "../../agent/hooks/MethodInterceptor"; -import { Source } from "../../agent/Source"; -import { extractStringsFromUserInput } from "../../helpers/extractStringsFromUserInput"; -import { SQL_STRING_CHARS } from "./config"; import { dangerousCharsInInput } from "./dangerousCharsInInput"; +import { queryContainsUserInput } from "./queryContainsUserInput"; import { userInputContainsSQLSyntax } from "./userInputContainsSQLSyntax"; +import { userInputOccurrencesSafelyEncapsulated } from "./userInputOccurrencesSafelyEncapsulated"; /** * This function executes 2 checks to see if something is or is not an SQL Injection : @@ -41,78 +38,3 @@ export function detectSQLInjection(query: string, userInput: string) { // Executing our final check with the massive RegEx : return userInputContainsSQLSyntax(userInput); } - -/** - * This function is the first step to determine if an SQL Injection is happening, - * If the sql statement contains user input, this function returns true (case-insensitive) - * @param query The SQL Statement you want to check it against - * @param userInput The user input you want to check - * @returns True when the sql statement contains the input - */ -export function queryContainsUserInput(query: string, userInput: string) { - const lowercaseSql = query.toLowerCase(); - const lowercaseInput = userInput.toLowerCase(); - - return lowercaseSql.includes(lowercaseInput); -} - -/** - * This function is the third step to determine if an SQL Injection is happening, - * This checks if **all** occurrences of our input are encapsulated as strings. - * @param query The SQL Statement - * @param userInput The user input you want to check is encapsulated - * @returns True if the input is always encapsulated inside a string - */ -export function userInputOccurrencesSafelyEncapsulated( - query: string, - userInput: string -) { - const queryWithoutUserInput = query.split(userInput); - for (let i = 0; i + 1 < queryWithoutUserInput.length; i++) { - // Get the last character of this segment - const lastChar = queryWithoutUserInput[i].slice(-1); - // Get the first character of the next segment - const firstCharNext = queryWithoutUserInput[i + 1].slice(0, 1); - - if (!SQL_STRING_CHARS.includes(lastChar)) { - return false; // If the character is not one of these, it's not a string. - } - - if (lastChar != firstCharNext) { - return false; // String is not encapsulated by the same type of quotes. - } - } - - return true; -} - -/** - * This function goes over all the different input types in the context and checks - * if it's a possible SQL Injection, if so the function returns an InterceptorResult - */ -export function checkContextForSqlInjection({ - sql, - operation, - context, -}: { - sql: string; - operation: string; - context: Context; -}): InterceptorResult { - for (const source of ["body", "query", "headers", "cookies"] as Source[]) { - if (context[source]) { - const userInput = extractStringsFromUserInput(context[source]); - for (const str of userInput) { - if (detectSQLInjection(sql, str)) { - return { - operation: operation, - kind: "sql_injection", - source: source, - pathToPayload: "UNKOWN", - metadata: {}, - }; - } - } - } - } -} diff --git a/library/src/vulnerabilities/sql-injection/queryContainsUserInput.ts b/library/src/vulnerabilities/sql-injection/queryContainsUserInput.ts new file mode 100644 index 000000000..e78f6fcad --- /dev/null +++ b/library/src/vulnerabilities/sql-injection/queryContainsUserInput.ts @@ -0,0 +1,13 @@ +/** + * This function is the first step to determine if an SQL Injection is happening, + * If the sql statement contains user input, this function returns true (case-insensitive) + * @param query The SQL Statement you want to check it against + * @param userInput The user input you want to check + * @returns True when the sql statement contains the input + */ +export function queryContainsUserInput(query: string, userInput: string) { + const lowercaseSql = query.toLowerCase(); + const lowercaseInput = userInput.toLowerCase(); + + return lowercaseSql.includes(lowercaseInput); +} diff --git a/library/src/vulnerabilities/sql-injection/userInputOccurrencesSafelyEncapsulated.ts b/library/src/vulnerabilities/sql-injection/userInputOccurrencesSafelyEncapsulated.ts new file mode 100644 index 000000000..4fda774ce --- /dev/null +++ b/library/src/vulnerabilities/sql-injection/userInputOccurrencesSafelyEncapsulated.ts @@ -0,0 +1,31 @@ +import { SQL_STRING_CHARS } from "./config"; + +/** + * This function is the third step to determine if an SQL Injection is happening, + * This checks if **all** occurrences of our input are encapsulated as strings. + * @param query The SQL Statement + * @param userInput The user input you want to check is encapsulated + * @returns True if the input is always encapsulated inside a string + */ +export function userInputOccurrencesSafelyEncapsulated( + query: string, + userInput: string +) { + const queryWithoutUserInput = query.split(userInput); + for (let i = 0; i + 1 < queryWithoutUserInput.length; i++) { + // Get the last character of this segment + const lastChar = queryWithoutUserInput[i].slice(-1); + // Get the first character of the next segment + const firstCharNext = queryWithoutUserInput[i + 1].slice(0, 1); + + if (!SQL_STRING_CHARS.includes(lastChar)) { + return false; // If the character is not one of these, it's not a string. + } + + if (lastChar != firstCharNext) { + return false; // String is not encapsulated by the same type of quotes. + } + } + + return true; +}