Segurança de Constraint Layers
Esta aula aborda a segurança de constraint layers, camadas de restrição que controlam o que entra e sai de um sistema. Exploramos boundary validation, input controls e secure defaults, com exemplos práticos e boas práticas para implementar essas defesas de forma eficaz.
Em segurança da informação, um constraint layer é uma camada de controle que impõe restrições sobre os dados e operações em um sistema. Essas camadas são fundamentais para garantir que apenas entradas válidas sejam processadas, que os limites do sistema sejam respeitados e que configurações padrão não introduzam vulnerabilidades. Nesta aula, vamos explorar três componentes essenciais de constraint layers: boundary validation, input controls e secure defaults.
Entender e implementar corretamente essas camadas é crucial para prevenir ataques como injeção de código, buffer overflow, cross-site scripting (XSS) e muitas outras vulnerabilidades. Vamos mergulhar em cada conceito, com exemplos práticos e recomendações para aplicar em seus projetos.
Boundary validation
Boundary validation (validação de limites) é o processo de verificar se os dados estão dentro dos limites aceitáveis antes de serem processados. Isso inclui limites de tamanho, tipo, formato e valor. A validação de limites é uma defesa fundamental porque muitos ataques exploram a falta de verificação de limites, como enviar dados excessivamente longos para causar buffer overflow ou valores fora da faixa esperada para quebrar a lógica do sistema.
Por exemplo, em uma aplicação web que aceita o nome de um usuário, a boundary validation deve garantir que o campo não exceda um certo número de caracteres (ex.: 50) e que contenha apenas caracteres permitidos (letras, espaços, hífens). Da mesma forma, em um sistema de idade, deve-se verificar que o valor é um número inteiro entre 0 e 120. Sem essa validação, um atacante poderia enviar uma string gigante ou um número negativo, causando erros ou comportamento inesperado.
A implementação de boundary validation pode ser feita tanto no cliente quanto no servidor, mas a validação no servidor é essencial, pois o cliente pode ser facilmente contornado. Em aplicações web, frameworks modernos oferecem mecanismos de validação, mas é importante entender como eles funcionam e configurá-los corretamente.
Aqui está um exemplo em Python usando Flask e a biblioteca cerberus para validar limites:
from flask import Flask, request, jsonify
from cerberus import Validator
app = Flask(__name__)
schema = {
'nome': {'type': 'string', 'maxlength': 50, 'regex': '^[a-zA-Z\s-]+$'},
'idade': {'type': 'integer', 'min': 0, 'max': 120}
}
@app.route('/usuario', methods=['POST'])
def criar_usuario():
dados = request.get_json()
v = Validator(schema)
if v.validate(dados):
# processa os dados com segurança
return jsonify({'status': 'ok'})
else:
return jsonify({'erros': v.errors}), 400
if __name__ == '__main__':
app.run()Nesse exemplo, a validação de limites garante que o nome tenha no máximo 50 caracteres e apenas caracteres permitidos, e que a idade seja um inteiro entre 0 e 120. Isso evita que dados maliciosos sejam processados.
Input controls
Input controls são mecanismos que restringem e sanitizam a entrada do usuário. Eles incluem a validação de formato, a filtragem de caracteres perigosos e a normalização de dados. Enquanto a boundary validation foca em limites, os input controls lidam com o conteúdo da entrada, garantindo que ele seja seguro para o contexto em que será usado.
Por exemplo, ao aceitar uma URL, é importante verificar que ela começa com http:// ou https:// e que não contém caracteres de controle. Ao aceitar um email, deve-se validar o formato. Além disso, quando a entrada é usada em consultas SQL, é essencial usar prepared statements ou escapar caracteres especiais para evitar injeção SQL.
Existem duas abordagens principais para input controls: allowlist (lista de permitidos) e blocklist (lista de bloqueados). A allowlist é mais segura, pois define exatamente o que é aceito, enquanto a blocklist tenta bloquear padrões conhecidos de ataque, mas pode ser contornada. Sempre que possível, prefira allowlist.
Um exemplo de input control em PHP usando filtros:
$email = filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);
if ($email === false) {
die('Email inválido');
}
$url = filter_var($_POST['url'], FILTER_VALIDATE_URL);
if ($url === false || !preg_match('/^https?:\/\//', $url)) {
die('URL inválida');
}Aqui, o PHP verifica se o email tem formato válido e se a URL é válida e começa com http(s). Isso impede que entradas malformadas sejam processadas.
Além de validar, a sanitização é importante. Por exemplo, para evitar XSS, você pode usar htmlspecialchars ao exibir dados do usuário. Em Python, bibliotecas como bleach podem sanitizar HTML.
import bleach
comentario = "Olá"
limpo = bleach.clean(comentario, tags=['p', 'b'], attributes={}, strip=True)
print(limpo) # Saída: OláOs input controls são uma camada vital para proteger a aplicação contra dados maliciosos.
Secure defaults
Secure defaults referem-se à prática de configurar sistemas e aplicações com opções seguras por padrão, exigindo que o usuário opte explicitamente por configurações menos seguras. Isso é crucial porque muitos usuários não alteram as configurações padrão, e se o padrão for inseguro, o sistema fica vulnerável.
Exemplos de secure defaults incluem: usar criptografia forte por padrão, desabilitar serviços desnecessários, exigir senhas fortes, e não expor informações sensíveis em mensagens de erro. Em desenvolvimento de software, isso significa que as bibliotecas e frameworks devem ter configurações seguras por padrão, como a validação de entrada habilitada, e que os desenvolvedores devem ser forçados a desativar explicitamente as proteções.
Um caso clássico é o uso de cookies com a flag Secure e HttpOnly por padrão. Em frameworks web, você deve definir essas flags automaticamente. Em Python, usando Flask, você pode configurar a sessão para ser segura:
app.config.update(
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE='Lax'
)Outro exemplo é a política de senhas: em vez de permitir senhas fracas, o sistema deve exigir um comprimento mínimo e complexidade por padrão. Em um sistema de banco de dados, as permissões padrão devem ser mínimas, seguindo o princípio do menor privilégio.
A implementação de secure defaults também envolve a documentação clara e a conscientização dos usuários. Muitas vezes, os usuários precisam alterar as configurações para aumentar a segurança, mas se o padrão já é seguro, o risco é reduzido.
Em resumo, secure defaults são uma estratégia proativa que minimiza a superfície de ataque e reduz a probabilidade de erros de configuração.
Boas práticas e observações finais
Ao implementar constraint layers, considere as seguintes boas práticas:
- Valide tudo no servidor: Nunca confie na validação do cliente como única defesa.
- Use allowlists em vez de blocklists sempre que possível.
- Centralize a validação: Crie funções ou classes reutilizáveis para validar entradas.
- Mantenha as bibliotecas atualizadas para obter correções de segurança.
- Teste seus constraint layers com casos de borda e ataques comuns.
Lembre-se de que a segurança é um processo contínuo. Constraint layers são apenas uma parte de uma estratégia de defesa em profundidade.
Referências
- OWASP Input Validation Cheat Sheet
- OWASP Top Ten
- MDN: Website security
- OWASP Secure Product Design Cheat Sheet
- Python Security documentation
Exercícios
- Explique a diferença entre boundary validation e input controls, dando um exemplo de cada.
- Por que a validação no cliente não é suficiente para segurança? Cite um cenário de ataque.
- Implemente uma função em Python que valide uma string como nome de usuário, usando allowlist, com no máximo 20 caracteres, apenas letras e números, e que não comece com número.
- Cite três exemplos de secure defaults em desenvolvimento web e explique cada um.
- Descreva um cenário onde a falta de boundary validation pode levar a uma vulnerabilidade de buffer overflow. Como você corrigiria?
import re
def validar_usuario(nome):
if len(nome) > 20:
return False
if not re.fullmatch(r'[a-zA-Z][a-zA-Z0-9]*', nome):
return False
return True