se você gosta de ir ao WordCamps como eu, provavelmente já ouviu isso: “o hash de senha do WordPress não é Seguro” ou na versão mais técnica: “…porque é baseado em md5”.Verdadeiro ou não, um forte hash de senha é crucial para um grande ecossistema como o WordPress, que sempre foi um alvo interessante para hackers. Então, decidi dar uma olhada mais de perto no sistema de hashing e tentar quebrar os hashes do WordPress do zero!
Entendendo os hashes de senha do WordPress
comecei a fazer algumas pesquisas no Google e descobri que a maioria das informações por aí é genérica e confusa. Muitas referências às bibliotecas PHP usadas (hash portátil do phpass), mas nada realmente Concreto.
decidi adotar uma abordagem diferente a partir de uma suposição:
hash é um processo unidirecional, mas o WordPress é já capaz de autenticar usuários correspondentes a sua senha de entrada com o hash armazenado no banco de dados
a Partir daí, comecei a verificar o código e encontrou a primeira função interessante: wp_check_password($password,$hash)
o que compara com a senha de texto sem formatação com o condensado e retorna true se eles combinam.
1// presume the new style phpass portable hash. 2if ( empty( $wp_hasher ) ) { 3 require_once ABSPATH . WPINC . '/class-phpass.php'; 4 // By default, use the portable hash from phpass. 5 $wp_hasher = new PasswordHash( 8, true ); 6} 7 8$check = $wp_hasher->CheckPassword( $password, $hash ); 9 10/** This filter is documented in wp-includes/pluggable.php */ 11return apply_filters( 'check_password', $check, $password, $hash, $user_id );
Indo através do código, logo nos trazer a CheckPassword
, crypt_private
e encode64
que, basicamente, é onde a mágica acontece.
longa história curta, crypt_private($password, $stored_hash)
re-hashes a senha antes de ser comparado com o hash armazenado. Se corresponderem, a senha está correta e a autenticação continua. O que significa que também podemos usar essa função para quebrar o hash.
Hash anatomia
Este é um hash do WordPress:
1$P$BnPVO4gP9JUMSAM1WlLTHPdH6EDj4e1
Por simplicidade, vamos assumir que o site usa PHP>5 e o mais recente phpass portátil de hash, que é o mais comum de instalação.
os primeiros 3 caracteres $P$
são um ID, informando ao sistema que tipo Oh hash temos.
o caractere número 3 (contando de 0) é usado para determinar quantas vezes o md5 () tem que processar a string de entrada.
Caracteres de 4 a 12 nPVO4gP9
são o sal, que é uma string aleatória anexada à senha antes do hash, para dar mais aleatoriedade. Por exemplo, se sua senha for admin, ela será transformada em nPVO4gP9admin e depois em hash.
a parte restante do hash JUMSAM1WlLTHPdH6EDj4e1
é a aleatoriedade real, gerada pela senha salt+passada em uma função encode64
não documentada, que executa algumas operações bit a bit na string de entrada e retorna uma saída de 22 caracteres.
não está claro, uh? Fica comigo.
até agora sabemos:
- a primeira parte do hash é uma fixo id
- o segundo é um único char usado como contador, também fixa
- o terceiro é o sal, amarrado a palavra-passe
- o último é aleatório, gerado por “sal+pass” processado por encode64 função
1
Assim, podemos re-escrever a lógica de salt+hash de senha X vezes e passou em encode64 – para a realização de um dicionário ou de força-bruta ataque, e obter o mesmo “última parte” do hash e que seria um sucesso de hash crack!
em um cenário da vida real, e os hackers estariam mais interessados em encontrar senhas fracas porque são mais propensas a serem reutilizadas, em vez de aleatórias que geralmente são geradas por um gerenciador de senhas e, portanto, específicas do site.
reescrevendo encode64 usando golang
eu decidi ir com golang porque é muito rápido, e precisamos de toda essa velocidade para calcular grandes dicionários de senha.
assim como no procedimento de criptografia do WordPress, o script pega o hash e isola o salt, então ele compõe a senha (salt+pass) tentando cada senha no dicionário e md5-hashing x número de vezes, e vimos como X é determinado.
1itoa64 := "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 2hashloop := 1 << strings.Index(itoa64, string(hash)); // char n. 4 in the hash
Feito isso, ele executa uma série de operação bit a bit em bytes obtidos usando ord() em PHP, o que não precisamos no golang como já temos os valores de byte:
1itoa64 & 0x3f]
neste ponto, eu percebi que nós não precisa mesmo de encontrar toda a cadeia. Basta combinar alguns caracteres para combinar com toda a senha e tornar o script mais eficiente! Então, devido à natureza do processo de hash, se char n. 0 / 4 / 8 … corresponde, a senha passada como entrada está correta.
E BOOM, rachou o hash:
1wphashcrash '$P$BnPVO4gP9JUMSAM1WlLTHPdH6EDj4e1' Dev/wphashcrash/dict.txt 2$P$BnPVO4gP9JUMSAM1WlLTHPdH6EDj4e1 admin 32020/05/30 12:44:15 Executed in 0.000221
O código é, como de costume no GitHub – https://github.com/francescocarlucci/wphashcrash – e, no momento, o script é um POC e recebe apenas uma única senha hash como entrada, e o caminho para a palavra-passe de dicionário.
já descrevi no readme algumas melhorias que seriam boas de ter e provavelmente adicionarei no futuro.
se você realmente quer tentar cortar uma lista de hashes, você pode bifurcá-lo ou apenas embrulhar o script em um bash para loop.
considerações Secuirty
neste ponto, acho que é claro o suficiente que, se um invasor obtém acesso ao banco de dados em um site WordPress, ele/ela pode basicamente quebrar cada senha fraca.
este infeliz, especialmente porque:
- Injeção de SQL não é raro em plugins WordPress
- RCE também irá provavelmente conceder acesso ao DB
- WordPress ecossistema é um enorme território para o freelancer e o trabalho, é muito comum encontrar antigo/não utilizados wp-admin e contas de FTP, criados para necessidades temporárias
Este último ponto, combinado com a enumeração de usuário que é um plugin problema comum, deixa a porta aberta para a obtenção de acesso ao DB usando roubado/penhorado credenciais de administrador.
além disso, muitos sites não impõem senhas fortes para não prejudicar a experiência do Usuário, e tenho experiência direta em qualquer um dos pontos mencionados.
uma vez que um site é comprometido, ter hashes fracos armazenados permite que os invasores comprometam outras contas de usuário em outros sites, se eles tendem a reutilizar senhas e sabemos que sim.
temos soluções?
minha pesquisa não foi tão longe, eu só queria ver como quebrar os hashes do WordPress.
Bcrypt é conhecido como um forte hash do método comparado ao md5, e existe um plugin que usa bcrypt e substitui todas as principais funções necessárias para lidar com senhas:
- wp_check_password()
- wp_hash_password()
- wp_set_password()
E, claro, você pode escrever sua própria solução, como um desenvolvedor, mas acredito que este é um problema para correção no nível central para apontar para uma grande adoção.
nota Final
Existem muitas ferramentas para quebrar hashes, como Hashcat e John The Ripper, que pode ser ainda mais alto desempenho, mas, novamente, o escopo desta pesquisa é compreender o WordPress hash estrutura e rachaduras a partir do zero.
obrigado pela leitura.
frenxi