Segurança de Campos de Entrada
Esta aula aborda a segurança de campos de entrada, focando em validação, normalização, codificação e escape de dados. O objetivo é prevenir vulnerabilidades como injeção de código e XSS, garantindo a integridade e segurança das aplicações.
Campos de entrada são a porta de entrada para dados fornecidos pelo usuário. Se não forem tratados adequadamente, podem se tornar vetores de ataques como injeção de SQL, Cross-Site Scripting (XSS), injeção de comandos e outros. Nesta aula, vamos explorar quatro pilares fundamentais para proteger esses campos: validação, normalização, codificação e escape. Compreender e aplicar essas técnicas é essencial para construir aplicações seguras e robustas.
Vamos começar com uma visão geral de cada conceito e depois aprofundar em cada um, com exemplos práticos e boas práticas. O foco será em linguagens de programação comuns, mas os princípios são universais.
Validação
Validação é o processo de verificar se os dados fornecidos pelo usuário atendem aos critérios esperados antes de aceitá-los. Isso inclui verificar tipo, formato, comprimento, faixa de valores e outras regras de negócio. A validação pode ser feita no cliente (JavaScript) e no servidor, mas a validação no servidor é imprescindível, pois o cliente pode ser burlado.
Por exemplo, se um campo espera um e-mail, devemos verificar se o valor contém um formato de e-mail válido. Se espera uma idade, deve ser um número inteiro entre 0 e 120. A validação não apenas previne ataques, mas também melhora a experiência do usuário, fornecendo feedback imediato.
Exemplo em Python com Flask (usando WTForms para validação):
from flask_wtf import FlaskForm
from wtforms import StringField, IntegerField, validators
class UserForm(FlaskForm):
email = StringField('Email', [validators.DataRequired(), validators.Email()])
age = IntegerField('Idade', [validators.NumberRange(min=0, max=120)])
username = StringField('Username', [validators.Length(min=3, max=20)])
Neste exemplo, o formulário valida se o e-mail é obrigatório e tem formato válido, se a idade está entre 0 e 120, e se o username tem entre 3 e 20 caracteres. Se a validação falhar, o formulário não é processado e erros são exibidos.
Normalização
Normalização é o processo de transformar os dados em uma forma canônica ou padrão antes de processá-los. Isso é importante porque dados podem ser representados de várias maneiras, e diferentes representações podem causar problemas de segurança. Por exemplo, em Unicode, caracteres podem ser representados de formas diferentes (composição vs. decomposição). Um atacante pode explorar isso para contornar filtros.
Normalizar envolve, por exemplo, converter todas as letras para minúsculas, remover espaços extras, ou converter codificações. Em segurança, é crucial normalizar antes de aplicar validação ou armazenamento, para evitar bypasses.
Um exemplo comum é a normalização de caminhos de arquivos para evitar ataques de path traversal. Em Python, podemos usar os.path.normpath para normalizar um caminho:
import os
user_input = "../../etc/passwd"
normalized = os.path.normpath(user_input)
if normalized.startswith("/safe/directory"):
print("Acesso permitido")
else:
print("Acesso negado")
Outro exemplo é a normalização de strings Unicode usando unicodedata.normalize em Python, para evitar ataques de homógrafos ou caracteres invisíveis:
import unicodedata
s = "café" # pode ter duas representações
nfc = unicodedata.normalize('NFC', s)
nfd = unicodedata.normalize('NFD', s)
print(nfc, nfd)
Encoding
Codificação (encoding) é o processo de converter dados de uma representação para outra, geralmente para transmissão ou armazenamento seguro. No contexto de segurança, é importante codificar dados de saída para evitar injeção. Por exemplo, ao exibir dados do usuário em HTML, devemos codificar caracteres especiais como <, >, &, etc., para que sejam interpretados como texto, não como HTML.
Também é importante codificar dados ao enviar para outros sistemas, como URLs, cabeçalhos HTTP, etc. Por exemplo, ao incluir dados em uma URL, devemos usar percent-encoding.
Em HTML, podemos usar bibliotecas como html.escape em Python:
import html
user_input = ""
escaped = html.escape(user_input)
print(escaped) # <script>alert('xss')</script>
Em JavaScript, podemos usar encodeURIComponent para codificar dados em URLs:
let userInput = "foo bar&baz";
let encoded = encodeURIComponent(userInput);
console.log(encoded); // foo%20bar%26baz
Escaping
Escaping é um caso específico de codificação que visa neutralizar caracteres que têm significado especial em um contexto particular. Por exemplo, em SQL, aspas simples podem ser usadas para injetar código. Escaping envolve adicionar uma barra invertida (ou outro caractere) para indicar que o caractere deve ser tratado literalmente.
No entanto, a prática moderna recomenda usar prepared statements (consultas parametrizadas) em vez de escapar manualmente, pois é mais seguro e evita erros.
Exemplo em PHP com PDO (prepared statement):
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);
Se você precisar escapar manualmente, em SQL, você pode usar mysqli_real_escape_string (mas não é recomendado):
$safe = mysqli_real_escape_string($conn, $input);
$query = "SELECT * FROM users WHERE username = '$safe'";
Em HTML, escapar é essencial para prevenir XSS. Já vimos o uso de html.escape. Em JavaScript, para inserir texto seguro no DOM, use textContent em vez de innerHTML:
element.textContent = userInput; // seguro
element.innerHTML = userInput; // perigoso
Boas Práticas e Observações Finais
Além de aplicar essas técnicas, é importante seguir algumas boas práticas:
- Defesa em profundidade: Não confie em uma única camada de proteção. Combine validação, normalização, codificação e escape.
- Valide sempre no servidor: A validação no cliente é apenas para UX, nunca para segurança.
- Use bibliotecas testadas: Não tente implementar sua própria codificação ou escape; use funções e bibliotecas amplamente testadas.
- Mantenha-se atualizado: Vulnerabilidades evoluem; acompanhe as melhores práticas e atualizações de segurança.
- Teste com entradas maliciosas: Inclua casos de teste com payloads conhecidos (XSS, SQLi, etc.) para garantir que sua aplicação está protegida.
Lembre-se: a segurança de campos de entrada é uma responsabilidade contínua, não uma tarefa única.
Referências
- OWASP Input Validation Cheat Sheet
- OWASP XSS
- MDN encodeURIComponent
- Python html module
- PHP mysqli_real_escape_string
- OWASP SQL Injection
- Unicode Normalization Forms
Exercícios
- Exercício 1: Explique por que a validação no servidor é mais importante do que a validação no cliente. Dê um exemplo de ataque que poderia ocorrer se apenas a validação no cliente fosse usada.
- Exercício 2: Descreva uma situação em que a normalização de dados é necessária para evitar um ataque de path traversal. Implemente um exemplo em Python.
- Exercício 3: Qual é a diferença entre codificação e escape? Dê um exemplo de cada.
- Exercício 4: Considere o seguinte código PHP que usa
mysqli_real_escape_stringpara prevenir injeção SQL. Ele é seguro? Justifique. - Exercício 5: Escreva um exemplo em JavaScript que demonstre a diferença entre usar
innerHTMLetextContentao inserir dados do usuário. Qual é seguro e por quê?
os.path.normpath resolve isso, pois remove os componentes ".." e resolve o caminho. Exemplo:import os
base_dir = '/var/www/uploads'
user_input = '../../etc/passwd'
full_path = os.path.join(base_dir, user_input)
normalized = os.path.normpath(full_path)
if normalized.startswith(base_dir):
print('Acesso permitido')
else:
print('Acesso negado')
< para < em HTML. Exemplo de codificação: encodeURIComponent em JavaScript. Exemplo de escape: html.escape em Python.$username = mysqli_real_escape_string($conn, $_POST['username']);
$query = "SELECT * FROM users WHERE username = '$username'";
mysqli_real_escape_string escape certos caracteres, ele não é infalível em todos os contextos, especialmente quando o conjunto de caracteres da conexão não é conhecido. Além disso, o uso de concatenação de strings é propenso a erros. A recomendação moderna é usar prepared statements com bind parameters, que separam dados de código, eliminando o risco de injeção SQL.let userInput = "
";
// Perigoso: interpreta como HTML
document.getElementById('output').innerHTML = userInput;
// Seguro: insere como texto
document.getElementById('output').textContent = userInput;
Usar innerHTML pode executar scripts ou carregar elementos maliciosos, levando a XSS. textContent insere o conteúdo como texto puro, sem interpretar HTML, tornando-o seguro.