Skip to content

Commit 4efc168

Browse files
authored
Merge pull request #988 from AikidoSec/default-block-invalid-sql-off
Default AIKIDO_BLOCK_INVALID_SQL to off
2 parents c48befe + deb6b21 commit 4efc168

9 files changed

Lines changed: 91 additions & 36 deletions

docs/invalid-sql-queries.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
Zen blocks SQL queries that it can't tokenize when they contain user input. This prevents attackers from bypassing SQL injection detection with malformed queries. For example, ClickHouse ignores invalid SQL after `;`, and SQLite runs queries before an unclosed `/*` comment.
44

5-
This is on by default. In blocking mode, these queries are blocked. In detection-only mode, they are reported but still executed.
6-
7-
If you see false positives (legitimate queries being blocked), disable it with:
5+
This is off by default. To enable it, set the environment variable:
86

97
```
10-
AIKIDO_BLOCK_INVALID_SQL=false node app.js
8+
AIKIDO_BLOCK_INVALID_SQL=true node app.js
119
```
10+
11+
In blocking mode, these queries are blocked. In detection-only mode, they are reported but still executed.

end2end/tests/express-mysql.test.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ t.test("it does not block in dry mode", (t) => {
156156
});
157157
});
158158

159-
t.test("it blocks invalid SQL queries by default", (t) => {
159+
t.test("it does not block invalid SQL queries by default", (t) => {
160160
const server = spawn(`node`, [pathToApp, "4003"], {
161161
env: {
162162
...process.env,
@@ -187,7 +187,7 @@ t.test("it blocks invalid SQL queries by default", (t) => {
187187
timeout(2000)
188188
.then(() => {
189189
return fetch(
190-
`http://localhost:4003/invalid-query?sql=${encodeURIComponent("I'm a test")}`,
190+
`http://localhost:4003/invalid-query?sql=${encodeURIComponent("SELECT * FROM test")}`,
191191
{
192192
signal: AbortSignal.timeout(5000),
193193
method: "POST",
@@ -196,7 +196,7 @@ t.test("it blocks invalid SQL queries by default", (t) => {
196196
})
197197
.then((response) => {
198198
t.equal(response.status, 500);
199-
t.match(stdout, /Zen has blocked an SQL injection/);
199+
t.notMatch(stdout, /Zen has blocked an SQL injection/);
200200
})
201201
.catch((error) => {
202202
t.fail(error.message);
@@ -207,14 +207,14 @@ t.test("it blocks invalid SQL queries by default", (t) => {
207207
});
208208

209209
t.test(
210-
"it does not block invalid SQL queries when AIKIDO_BLOCK_INVALID_SQL is false",
210+
"it blocks invalid SQL queries when AIKIDO_BLOCK_INVALID_SQL is true",
211211
(t) => {
212212
const server = spawn(`node`, [pathToApp, "4004"], {
213213
env: {
214214
...process.env,
215215
AIKIDO_DEBUG: "true",
216216
AIKIDO_BLOCKING: "true",
217-
AIKIDO_BLOCK_INVALID_SQL: "false",
217+
AIKIDO_BLOCK_INVALID_SQL: "true",
218218
},
219219
});
220220

@@ -240,7 +240,7 @@ t.test(
240240
timeout(2000)
241241
.then(() => {
242242
return fetch(
243-
`http://localhost:4004/invalid-query?sql=${encodeURIComponent("SELECT * FROM test")}`,
243+
`http://localhost:4004/invalid-query?sql=${encodeURIComponent("I'm a test")}`,
244244
{
245245
signal: AbortSignal.timeout(5000),
246246
method: "POST",
@@ -249,7 +249,7 @@ t.test(
249249
})
250250
.then((response) => {
251251
t.equal(response.status, 500);
252-
t.notMatch(stdout, /Zen has blocked an SQL injection/);
252+
t.match(stdout, /Zen has blocked an SQL injection/);
253253
})
254254
.catch((error) => {
255255
t.fail(error.message);

library/agent/Agent.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { AttackWaveDetector } from "../vulnerabilities/attack-wave-detection/Att
3636
import type { FetchListsAPI } from "./api/FetchListsAPI";
3737
import { PendingEvents } from "./PendingEvents";
3838
import type { IdorProtectionConfig } from "./IdorProtectionConfig";
39+
import { warnIfBlockInvalidSqlDisabled } from "../helpers/warnIfBlockInvalidSqlDisabled";
3940
import { warnIfTsxIsUsed } from "../helpers/warnIfTsxIsUsed";
4041

4142
type WrappedPackage = { version: string | null; supported: boolean };
@@ -521,6 +522,7 @@ export class Agent {
521522
}
522523
}
523524

525+
warnIfBlockInvalidSqlDisabled();
524526
warnIfTsxIsUsed();
525527

526528
// When our library is required, we are not intercepting `require` calls yet
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { envToBool } from "./envToBool";
2+
3+
export function shouldBlockInvalidSqlQueries(): boolean {
4+
if (process.env.AIKIDO_BLOCK_INVALID_SQL === undefined) {
5+
return false;
6+
}
7+
8+
return envToBool(process.env.AIKIDO_BLOCK_INVALID_SQL);
9+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import * as t from "tap";
2+
import { warnIfBlockInvalidSqlDisabled } from "./warnIfBlockInvalidSqlDisabled";
3+
4+
const logs: string[] = [];
5+
// oxlint-disable-next-line no-console
6+
console.log = function log(message: string) {
7+
logs.push(message);
8+
};
9+
10+
t.beforeEach(() => {
11+
delete process.env.AIKIDO_BLOCK_INVALID_SQL;
12+
logs.length = 0;
13+
});
14+
15+
const expectedMessage =
16+
"AIKIDO: We recommend setting AIKIDO_BLOCK_INVALID_SQL=true. See https://github.com/AikidoSec/firewall-node/blob/main/docs/invalid-sql-queries.md";
17+
18+
t.test("it warns when AIKIDO_BLOCK_INVALID_SQL is not set", async (t) => {
19+
warnIfBlockInvalidSqlDisabled();
20+
21+
t.same(logs, [expectedMessage]);
22+
});
23+
24+
t.test("it does not warn when AIKIDO_BLOCK_INVALID_SQL is true", async (t) => {
25+
process.env.AIKIDO_BLOCK_INVALID_SQL = "true";
26+
27+
warnIfBlockInvalidSqlDisabled();
28+
29+
t.same(logs, []);
30+
});
31+
32+
t.test("it warns when AIKIDO_BLOCK_INVALID_SQL is false", async (t) => {
33+
process.env.AIKIDO_BLOCK_INVALID_SQL = "false";
34+
35+
warnIfBlockInvalidSqlDisabled();
36+
37+
t.same(logs, [expectedMessage]);
38+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { shouldBlockInvalidSqlQueries } from "./shouldBlockInvalidSqlQueries";
2+
3+
export function warnIfBlockInvalidSqlDisabled() {
4+
if (shouldBlockInvalidSqlQueries()) {
5+
return;
6+
}
7+
8+
// oxlint-disable-next-line no-console
9+
console.log(
10+
"AIKIDO: We recommend setting AIKIDO_BLOCK_INVALID_SQL=true. See https://github.com/AikidoSec/firewall-node/blob/main/docs/invalid-sql-queries.md"
11+
);
12+
}

library/sinks/SQLite3.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ t.test("it detects SQL injections", async () => {
130130

131131
// Query with syntax error and user input should be blocked by Zen
132132
// because the SQL tokenizer can't parse the query (unclosed quote)
133+
process.env.AIKIDO_BLOCK_INVALID_SQL = "true";
133134
const syntaxError = await t.rejects(async () => {
134135
await runWithContext(
135136
{ ...dangerousContext, body: { name: "SELECT * FROM test" } },
@@ -144,6 +145,7 @@ t.test("it detects SQL injections", async () => {
144145
"Zen has blocked an SQL injection: sqlite3.run(...) originating from body.name"
145146
);
146147
}
148+
delete process.env.AIKIDO_BLOCK_INVALID_SQL;
147149
} catch (error: any) {
148150
t.fail(error);
149151
} finally {

library/vulnerabilities/sql-injection/checkContextForSqlInjection.test.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -63,33 +63,22 @@ function createFailingContext(): Context {
6363
};
6464
}
6565

66-
t.test("it blocks failed tokenization by default", async () => {
66+
t.test("it does not block failed tokenization by default", async () => {
6767
t.same(
6868
checkContextForSqlInjection({
6969
sql: failingSQL,
7070
operation: "mysql.query",
7171
dialect: new SQLDialectMySQL(),
7272
context: createFailingContext(),
7373
}),
74-
{
75-
operation: "mysql.query",
76-
kind: "sql_injection",
77-
source: "body",
78-
pathsToPayload: [".comment"],
79-
metadata: {
80-
sql: failingSQL,
81-
dialect: "MySQL",
82-
failedToTokenize: "true",
83-
},
84-
payload: failingInput,
85-
}
74+
undefined
8675
);
8776
});
8877

8978
t.test(
90-
"it does not block failed tokenization when AIKIDO_BLOCK_INVALID_SQL is false",
79+
"it blocks failed tokenization when AIKIDO_BLOCK_INVALID_SQL is true",
9180
async () => {
92-
process.env.AIKIDO_BLOCK_INVALID_SQL = "false";
81+
process.env.AIKIDO_BLOCK_INVALID_SQL = "true";
9382

9483
t.same(
9584
checkContextForSqlInjection({
@@ -98,7 +87,18 @@ t.test(
9887
dialect: new SQLDialectMySQL(),
9988
context: createFailingContext(),
10089
}),
101-
undefined
90+
{
91+
operation: "mysql.query",
92+
kind: "sql_injection",
93+
source: "body",
94+
pathsToPayload: [".comment"],
95+
metadata: {
96+
sql: failingSQL,
97+
dialect: "MySQL",
98+
failedToTokenize: "true",
99+
},
100+
payload: failingInput,
101+
}
102102
);
103103
}
104104
);

library/vulnerabilities/sql-injection/checkContextForSqlInjection.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,15 @@
11
import { Context } from "../../agent/Context";
22
import { InterceptorResult } from "../../agent/hooks/InterceptorResult";
33
import { getPathsToPayload } from "../../helpers/attackPath";
4-
import { envToBool } from "../../helpers/envToBool";
54
import { extractStringsFromUserInputCached } from "../../helpers/extractStringsFromUserInputCached";
65
import { getSourceForUserString } from "../../helpers/getSourceForUserString";
6+
import { shouldBlockInvalidSqlQueries } from "../../helpers/shouldBlockInvalidSqlQueries";
77
import {
88
detectSQLInjection,
99
SQLInjectionDetectionResult,
1010
} from "./detectSQLInjection";
1111
import { SQLDialect } from "./dialects/SQLDialect";
1212

13-
function shouldBlockInvalidSqlQueries(): boolean {
14-
if (process.env.AIKIDO_BLOCK_INVALID_SQL === undefined) {
15-
return true;
16-
}
17-
18-
return envToBool(process.env.AIKIDO_BLOCK_INVALID_SQL);
19-
}
20-
2113
/**
2214
* This function goes over all the different input types in the context and checks
2315
* if it's a possible SQL Injection, if so the function returns an InterceptorResult

0 commit comments

Comments
 (0)