
Exploit de prova de conceito para CVE-2019-0217, uma condição de corrida no mod_auth_digest do Apache HTTP Server permitindo bypass de autenticação. Inclui ambiente vulnerável baseado em Docker e script de automação Burp Turbo Intruder.
Este repositório contém um exploit de prova de conceito para o CVE-2019-0217, juntamente com um Dockerfile para configurar um servidor web vulnerável ao CVE.
No Apache HTTP Server 2.4, versão 2.4.38 e anteriores, uma condição de corrida no mod_auth_digest quando executado em um servidor com threads poderia permitir que um usuário com credenciais válidas se autenticasse usando outro nome de usuário, contornando as restrições de controle de acesso configuradas.
Quando um cliente tenta acessar um recurso protegido por Autenticação Digest HTTP, o servidor responde com um código de status 401, juntamente com um cabeçalho WWW-Authenticate, aproximadamente no formato abaixo:
Digest realm="rlm", nonce="TToc8M0jBgA=7afa4f292c97632a6c17eec458d3db31021b111f", algorithm=MD5, qop="auth"
O cliente calcula o HA1, que é o resumo MD5 de A1 (ou seja, username:realm:password), e depois adiciona a ele o nonce e outros metadados, como cnonce, para obter A2, cujo resumo MD5, por sua vez, produz HA2, que o cliente envia como resposta, juntamente com o nome de usuário, cnonce e outros metadados, no cabeçalho Authentication:
'Digest username="attacker", realm="rlm", nonce="TToc8M0jBgA=7afa4f292c97632a6c17eec458d3db31021b111f", uri="/scripts/userprofile.cgi", response="bcb433e4071fa228fe0c9452a7495efd", algorithm="MD5", qop="auth", nc=00000001, cnonce="2309510923095109"
^^^^^^^^^^
Conforme descrito abaixo, a parte destacada do cabeçalho acima pode ser manipulada para criar a condição de corrida necessária para o exploit funcionar.
A vulnerabilidade foi corrigida por este commit.
Analisando o código do módulo mod_auth_digest (especificamente a função get_hash em mod_auth_digest.c), é evidente que o servidor primeiro analisa o cabeçalho Authentication para determinar o que está entre as aspas após username=, que ele trata como o usuário associado à requisição (r->user). Em seguida, ele verifica se esse "usuário" tem acesso ao recurso solicitado ou não. Se tiver, a função get_hash recupera o HA1 correspondente do arquivo de autenticação.
Antes do commit que corrigiu a vulnerabilidade, o HA1 assim recuperado era armazenado na variável conf = (digest_config_rec *) ap_get_module_config(r->per_dir_config,&auth_digest_module), que aparentemente não é thread-safe.
O que nos dá o exploit:
Com base na seção de análise da vulnerabilidade acima, deve ser possível criar uma condição de corrida enviando simultaneamente requisições com cabeçalho Authentication válido, juntamente com requisições com um cabeçalho Authentication forjado (que é o mesmo que o cabeçalho válido, mas com o valor de username= definido como o usuário alvo (como o qual o atacante deseja se autenticar)).
Terminologia usada neste exploit: - Atacante: o usuário que executa este exploit. Ele tem um nome de usuário e senha válidos para a sua própria conta. - Vítima: o usuário pelo qual o atacante deseja se passar. O nome de usuário da vítima é conhecido, mas a senha é (obviamente) desconhecida.
Consegui fazer esse método funcionar disparando vários threads de Python, alguns dos quais enviavam requisições do primeiro tipo e outros do segundo tipo, mas não era confiável e exigia um grande número de requisições para dar certo. Isso me levou a usar o turbo intruder do burp (gratuito), que envia as requisições em paralelo de forma confiável. Infelizmente, o suporte a cli do turbo intruder está quebrado, então algumas partes do exploit precisam ser executadas usando a GUI.
Para usar o servidor vulnerável de exemplo que incluí neste repositório, execute:
$ cd sample_vulnerable_server
$ docker build -t poc_httpd .
$ docker run -p 8038:80 poc_httpd
Execute generate_turbo_intruder_script.py após configurar as variáveis no topo:
# Configuration
URL = 'http://localhost:8038/scripts/userprofile.cgi' # location to the resource protected by digest auth
ATTACKER_USERNAME = 'attacker'
ATTACKER_PASSWORD = 'known'
VICTIM_USERNAME = 'victim'
NUMBER_OF_REQUESTS = 2000 # number of concurrent requests sent to catch a glimpse of the race condition
Esse script então gera dois arquivos, generated/turbo_intruder_script.py e generated/request.txt, no diretório atual.
Agora abra o BurpSuite Community Edition e instale a extensão "Turbo Intruder" indo em Extensions -> BApp Store, caso ainda não tenha feito.
Abra o repeater do burp. Defina o host e a porta do alvo:

Cole o conteúdo de generated/request.txt na seção Request:

Clique com o botão direito em qualquer lugar da seção Request e selecione 'Extensions->Turbo Intruder->Send to Turbo Intruder'.

Cole o conteúdo de generated/turbo_intruder_script.py na seção script:

Por fim, clique no botão Attack na parte inferior da janela do Turbo Intruder. Ele deve mostrar quaisquer resultados bem-sucedidos na tela resultante:
