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

Exercícios

  1. 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".
  2. ✓ Resposta:
    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
    
  3. Escreva um código que normalize uma string para remover acentos, usando unicodedata. Teste com "café" e "olá".
  4. ✓ Resposta:
    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
    
  5. Dada a string "", aplique HTML encoding usando html.escape() e mostre o resultado.
  6. ✓ Resposta:
    import html
    
    entrada = ""
    seguro = html.escape(entrada)
    print(seguro)  # <script>alert('xss')</script>
    
  7. Explique por que a validação no cliente não é suficiente para segurança e dê um exemplo de como ela pode ser contornada.
  8. ✓ Resposta:

    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.

  9. 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.
  10. ✓ Resposta:
    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.