Introdução
Imagine que você está desenvolvendo um aplicativo web de blog usando Prisma. Você escreve uma consulta simples para autenticar usuários com base no e-mail e senha fornecidos:
1const user = await prisma.user.findFirst({
2 where: { email, password },
3});Parece inofensivo, certo? Mas e se um invasor enviar password = { "not": "" }? Em vez de retornar o objeto User apenas quando e-mail e senha correspondem, a consulta sempre retorna o User quando apenas o e-mail fornecido corresponde.
Essa vulnerabilidade é conhecida como operator injection, mas é mais comumente referida como NoSQL injection. O que muitos desenvolvedores não percebem é que, apesar de esquemas de modelo rigorosos, alguns ORMs são vulneráveis a operator injection mesmo quando usados com um banco de dados relacional como o PostgreSQL, tornando-o um risco mais disseminado do que o esperado.
Neste post, exploraremos como o operator injection funciona, demonstraremos exploits no Prisma ORM e discutiremos como preveni-los.
Entendendo o Operator Injection
Para entender o operator injection em ORMs, é interessante primeiro analisar o NoSQL injection. O MongoDB introduziu aos desenvolvedores uma API para consultar dados usando operadores como $eq, $lt e $ne. Quando a entrada do usuário é passada cegamente para as funções de consulta do MongoDB, existe o risco de NoSQL injection.
Bibliotecas ORM populares para JavaScript começaram a oferecer uma API semelhante para consultar dados e, atualmente, quase todos os principais ORMs suportam alguma variação de operadores de consulta, mesmo quando não suportam MongoDB. Prisma, Sequelize e TypeORM implementaram suporte para operadores de consulta para bancos de dados relacionais como o PostgreSQL.
Explorando o Operator Injection no Prisma
Funções de consulta do Prisma que operam em mais de um registro geralmente suportam operadores de consulta e são vulneráveis a injection. Exemplos de funções incluem findFirst, findMany, updateMany e deleteMany. Embora o Prisma valide os campos do modelo referenciados na consulta em tempo de execução, os operadores são uma entrada válida para essas funções e, portanto, não são rejeitados pela validação.
Uma razão pela qual o operator injection é fácil de explorar no Prisma são os operadores baseados em string oferecidos pela API do Prisma. Algumas bibliotecas ORM removeram o suporte para operadores de consulta baseados em string porque são facilmente negligenciados pelos desenvolvedores e fáceis de explorar. Em vez disso, elas forçam os desenvolvedores a referenciar objetos personalizados para operadores. Como esses objetos não podem ser facilmente desserializados a partir da entrada do usuário, o risco de operation injection é bastante reduzido nessas bibliotecas.
Nem todas as funções de consulta no Prisma são vulneráveis a operator injection. Funções que selecionam ou modificam um único registro de banco de dados geralmente não suportam operadores e lançam um erro em tempo de execução quando um Objeto é fornecido. Além de findUnique, as funções update, delete e upsert do Prisma também não aceitam operadores em seu filtro where.
1 // This query throws a runtime error:
2 // Argument `email`: Invalid value provided. Expected String, provided Object.
3 const user = await prisma.user.findUnique({
4 where: { email: { not: "" } },
5 });
Melhores Práticas para Prevenir o Operator Injection
1. Converter Entrada do Usuário para Tipos de Dados Primitivos
Geralmente, converter a entrada para tipos de dados primitivos, como strings ou números, é suficiente para evitar que invasores injetem objetos. No exemplo original, a conversão seria assim:
1 const user = await prisma.user.findFirst({
2 where: { email: email.toString(), password: password.toString() },
3 });2. Validar Entrada do Usuário
Embora o casting seja eficaz, pode ser interessante validar a entrada do usuário para garantir que ela atenda aos requisitos da sua lógica de negócio.
Existem muitas bibliotecas para validação de entrada do usuário no lado do servidor, como class-validator, zod e joi. Se você estiver desenvolvendo para um framework de aplicação web como NestJS ou NextJS, eles provavelmente recomendarão métodos específicos para validação de entrada do usuário no controller.
No exemplo original, a validação com zod pode ser assim:
1import { z } from "zod";
2
3const authInputSchema = z.object({
4 email: z.string().email(),
5 password: z.string().min(8)
6});
7
8const { email, password } = authInputSchema.parse({email: req.params.email, password: req.params.password});
9
10const user = await prisma.user.findFirst({
11 where: { email, password },
12});3. Mantenha seu ORM atualizado
Mantenha-se atualizado para se beneficiar de melhorias e correções de segurança. Por exemplo, o Sequelize desabilitou aliases de string para operadores de consulta a partir da versão 4.12, o que reduz significativamente a suscetibilidade à injeção de operadores.
Conclusão
A injeção de operador é uma ameaça real para aplicações que utilizam ORMs modernos. A vulnerabilidade decorre do design da API do ORM e não está relacionada ao tipo de banco de dados em uso. De fato, até mesmo o Prisma combinado com o PostgreSQL pode ser vulnerável à injeção de operador. Embora o Prisma ofereça algumas proteções integradas contra injeção de operador, os desenvolvedores ainda devem praticar a validação e sanitização de entrada para garantir a segurança da aplicação.
Apêndice: Schema do Prisma para o modelo User
1// This is your Prisma schema file,
2// learn more about it in the docs: https://pris.ly/d/prisma-schema
3
4generator client {
5 provider = "prisma-client-js"
6}
7
8datasource db {
9 provider = "postgresql"
10 url = env("DATABASE_URL")
11}
12
13// ...
14
15model User {
16 id Int @id @default(autoincrement())
17 email String @unique
18 password String
19 name String?
20 posts Post[]
21 profile Profile?
22}Proteja seu software agora


.avif)
