Segurança
além do óbvio
com PHP

Quem sou eu?

Agenda

  • OWASP Top Ten
  • Gerenciamento de sessão
  • Validação de entradas
  • Armazenamento e políticas de senhas
  • Enumeração de usuários
  • Runtime Application Self-Protection

OWASP Top Ten

owasp.org

OWASP Top Ten

OWASP Top Ten - Parte 1/2

OWASP Top Ten

OWASP Top Ten - Parte 2/2

Gerenciamento de sessão

Codificação

Regere o ID da sessão no login e logout para mitigar Session Fixation

if ($user = $this->loginFrom($_POST)) {
    session_start();
    session_regenerate_id(true); // true para apagar a sessão antiga
}

Adicione camadas extras de proteção à sessão

protected function afterLogin() : void
{
    $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
    $_SESSION['ua'] = $_SERVER['HTTP_USER_AGENT'];
    $_SESSION['timestamp'] = time();
}

Por mais que IP e User-Agent possam ser forjados, é uma camada extra de proteção

Mas tome cuidado com o IP, pois proxies e ISPs podem fazer com que ele mude constantemente

protected function isSessionValid() : bool
{
    if ((empty($_SESSION['ip']))
        || (empty($_SESSION['ua']))
        || (empty($_SESSION['timestamp']))
        || ($_SESSION['ip'] !== $_SERVER['REMOTE_ADDR'])
        || ($_SESSION['ua'] !== $_SERVER['HTTP_USER_AGENT'])) {
        session_regenerate_id(true);
        $_SESSION = [];
		return false;
    }

    // Opcional: regerar o ID a cada X minutos
    $time = time();
    if ($_SESSION['timestamp'] < $time - 300) {
        session_regenerate_id(true);
        $_SESSION['timestamp'] = $time;
    }

    return true;
}

Configurações

Antes de mais nada...

USE HTTPS!

Com Let's Encrypt, podemos ter
certificados SSL gratuitos


Configurações do PHP

session.cookie_domain="meusite.com.br"
session.cookie_httponly=On
session.cookie_samesite="Strict"
session.cookie_secure=On
session.name="MYSESSNAME"
session.sid_bits_per_character=6
session.sid_length=48
session.use_only_cookies=On
session.use_strict_mode=On
session.use_trans_sid=Off

Referências

Validação de entradas

Nunca confie em entradas do usuário!

“Mas eu estou validando no frontend!”
— Alguém antes de ter sua aplicação invadida
  • Você pode utilizar filtros de XSS na entrada
  • Mas deve tratar a saída
    • Se usar uma ferramenta de templating, verifique sua própria função
    • Senão, use a função nativa:
      htmlentities($str, ENT_QUOTES | ENT_HTML5, $encoding)
  • Valide os dados de entrada de acordo com o que você espera para aquele campo (data type) Exemplo: um nome de uma pessoa pode ter apenas letras, espaços, números, hífen e apóstrofo (não esqueça dos acentos!)
  • Crie uma camada em seu framework que irá validar todos os dados da requisição antes de chegar à camada de negócio
  • Se um campo não possuir um data type estipulado, remova-o da requisição
  • Você pode optar por duas abordagens
    1. Sanitizar a entrada e seguir o processamento com o valor limpo; ou
    2. Parar o processamento com HTTP 400 Bad Request (recomendado para aplicações críticas)

Referências

Armazenamento e políticas de senhas

Políticas

  • Não limite os caracteres permitidos
  • Não coloque um tamanho máximo
  • O tamanho mínimo pode variar de acordo com as necessidades de sua aplicação
    (de acordo com o NIST SP800-132, 10 caracteres é o desejável)
  • Crie mecanismos de complexidade
    Exemplo: ao menos 1 caractere maiúsculo, 1 minúsculo, 1 número e 1 caractere especial

Armazenamento

  • Aplique um algoritmo de hash para transformá-la em um texto com tamanho fixo
  • Utilize um salt específico para aquela senha
  • Criptografe o hash gerado para garantir que você possa recriptografar todas as senhas caso haja vazamento do banco de dados

Se possível, use a libsodium!

Uma biblioteca segura, moderna e fácil de usar...
(por mais que os métodos sejam um pouco verbosos)


Sim, estou de olho em você, sodium_crypto_sign_keypair_from_secretkey_and_publickey()

Referências

O que um sistema que rode o código a seguir pode dar de informação a um atacante?
public function login(string $username, string $password) : bool
{
    $user = $this->findByUsername($username);
    if (! $user) {
        return false;
    }
    if (! $this->verifyPassword($password, $user->getPassword())) {
        return false;
    }
    return true;
}

Enumeração de usuários

É possível saber que usuários existem em sua aplicação através de Timing attacks

public function login(string $username, string $password) : bool
{
    $storedPassword = $this->generateFakePassword();
    $user = $this->findByUsername($username);
    if ($user) {
        $storedPassword = $user->getPassword();
    }
    return ($this->verifyPassword($password, $storedPassword))
        && ($user !== null);
}

Será que não estamos exagerando?
É realmente possível ver essa diferença?


Vamos testar, então!

Executando os scripts login-user-enumeration.php
e login-user-enumeration-fixed.php

Runtime Application Self-Protection

O que aconteceria se alguém alterasse sua aplicação de alguma maneira?
Além de ferramentas de IDS (Intrusion Detection System, como o OSSEC), podemos colocar lógicas em nossa própria aplicação!
public function bootstrap() : void {
    // Se não for performático sempre, balanceie as validações
    if ($this->shouldValidateRASP()) {
        $this->validateRequest()
            ->validateFiles()
            ->validatePhpConfig()
            ->validateDatabaseConfig()
            ->validateWebServerConfig()
            ->validateServerConfig();
    }
}
Por exemplo, você pode garantir que estamos sempre rodando em HTTPS...
final private function validateRequest() : self {
    if ((!isset($_SERVER['HTTPS']))
        || ($_SERVER['HTTPS'] !== 'on')) {
        throw new RuntimeException('HTTPS only');
    }
    return $this;
}
... ou que um arquivo não tenha sido alterado!
final private function validateFiles() : self {
    $fingerprint = '/path/to/file.crt';
    if (! hash_equals(
        $this->getOriginalFingerprint($file),
        $this->getCertificateFingerprint($file)
    )) {
        $this->alertAuthorities();
        throw new RuntimeException('O ambiente está comprometido');
    }
    return $this;
}

Pontos de atenção

  • Proteger uma aplicação Web não é algo trivial
  • Existem muitos outros ataques e técnicas de defesas além dessas
  • Avalie o que faz sentido para seu sistema
  • Quando possível, utilize técnicas de defesa em camadas

Obrigado!

vcampitelli.github.io