@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
JavaScript
;
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;