UNPKG

@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.

204 lines (203 loc) 8.13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BetterSQLite3 = void 0; const Context_1 = require("../agent/Context"); const wrapExport_1 = require("../agent/hooks/wrapExport"); const isPlainObject_1 = require("../helpers/isPlainObject"); const checkContextForIdor_1 = require("../vulnerabilities/idor/checkContextForIdor"); const checkContextForPathTraversal_1 = require("../vulnerabilities/path-traversal/checkContextForPathTraversal"); const checkContextForSqlInjection_1 = require("../vulnerabilities/sql-injection/checkContextForSqlInjection"); const SQLDialectSQLite_1 = require("../vulnerabilities/sql-injection/dialects/SQLDialectSQLite"); class BetterSQLite3 { constructor() { this.dialect = new SQLDialectSQLite_1.SQLDialectSQLite(); } inspectQuery(operation, args) { const context = (0, Context_1.getContext)(); if (!context) { return undefined; } if (args.length > 0) { if (typeof args[0] === "string" && args[0].length > 0) { const sql = args[0]; return this.inspectSQLCommand(sql, context, operation); } } return undefined; } inspectStatementOperation(operation, args, statement) { const context = (0, Context_1.getContext)(); if (!context) { return undefined; } if (statement && typeof statement === "object" && "source" in statement && typeof statement.source === "string") { const sql = statement.source; // better-sqlite3 accepts params as an array: .all([v1, v2]) // or as individual arguments: .all(v1, v2) let params; if (args.length > 0) { params = Array.isArray(args[0]) ? args[0] : args; } return this.inspectSQLCommand(sql, context, operation, params); } } inspectSQLCommand(sql, context, operation, params) { const sqlResult = (0, checkContextForSqlInjection_1.checkContextForSqlInjection)({ operation: operation, sql: sql, context: context, dialect: this.dialect, }); if (sqlResult) { return sqlResult; } return (0, checkContextForIdor_1.checkContextForIdor)({ sql, context, dialect: this.dialect, resolvePlaceholder: (placeholder, placeholderNumber) => this.resolvePlaceholder(placeholder, placeholderNumber, params), }); } resolvePlaceholder(placeholder, placeholderNumber, params) { // ? placeholder (positional) if (placeholder === "?" && placeholderNumber !== undefined && params) { if (placeholderNumber < params.length) { return params[placeholderNumber]; } } // Named params (:name, @name, $name) — better-sqlite3 accepts an object if (params && params.length === 1 && (0, isPlainObject_1.isPlainObject)(params[0]) && placeholder.length > 1) { const prefix = placeholder[0]; if (prefix === ":" || prefix === "@" || prefix === "$") { const key = placeholder.substring(1); if (Object.hasOwn(params[0], key)) { return params[0][key]; } } } return undefined; } /** * Inspect path of sqlite3.backup for path traversal */ inspectPath(operation, args) { const context = (0, Context_1.getContext)(); if (!context) { return undefined; } if (args.length === 0 || typeof args[0] !== "string") { return undefined; } const filename = args[0]; const result = (0, checkContextForPathTraversal_1.checkContextForPathTraversal)({ filename: filename, operation: operation, context: context, checkPathStart: true, }); if (result) { return result; } return undefined; } wrap(hooks) { const sqlFunctions = ["exec", "pragma"]; const fsPathFunctions = ["backup", "loadExtension"]; const statementSqlFunctions = ["run", "get", "all", "iterate", "bind"]; const pkg = hooks .addPackage("better-sqlite3") .withVersion("^12.0.0 || ^11.0.0 || ^10.0.0 || ^9.0.0 || ^8.0.0"); pkg.onRequire((exports, pkgInfo) => { for (const func of sqlFunctions) { (0, wrapExport_1.wrapExport)(exports.prototype, func, pkgInfo, { kind: "sql_op", inspectArgs: (args) => { return this.inspectQuery(`better-sqlite3.${func}`, args); }, }); } for (const func of fsPathFunctions) { (0, wrapExport_1.wrapExport)(exports.prototype, func, pkgInfo, { kind: "fs_op", inspectArgs: (args) => { return this.inspectPath(`better-sqlite3.${func}`, args); }, }); } (0, wrapExport_1.wrapExport)(exports.prototype, "prepare", pkgInfo, { kind: "sql_op", modifyReturnValue: (args, statement) => { for (const func of statementSqlFunctions) { (0, wrapExport_1.wrapExport)(statement, func, pkgInfo, { kind: "sql_op", inspectArgs: (args, _, statement) => { return this.inspectStatementOperation(`better-sqlite3.prepare(...).${func}`, args, statement); }, }); } return statement; }, }); }); const wrapperFunctionsInstructions = sqlFunctions.map((func) => ({ name: `exports.${func}`, operationKind: "sql_op", nodeType: "FunctionAssignment", inspectArgs: (args) => { return this.inspectQuery(`better-sqlite3.${func}`, args); }, })); wrapperFunctionsInstructions.push({ name: "exports.prepare", operationKind: "sql_op", nodeType: "FunctionAssignment", modifyReturnValue: (args, statement) => { for (const func of statementSqlFunctions) { (0, wrapExport_1.wrapExport)(statement, func, { name: "better-sqlite3", type: "external", }, { kind: "sql_op", inspectArgs: (args, _, statement) => { return this.inspectStatementOperation(`better-sqlite3.prepare(...).${func}`, args, statement); }, }); } return statement; }, }); wrapperFunctionsInstructions.push({ name: "exports.loadExtension", operationKind: "fs_op", nodeType: "FunctionAssignment", inspectArgs: (args) => { return this.inspectPath("better-sqlite3.loadExtension", args); }, }); pkg.addFileInstrumentation({ path: "lib/methods/wrappers.js", functions: wrapperFunctionsInstructions, }); // Add backup instrumentation pkg.addFileInstrumentation({ path: "lib/methods/backup.js", functions: [ { name: "module.exports", operationKind: "fs_op", nodeType: "FunctionAssignment", inspectArgs: (args) => { return this.inspectPath("better-sqlite3.backup", args); }, }, ], }); } } exports.BetterSQLite3 = BetterSQLite3;