Segurança de Campos de Entrada
Esta aula aborda a segurança de campos de entrada em aplicações web, focando em quatro pilares fundamentais: validação, normalização, encoding e escaping. Você aprenderá a aplicar essas técnicas para prevenir vulnerabilidades como injeção de código, XSS e SQL Injection, com exemplos práticos e exercícios.
A segurança de campos de entrada é um dos pilares da segurança de aplicações web. Qualquer dado fornecido pelo usuário pode ser uma porta de entrada para ataques, como injeção de SQL, cross-site scripting (XSS), command injection e outros. Nesta aula, você vai aprender as quatro técnicas essenciais para proteger sua aplicação: validação, normalização, encoding e escaping. Cada uma desempenha um papel distinto, mas juntas formam uma camada robusta de defesa.
Vamos explorar cada técnica em profundidade, com exemplos práticos e boas práticas, para que você possa aplicá-las imediatamente em seus projetos. Ao final, você terá uma compreensão sólida de como tratar dados de entrada de forma segura.
Validação
A validação é o processo de verificar se os dados fornecidos pelo usuário atendem a critérios específicos antes de serem aceitos pela aplicação. O objetivo é garantir que apenas dados válidos e esperados sejam processados, rejeitando qualquer coisa que não esteja em conformidade. Isso reduz a superfície de ataque, pois muitos ataques dependem de entrada malformada.
Existem dois tipos principais de validação: validação de sintaxe e validação de semântica. A validação de sintaxe verifica se o dado está no formato correto (por exemplo, um e-mail deve conter '@' e um domínio). A validação de semântica verifica se o dado faz sentido no contexto (por exemplo, uma idade deve ser um número positivo). A validação deve ser feita no servidor, nunca apenas no cliente, pois o cliente pode ser contornado.
Exemplo em Python usando a biblioteca validate_email:
from validate_email import validate_email
email = "usuario@example.com"
if validate_email(email):
print("E-mail válido")
else:
print("E-mail inválido")
No exemplo acima, a validação garante que o e-mail tenha um formato aceitável. Mas lembre-se: validação não é suficiente para evitar todos os ataques. Ela deve ser combinada com as outras técnicas.
Normalização
A normalização é o processo de transformar os dados de entrada em uma forma canônica e consistente. Isso é importante porque o mesmo dado pode ser representado de várias maneiras, e algumas representações podem ser exploradas por atacantes. Por exemplo, em URLs, caracteres podem ser codificados de formas diferentes, como %2e para um ponto. Normalizar os dados ajuda a garantir que a aplicação interprete a entrada de forma consistente.
Um caso clássico é a normalização de strings Unicode. Por exemplo, o caractere é pode ser representado como um único código (U+00E9) ou como uma combinação de e mais acento (U+0065 U+0301). Se a aplicação não normalizar, um atacante pode usar uma representação para contornar filtros. Em Python, podemos usar unicodedata.normalize() para padronizar:
import unicodedata
entrada = "café" # pode estar em forma decomposta
normalizada = unicodedata.normalize('NFKC', entrada)
print(normalizada) # exibe "café" em forma composta
Outro exemplo é a normalização de caminhos de arquivos. Se a aplicação recebe um caminho de arquivo, é importante resolver caminhos relativos e eliminar componentes como .. para evitar directory traversal. Em Python, os.path.normpath() faz isso:
import os
caminho = "../../etc/passwd"
normalizado = os.path.normpath(caminho)
print(normalizado) # exibe "../../etc/passwd" (mas cuidado: não resolve completamente)
Normalização é especialmente importante em sistemas que lidam com URLs, nomes de arquivos e dados Unicode. Ela deve ser aplicada antes da validação, para que as regras de validação operem sobre dados padronizados.
Encoding
Encoding (codificação) é o processo de converter dados de um formato para outro, geralmente para permitir transmissão ou armazenamento seguro. No contexto de segurança, encoding é usado para representar dados de forma que não sejam interpretados como código pelo interpretador alvo. Por exemplo, em HTML, caracteres especiais como < e & precisam ser codificados para não serem interpretados como tags.
Existem muitos tipos de encoding: URL encoding, HTML encoding, Base64, etc. A escolha depende do contexto onde os dados serão usados. Por exemplo, ao inserir dados em uma URL, você deve usar URL encoding; ao inserir em HTML, deve usar HTML encoding.
Em Python, podemos usar urllib.parse.quote() para URL encoding:
from urllib.parse import quote
dado = "café com açúcar"
url_encodado = quote(dado)
print(url_encodado) # exibe "caf%C3%A9%20com%20a%C3%A7%C3%BAcar"
E para HTML encoding, usar html.escape():
import html
dado = ""
html_encodado = html.escape(dado)
print(html_encodado) # exibe "<script>alert('xss')</script>"
É crucial aplicar encoding no momento em que os dados são inseridos no contexto (saída), e não apenas na entrada. Isso ajuda a prevenir XSS, pois o navegador não interpretará os dados como código.
Escaping
Escaping é uma forma específica de encoding que envolve prefixar caracteres especiais com um caractere de escape (geralmente uma barra invertida) para indicar que devem ser tratados literalmente. É comumente usado em strings de consulta SQL, comandos de shell e expressões regulares.
Por exemplo, em SQL, para evitar SQL Injection, é necessário escapar aspas simples. Em Python, ao usar a biblioteca sqlite3, podemos usar parâmetros (query parameterization) que lidam com o escaping automaticamente:
import sqlite3
conn = sqlite3.connect('exemplo.db')
cursor = conn.cursor()
usuario = "joao'; DROP TABLE usuarios; --"
cursor.execute("SELECT * FROM usuarios WHERE nome = ?", (usuario,))
No exemplo, o parâmetro ? garante que o valor seja tratado como dado, não como parte da query. Isso é mais seguro do que concatenar strings.
Em shell, ao executar comandos com entrada do usuário, é essencial escapar caracteres especiais. Em Python, shlex.quote() pode ser usado:
import shlex
comando = "ls " + shlex.quote("arquivo; rm -rf /")
print(comando) # exibe: ls 'arquivo; rm -rf /'
Escaping é uma técnica complementar à validação e encoding. Ele é especialmente útil quando você precisa incluir dados em contextos que têm seus próprios caracteres especiais.
Boas Práticas e Observações Finais
A segurança de campos de entrada não se resume a uma única técnica. É essencial aplicar todas elas em conjunto, dependendo do contexto. Aqui estão algumas boas práticas:
- Defenda em profundidade: Use validação, normalização, encoding e escaping em combinação.
- Valide no servidor: Nunca confie apenas na validação do cliente.
- Use bibliotecas confiáveis: Prefira funções padrão da linguagem para encoding/escaping.
- Mantenha-se atualizado: Acompanhe as vulnerabilidades conhecidas (OWASP Top 10).
- Teste regularmente: Realize testes de segurança, como fuzzing e análise estática.
Lembre-se: a segurança é um processo contínuo. Sempre revise e atualize suas defesas.
Referências
- OWASP Input Validation Cheat Sheet
- OWASP XSS Attack
- MDN Encoding Glossary
- Python unicodedata Documentation
- Python html Module Documentation
- Python sqlite3 Documentation
- Python shlex Documentation
Exercícios
- Crie uma função em Python que valide se uma string é um CPF válido (apenas formato, não dígitos verificadores). Use expressões regulares para validar o padrão "xxx.xxx.xxx-xx".
- Escreva um código que normalize uma string para remover acentos, usando
unicodedata. Teste com "café" e "olá". - Dada a string
"", aplique HTML encoding usandohtml.escape()e mostre o resultado. - Explique por que a validação no cliente não é suficiente para segurança e dê um exemplo de como ela pode ser contornada.
- Crie um exemplo de como usar escaping em SQL para inserir um nome de usuário que contém aspas simples, usando parâmetros em Python.
import re
def valida_cpf(cpf):
padrao = r'^\d{3}\.\d{3}\.\d{3}-\d{2}$'
return bool(re.match(padrao, cpf))
print(valida_cpf("123.456.789-00")) # True
print(valida_cpf("12345678900")) # False
import unicodedata
def remover_acentos(texto):
nfkd = unicodedata.normalize('NFKD', texto)
return ''.join(c for c in nfkd if not unicodedata.combining(c))
print(remover_acentos("café")) # cafe
print(remover_acentos("olá")) # ola
import html
entrada = ""
seguro = html.escape(entrada)
print(seguro) # <script>alert('xss')</script>
A validação no cliente pode ser contornada simplesmente desabilitando o JavaScript ou enviando uma requisição diretamente para o servidor com dados maliciosos. Por exemplo, um atacante pode usar ferramentas como curl para enviar dados sem passar pela interface do navegador. Portanto, a validação deve ser sempre feita no servidor, onde o atacante não tem controle.
import sqlite3
conn = sqlite3.connect('exemplo.db')
cursor = conn.cursor()
nome_usuario = "joão'; DROP TABLE usuarios; --"
cursor.execute("INSERT INTO usuarios (nome) VALUES (?)", (nome_usuario,))
conn.commit()
# O parâmetro ? garante que a string seja tratada como dado, não como parte do SQL.