Encontre o Sink de Injeção SQL: Identificando SQL Montado por Concatenação em Node.js
O desafio
Esta rota Node monta uma consulta SQL colando a entrada do usuário direto na string. Uma chamada executa esse SQL não filtrado contra o banco. Leia os dois arquivos e digite o nome dessa chamada.
O que você vai aprender
- Reconhecer SQL construído por concatenação de string com entrada do usuário como um sink de injeção SQL
- Rastrear um parâmetro de requisição desde o ponto de entrada até a chamada que o executa
- Diferenciar uma query concatenada (db.query) de uma parametrizada (db.execute com placeholders)
- Ler duas rotas semelhantes e identificar qual delas é explorável
- Explicar por que parâmetros vinculados impedem a injeção
Habilidades testadas
Pré-requisitos
- Leitura básica de JavaScript
- Familiaridade com parâmetros de query HTTP
- Como é um SELECT SQL
Como funciona
Injeção SQL acontece quando entrada não confiável é misturada ao texto de uma instrução SQL em vez de ser enviada como dado. O parser do banco de dados não consegue distinguir qual parte da string era um valor e qual parte é um comando, então um atacante que controla o valor pode mudar o significado de toda a query.
Em users.js a rota lê req.query.id e monta a instrução com "SELECT ... WHERE id=" + id, e então a executa com db.query(sql). Um ?id=42 benigno funciona normalmente, e é por isso que o bug passa despercebido. Mas ?id=0 OR 1=1 faz a cláusula WHERE ser sempre verdadeira e despeja todos os usuários, e um payload UNION SELECT pode ler outras tabelas ou até credenciais. O valor nunca foi escapado, então é interpretado como SQL.
A correção está em orders.js. Ela usa um placeholder ? e passa o valor em um array separado: db.execute(sql, [req.query.user]). O driver vincula o valor como um parâmetro tipado, então ele nunca consegue escapar do seu lugar e virar SQL. Identificar o bug em uma revisão significa seguir o valor do usuário e perceber que ele acaba dentro da string da query, e não na lista de parâmetros.
Erros comuns
- Culpar req.query.id. Ler a entrada não é o problema; a vulnerabilidade está em concatená-la no SQL e executá-la. Aponte a chamada que executa a query.
- Escolher db.execute. Essa é a rota segura e parametrizada, ela vincula os valores, então não é o sink.
- Assumir que um id inteiro é seguro. Nada valida que
idé numérico; é uma string bruta vinda da URL. - Achar que um ORM ou framework escapa isso automaticamente. A concatenação bruta ignora qualquer proteção que o driver ofereça.
Como se proteger
Nunca construa SQL concatenando entrada do usuário. Sempre passe os valores como parâmetros vinculados para que o banco de dados os trate como dado, não como código.
- Use queries parametrizadas ou prepared statements (
db.execute(sql, [value])) para todo valor fornecido pelo usuário. - Valide e converta os tipos de entrada (por exemplo, transforme um id em número) antes que eles cheguem à camada de dados.
- Aplique contas de banco de dados com privilégio mínimo para que uma query injetada não consiga ler tabelas não relacionadas.
- Adicione uma regra de lint ou revisão de código que sinalize concatenação de string ao lado de
db.query.