Prevenção de Timing Attack no PHP

Artigo que explica o que são e como previnir ataques baseados em tempo (timing attack) no PHP.

Introdução

Timing Attack (ataque baseado em tempo) é um tipo de ataque a sistemas. Ele é pouco conhecido e normalmente é realizado por crackers mais avançados. Neste artigo, veremos o que são estes tipos de ataque e como podem ser prevenidos no PHP.

O que é Timing Attack?

Timing Attack, como o nome diz, é um tipo de ataque baseado no tempo. Basicamente o ataque consiste em tentar descobrir informações relevantes de um sistema analisando o tempo que o sistema leva para realizar determinadas operações.

Como o computador funciona com um processador baseado em ciclos, o tempo gasto para realizar o mesmo conjunto de operações mais de uma vez é praticamente idêntico. Então se a pessoa consegue interagir com o sistema passando um dado de entrada diferente, ele consegue analisar o tempo que levou para processar aquela informação.

O que pode ser descoberto com Timing Attacks?

Normalmente, este tipo de ataque está associado à descoberta de senhas. Ou seja, a pessoa tenta explorar o formulário de "login" do sistema passando senhas diferentes e analisando o tempo que o sistema levou para dizer que a senha é inválida.

A ideia central deste ataque é explorar um comportamento dos sistemas durante a comparação de strings. Normalmente as linguagens de programação oferecem um mecanismo para comparação de strings que percorre caractere por caractere, comparando-os e retornando false assim que encontra uma diferença. Ou seja, se o primeiro caractere de duas strings já forem diferentes, a operação levará pouquíssimo tempo para descobrir que as strings são diferentes. Porém, se as duas strings forem grandes e os 100 primeiros caracteres forem iguais, mas o 101º for diferente, então a operação levará mais tempo, pois precisará percorrer 100 caracteres até perceber a diferença.

Como prevenir Timing Attacks no PHP?

Bom, se o ataque é baseado no tempo, então a solução não poderia ser diferente: ela também é baseada no tempo. Ou seja, a solução é fazer com que determinada operação sensível ao tempo seja realizada em um tempo que não ajude ninguém a descobrir informações, por exemplo, realizando a operação em um tempo constante ou então realizando em um tempo totalmente aleatório.

No exemplo citado anteriormente, da descoberta de senhas, isso pode ser feito fazendo uma função para comparar string que percorra toda a string para então devolver um resultado. Sim, ela terá uma performance pior do que a comparação tradicional, mas é uma quantidade de tempo insignificante. Veja um exemplo:

function comparar_string_tempo_constante($str_secreta, $str_usuario) {
    $len_secreta = strlen($str_secreta);
    $len_usuario = strlen($str_usuario);

    $resultado = 0;
    for ($i = 0; $i < $len_usuario; $i++) {
        $ord_secreta = isset($str_secreta[$i]) ? ord($str_secreta[$i]) : ord($str_usuario[$i]);
        $ord_usuario = ord($str_usuario[$i]);
        $resultado |= $ord_secreta ^ $ord_usuario;
    }
    $resultado |= $len_secreta ^ $len_usuario;

    return ($resultado === 0);
}

Observe que percorremos todos os caracteres da string passada pelo usuário e comparamos caractere por caractere com a string secreta. Caso a string secreta seja menor que a do usuário, utilizamos o mesmo caractere da string secreta para ser comparada (note que no operador ternário fazemos operações similares, para gastar o mesmo tempo). Por fim, também comparamos o tamanho das strings para garantir se são realmente iguais. Neste exemplo, a função retornará o resultado em um tempo diretamente proporcional ao tamanho da string passada pelo usuário. Se ele passar uma string pequena, será rápido e se passar uma string grande, levará mais tempo. Ou seja, o tempo para comparar não é diretamente proporcional à posição em que existe diferenças entre as duas strings.

Observação

No PHP 5.6, no entanto, foi criada a função hash_equals na extensão hash. Ela faz exatamente isso que precisamos, ou seja, uma comparação segura quanto ao tempo para comparar hashes de senhas.

5 comentários

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, MegaBrasil Host

Note que a prevenção de "timing attack" não está diretamente relacionada ao tipo de algoritmo que você usou para criptografar algo, mas sim ao operador que você usou para comparar as strings de hash.

Normalmente se faz assim:

// Supondo uma senha codificada com MD5
$md5 = md5($_POST['senha']);
$senha_eh_valida = ($md5 == $senha_codificada_no_bd);

// Suponto uma senha codificada com cript
$hash = crypt($_POST['senha'], $senha_codificada_no_bd);
$senha_eh_valida = ($hash == $senha_codificada_no_bd);

Note que nestes dois exemplos acima, utilizei o operador "==" (igual) para comparar o hash produzido e o hash armazenado no BD.

A função mostrada no artigo serve justamente para comparar os dois hashes, assim como o operador "==" ou como a função strcmp. A diferença é que a função mostrada no artigo leva um tempo diferente que o "==" ou "strcmp" para devolver a resposta e, com isso, o usuário que informou a senha não consegue usar o tempo de resposta da aplicação para deduzir nada.

Felippi disse...

Se o hash da senha informada é feito no php, creio que fica mais difícil de obter alguma informação, em especial se for colocado uma semente junto na hora de gerar esse hash.
Mas segurança nunca é demais, então melhor previnir.

Icaro disse...

mas coloca na pratica seria difícil se tratando de um servidor PHP com recurso compartilhado, o tempo de resposta da tentativa de senha poderia estar ligado ao compartilhamento de hardware com outras aplicações ou latência do webserver. Isso funciona melhor localmente.