~/VibeHandbook
PDF grátis

Capítulo 18 · 02

Injeção: quando a entrada vira código

Injeção é o bug sério mais antigo e ainda o mais comum. Acontece quando dados de um usuário são tratados como instruções em vez de dados. Duas variantes importam para a maioria dos apps.

Injeção de — SQL (Structured Query Language, a linguagem que os apps usam para pedir dados a um banco de dados) — acontece quando a entrada do usuário se infiltra em uma consulta ao banco. O padrão vulnerável clássico:

// VULNERÁVEL: a entrada do usuário é colada direto na consulta
app.get("/user", (req, res) => {
  const name = req.query.name;
  db.query(`SELECT * FROM users WHERE name = '${name}'`);
});
// Se alguém passar  name = '; DROP TABLE users; --
// sua consulta vira um comando para apagar a tabela.

A correção é uma consulta parametrizada (também chamada de prepared statement). Você envia a consulta e os dados separadamente, então o banco nunca confunde um com o outro:

// SEGURO: o valor é passado como parâmetro, nunca como código
app.get("/user", (req, res) => {
  const name = req.query.name;
  db.query("SELECT * FROM users WHERE name = ?", [name]);
});

A regra: nunca monte uma consulta por concatenação de strings. Se você ver crases ou + colando SQL em torno de uma variável em um diff, pare e peça a versão parametrizada.

Os dois caminhos colocam a mesma entrada do usuário no banco de dados, mas só um impede que o banco confunda essa entrada com comandos:

  entrada do usuário:  '; DROP TABLE users; --
        │
        ├─────────────────────────┬───────────────────────────┐
        ▼                         ▼                            
  ✗ CONCATENADA             ✓ PARAMETRIZADA                    
  "...WHERE name=             "...WHERE name = ?", [entrada]    
     '" + entrada + "'"        (consulta e dados enviados separados)
        │                         │
        ▼                         ▼
  ┌───────────────┐         ┌───────────────┐
  │  BANCO DE     │         │  BANCO DE     │
  │  DADOS        │         │  DADOS        │
  │ lê a entrada  │         │ trata a       │
  │ COMO COMANDO  │         │ entrada só    │
  │  → tabela some│         │ como DADO     │
  └───────────────┘         └───────────────┘
       VIOLAÇÃO                   SEGURO

XSS (cross-site scripting) é a mesma ideia no navegador. Se você pega a entrada do usuário e a coloca em uma página como HTML bruto (HyperText Markup Language, o código que diz ao navegador o que exibir), um atacante pode injetar uma tag <script> que roda no navegador dos seus outros usuários — roubando as sessões deles, por exemplo. A correção é o escaping (transformar caracteres especiais em texto seguro): renderizar o conteúdo do usuário como texto, não como HTML. Frameworks modernos como o React fazem escaping por padrão, o que é ótimo, até a IA recorrer a uma saída de emergência como dangerouslySetInnerHTML ou innerHTML para "fazer funcionar". Isso contorna a proteção. Trate qualquer chamada desse tipo em um diff como algo a questionar.

O princípio unificador por trás de todo bug de injeção: mantenha dados e código separados. Sempre que a entrada do usuário cruza para dentro de uma consulta, um comando, um template ou HTML, algo precisa fazer escaping ou parametrizar.

Quer offline?

Baixe o livro inteiro em PDF ou EPUB — grátis.