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

Exercícios

  1. Explique a diferença entre boundary validation e input controls, dando um exemplo de cada.
  2. ✓ Resposta: Boundary validation verifica os limites de dados (tamanho, faixa, tipo), por exemplo, garantir que um campo de idade aceite apenas inteiros entre 0 e 120. Input controls tratam do conteúdo, como validar formato de email e sanitizar HTML para evitar XSS.
  3. Por que a validação no cliente não é suficiente para segurança? Cite um cenário de ataque.
  4. ✓ Resposta: A validação no cliente pode ser contornada, pois o atacante pode manipular requisições HTTP diretamente, sem passar pela interface. Por exemplo, um usuário malicioso pode enviar um POST com um campo 'idade' igual a -1, mesmo que o formulário impeça, se a requisição for feita com curl.
  5. 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.
  6. ✓ Resposta:
    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
  7. Cite três exemplos de secure defaults em desenvolvimento web e explique cada um.
  8. ✓ Resposta: 1) Cookies com flags Secure e HttpOnly por padrão, para evitar acesso via JavaScript e transmissão em HTTP. 2) Configuração de sessão com SameSite=Lax para mitigar CSRF. 3) Validação de entrada habilitada por padrão em frameworks, como o CSRF token automático em frameworks MVC.
  9. Descreva um cenário onde a falta de boundary validation pode levar a uma vulnerabilidade de buffer overflow. Como você corrigiria?
  10. ✓ Resposta: Um cenário clássico é em C, quando uma função como strcpy copia uma string para um buffer sem verificar o tamanho. Se o usuário fornecer uma entrada maior que o buffer, ocorre overflow. A correção é usar funções seguras como strncpy ou, em linguagens gerenciadas, a validação de tamanho antes de processar.