UNPKG

@aikidosec/firewall

Version:

Zen by Aikido is an embedded Web Application Firewall that autonomously protects Node.js apps against common and critical attacks

161 lines (160 loc) 7.35 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Prisma = void 0; const wrapNewInstance_1 = require("../agent/hooks/wrapNewInstance"); const SQLDialectMySQL_1 = require("../vulnerabilities/sql-injection/dialects/SQLDialectMySQL"); const SQLDialectGeneric_1 = require("../vulnerabilities/sql-injection/dialects/SQLDialectGeneric"); const SQLDialectPostgres_1 = require("../vulnerabilities/sql-injection/dialects/SQLDialectPostgres"); const SQLDialectSQLite_1 = require("../vulnerabilities/sql-injection/dialects/SQLDialectSQLite"); const checkContextForSqlInjection_1 = require("../vulnerabilities/sql-injection/checkContextForSqlInjection"); const Context_1 = require("../agent/Context"); const onInspectionInterceptorResult_1 = require("../agent/hooks/onInspectionInterceptorResult"); const AgentSingleton_1 = require("../agent/AgentSingleton"); const detectNoSQLInjection_1 = require("../vulnerabilities/nosql-injection/detectNoSQLInjection"); const NOSQL_OPERATIONS_WITH_FILTER = ["findRaw"]; const NOSQL_OPERATIONS_WITH_PIPELINE = ["aggregateRaw"]; const SQL_OPERATIONS_TO_PROTECT = ["$queryRawUnsafe", "$executeRawUnsafe"]; class Prisma { // Check if the prisma client is a NoSQL client isNoSQLClient(clientInstance) { if (!clientInstance || typeof clientInstance !== "object" || !("_engineConfig" in clientInstance) || !clientInstance._engineConfig || typeof clientInstance._engineConfig !== "object" || !("activeProvider" in clientInstance._engineConfig) || typeof clientInstance._engineConfig.activeProvider !== "string") { return false; } return clientInstance._engineConfig.activeProvider === "mongodb"; } // Try to detect the SQL dialect used by the Prisma client, so we can use the correct SQL dialect for the SQL injection detection. getClientSQLDialect(clientInstance) { // https://github.com/prisma/prisma/blob/559988a47e50b4d4655dc45b11ceb9b5c73ef053/packages/generator-helper/src/types.ts#L75 if (!clientInstance || typeof clientInstance !== "object" || !("_engineConfig" in clientInstance) || !clientInstance._engineConfig || typeof clientInstance._engineConfig !== "object" || !("activeProvider" in clientInstance._engineConfig) || typeof clientInstance._engineConfig.activeProvider !== "string") { return new SQLDialectGeneric_1.SQLDialectGeneric(); } switch (clientInstance._engineConfig.activeProvider) { case "mysql": return new SQLDialectMySQL_1.SQLDialectMySQL(); case "postgresql": case "postgres": return new SQLDialectPostgres_1.SQLDialectPostgres(); case "sqlite": return new SQLDialectSQLite_1.SQLDialectSQLite(); default: return new SQLDialectGeneric_1.SQLDialectGeneric(); } } inspectSQLQuery(args, operation, dialect) { 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]; return (0, checkContextForSqlInjection_1.checkContextForSqlInjection)({ sql: sql, context: context, operation: `prisma.${operation}`, dialect: dialect, }); } return undefined; } inspectNoSQLQuery(args, operation, model) { const context = (0, Context_1.getContext)(); if (!context) { return undefined; } if (!args || typeof args !== "object") { return undefined; } let filter; if (NOSQL_OPERATIONS_WITH_FILTER.includes(operation) && "filter" in args) { filter = args.filter; } if (NOSQL_OPERATIONS_WITH_PIPELINE.includes(operation) && "pipeline" in args) { filter = args.pipeline; } if (filter) { return this.inspectNoSQLFilter(model !== null && model !== void 0 ? model : "", context, filter, operation); } return undefined; } inspectNoSQLFilter(collection, request, filter, operation) { const result = (0, detectNoSQLInjection_1.detectNoSQLInjection)(request, filter); if (result.injection) { return { operation: `prisma.${operation}`, kind: "nosql_injection", source: result.source, pathsToPayload: result.pathsToPayload, metadata: { collection: collection, operation: operation, filter: JSON.stringify(filter), }, payload: result.payload, }; } } onClientOperation({ model, operation, args, query, isNoSQLClient, sqlDialect, agent, pkgInfo, }) { let inspectionResult; const start = performance.now(); if (!isNoSQLClient && SQL_OPERATIONS_TO_PROTECT.includes(operation)) { inspectionResult = this.inspectSQLQuery(args, operation, sqlDialect || new SQLDialectGeneric_1.SQLDialectGeneric()); } if (isNoSQLClient) { inspectionResult = this.inspectNoSQLQuery(args, operation, model); } if (inspectionResult) { // Run the logic to handle a detected attack (0, onInspectionInterceptorResult_1.onInspectionInterceptorResult)((0, Context_1.getContext)(), agent, inspectionResult, pkgInfo, start, operation, isNoSQLClient ? "nosql_op" : "sql_op"); } return query(args); } wrap(hooks) { hooks .addPackage("@prisma/client") .withVersion("^5.0.0 || ^6.0.0") .onRequire((exports, pkgInfo) => { (0, wrapNewInstance_1.wrapNewInstance)(exports, "PrismaClient", pkgInfo, (instance) => { const isNoSQLClient = this.isNoSQLClient(instance); const agent = (0, AgentSingleton_1.getInstance)(); if (!agent) { return; } // Extend all operations of the Prisma client // https://www.prisma.io/docs/orm/prisma-client/client-extensions/query#modify-all-operations-in-all-models-of-your-schema return instance.$extends({ query: { $allOperations: ({ model, operation, args, query, }) => { return this.onClientOperation({ model, operation, args, query, isNoSQLClient, sqlDialect: !isNoSQLClient ? this.getClientSQLDialect(instance) : undefined, agent, pkgInfo, }); }, }, }); }); }); } } exports.Prisma = Prisma;