@aikidosec/firewall
Version:
Zen by Aikido is an embedded Application Firewall that autonomously protects Node.js apps against common and critical attacks, provides rate limiting, detects malicious traffic (including bots), and more.
180 lines (179 loc) • 7.35 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Postgres = void 0;
const Context_1 = require("../agent/Context");
const checkContextForSqlInjection_1 = require("../vulnerabilities/sql-injection/checkContextForSqlInjection");
const checkContextForIdor_1 = require("../vulnerabilities/idor/checkContextForIdor");
const SQLDialectPostgres_1 = require("../vulnerabilities/sql-injection/dialects/SQLDialectPostgres");
const isPlainObject_1 = require("../helpers/isPlainObject");
const wrapExport_1 = require("../agent/hooks/wrapExport");
class Postgres {
constructor() {
this.dialect = new SQLDialectPostgres_1.SQLDialectPostgres();
}
resolvePlaceholder(placeholder, _placeholderNumber, params) {
// Postgres uses $1, $2, etc. (1-based)
const match = placeholder.match(/^\$(\d+)$/);
if (match && params) {
const index = parseInt(match[1], 10) - 1;
if (index >= 0 && index < params.length) {
return params[index];
}
}
return undefined;
}
findParams(args) {
if (args.length >= 2 && Array.isArray(args[1])) {
return args[1];
}
// Object format: query({ text: "...", values: [...] })
if (args.length > 0 &&
(0, isPlainObject_1.isPlainObject)(args[0]) &&
Array.isArray(args[0].values)) {
return args[0].values;
}
return undefined;
}
inspectQuery(args) {
const context = (0, Context_1.getContext)();
if (!context) {
return undefined;
}
if (args.length > 0 && typeof args[0] === "string" && args[0].length > 0) {
const sql = args[0];
const params = this.findParams(args);
// Check for SQL injection first to block malicious queries before parsing SQL query for IDOR analysis
const sqlInjectionResult = (0, checkContextForSqlInjection_1.checkContextForSqlInjection)({
sql: sql,
context: context,
operation: "pg.query",
dialect: this.dialect,
});
if (sqlInjectionResult) {
return sqlInjectionResult;
}
return (0, checkContextForIdor_1.checkContextForIdor)({
sql,
context,
dialect: this.dialect,
resolvePlaceholder: (placeholder, placeholderNumber) => this.resolvePlaceholder(placeholder, placeholderNumber, params),
});
}
if (args.length > 0 &&
(0, isPlainObject_1.isPlainObject)(args[0]) &&
args[0].text &&
typeof args[0].text === "string") {
const text = args[0].text;
const params = this.findParams(args);
// Check for SQL injection first to block malicious queries before parsing SQL query for IDOR analysis
const sqlInjectionResult = (0, checkContextForSqlInjection_1.checkContextForSqlInjection)({
sql: text,
context: context,
operation: "pg.query",
dialect: this.dialect,
});
if (sqlInjectionResult) {
return sqlInjectionResult;
}
return (0, checkContextForIdor_1.checkContextForIdor)({
sql: text,
context,
dialect: this.dialect,
resolvePlaceholder: (placeholder, placeholderNumber) => this.resolvePlaceholder(placeholder, placeholderNumber, params),
});
}
return undefined;
}
// This is needed as the AsyncContext is not properly working under high concurrency with pg Pool,
// as pg Pool executes queries in parallel and the context can get mixed up between different queries.
// By binding the context to the callback passed to pool.connect(), we ensure that the correct context is available when the query is executed.
modifyPoolConnectArgs(args) {
return args.map((arg) => {
if (typeof arg === "function") {
return (0, Context_1.bindContext)(arg);
}
return arg;
});
}
wrap(hooks) {
hooks
.addPackage("pg")
.withVersion("^7.0.0 || ^8.0.0")
.onRequire((exports, pkgInfo) => {
(0, wrapExport_1.wrapExport)(exports.Client.prototype, "query", pkgInfo, {
kind: "sql_op",
inspectArgs: (args) => this.inspectQuery(args),
});
})
.addFileInstrumentation({
path: "lib/client.js",
functions: [
{
nodeType: "MethodDefinition",
name: "query",
operationKind: "sql_op",
bindContext: true,
inspectArgs: (args) => this.inspectQuery(args),
},
],
});
hooks
.addPackage("pg-pool")
.withVersion("^3.0.0")
.onRequire((exports, pkgInfo) => {
(0, wrapExport_1.wrapExport)(exports.prototype, "connect", pkgInfo, {
kind: "sql_op",
modifyArgs: (args) => this.modifyPoolConnectArgs(args),
});
})
.addFileInstrumentation({
path: "index.js",
functions: [
{
nodeType: "MethodDefinition",
name: "connect",
operationKind: "sql_op",
modifyArgs: (args) => this.modifyPoolConnectArgs(args),
},
],
});
hooks
.addPackage("@prisma/adapter-pg")
.withVersion("^7.0.0")
.addFileInstrumentation({
// This is not needed for CJS, as we can see sub-imports of CJS packages
path: "dist/index.mjs",
functions: [
{
nodeType: "MethodDefinition",
className: "PrismaPgAdapter",
name: "constructor",
operationKind: undefined,
modifyArgs: (args) => {
const pkgInfo = {
type: "external",
name: "@prisma/adapter-pg",
};
if (!args[0] ||
typeof args[0] !== "object" ||
!("connect" in args[0]) ||
!("Client" in args[0])) {
return args;
}
const pool = args[0];
(0, wrapExport_1.wrapExport)(pool, "connect", pkgInfo, {
kind: "sql_op",
modifyArgs: (args) => this.modifyPoolConnectArgs(args),
});
(0, wrapExport_1.wrapExport)(pool.Client.prototype, "query", pkgInfo, {
kind: "sql_op",
inspectArgs: (args) => this.inspectQuery(args),
});
return args;
},
},
],
});
}
}
exports.Postgres = Postgres;