Implementar a autenticação em dois fatores (2FA) no WHMCS via e-mail é uma daquelas soluções estratégicas que eu adoto quando preciso elevar o nível de segurança do frontend sem forçar o usuário a depender de apps externos ou tokens físicos. É prático, eficiente e, se bem feito, muito seguro.
Neste artigo, vou abrir o capô do que desenvolvi, detalhando como o WHMCS trata esses módulos internamente e as decisões técnicas que tomei para garantir um código limpo, funcional e blindado contra vulnerabilidades comuns.
1. O ciclo de vida do 2FA no WHMCS#
Uma coisa que aprendi apanhando do sistema é que o WHMCS já possui um framework nativo para segurança. Os módulos ficam localizados em: /modules/security/
O fluxo que o sistema executa por baixo dos panos é elegante:
- As credenciais primárias são validadas.
- O sistema detecta que o 2FA está ativo para aquele usuário.
- Ele dispara a função
*_verify(). - Exibe o seu template customizado.
- Aguarda o POST para executar a função
*_validate().
Isso significa que eu não precisei interceptar o processo de login manualmente; eu apenas trabalhei dentro do "sandbox" que o WHMCS fornece.
2. A arquitetura do módulo#
Para este projeto, estruturei o diretório da seguinte forma:
modules/
└── security/
└── email2fa/
├── email2fa.php
└── template.tpl
Decisões técnicas de segurança (hardening)#
| Componente | Minha Estratégia | Por que? |
|---|---|---|
| Geração de Código | random_int() | Evita a previsibilidade do rand() comum. |
| Expiração | 5 minutos (TTL) | Tempo suficiente para o e-mail chegar, mas curto para ataques. |
| Armazenamento | password_hash() em $_SESSION | O código nunca é salvo em texto plano na sessão, mitigando sequestros ou vazamentos. |
| Envio | sendmail() nativo | Uso a API correta do WHMCS para herdar as configurações de SMTP do core. |
| Proteção de Sessão | Vínculo de IP (REMOTE_ADDR) | Previne ataques de Session Hijacking vinculando a validação ao IP de origem. |
| Proteção CSRF | Token Criptográfico Único | Impede que atacantes forjem a submissão do formulário de validação. |
| Rate Limiting | Máximo de 5 tentativas | Bloqueia ataques de força bruta, invalidando a sessão e registrando o log. |
3. O código operacional (email2fa.php)#
Aqui está a implementação real com foco total em segurança. Note como aplicamos hash criptográfico ao código, validamos a integridade da requisição por meio de IP e token CSRF, limitamos as tentativas e validamos a entrada do usuário com expressões regulares:
<?php
if (!defined("WHMCS")) {
die("Acesso direto não permitido.");
}
function email2fa_config() {
return [
'name' => '2FA via E-mail Custom',
'description' => 'Módulo técnico para autenticação secundária via e-mail corporativo com hardening de segurança.',
'version' => '1.1',
'author' => 'Percio Castelo',
];
}
function email2fa_verify($params) {
$userId = $params['user_id'];
// Geração do token CSRF para prevenção de submissões maliciosas
$csrfToken = bin2hex(random_bytes(32));
// Geração do código utilizando random_int para garantir entropia criptográfica
$code = (string)random_int(100000, 999999);
// Armazena com hash seguro (password_hash) em vez de texto plano
$_SESSION['email2fa_hash'] = password_hash($code, PASSWORD_DEFAULT);
$_SESSION['email2fa_expire'] = time() + 300; // Validade de 5 min
$_SESSION['email2fa_ip'] = $_SERVER['REMOTE_ADDR'] ?? '';
$_SESSION['email2fa_csrf'] = $csrfToken;
$_SESSION['email2fa_attempts'] = 0; // Inicializa contador de tentativas
// O template 'email2fa_template' deve ser criado no painel admin.
// Usamos a função correta 'sendmail()' do core do WHMCS.
sendmail("email2fa_template", $userId, [
"code" => $code
]);
return ['success' => true];
}
function email2fa_validate($params) {
// 1. Validação de IP para evitar sequestro de sessão (Session Hijacking)
$remoteIp = $_SERVER['REMOTE_ADDR'] ?? '';
if (!isset($_SESSION['email2fa_ip']) || $_SESSION['email2fa_ip'] !== $remoteIp) {
return ['error' => 'IP de origem inválido para esta sessão de autenticação.'];
}
// 2. Validação do Token CSRF
$csrfInput = $_POST['csrf_token'] ?? '';
if (!isset($_SESSION['email2fa_csrf']) || !hash_equals($_SESSION['email2fa_csrf'], $csrfInput)) {
return ['error' => 'Token CSRF inválido ou ausente.'];
}
// 3. Validação de presença dos dados da sessão
if (!isset($_SESSION['email2fa_hash']) || !isset($_SESSION['email2fa_expire'])) {
return ['error' => 'Sessão expirada ou código não solicitado.'];
}
// 4. Rate Limiting (Máximo de 5 tentativas)
if ($_SESSION['email2fa_attempts'] >= 5) {
unset($_SESSION['email2fa_hash'], $_SESSION['email2fa_expire'], $_SESSION['email2fa_ip'], $_SESSION['email2fa_csrf'], $_SESSION['email2fa_attempts']);
logActivity("Bloqueio de 2FA: Limite de tentativas excedido para o usuário ID " . $params['user_id']);
return ['error' => 'Limite de tentativas excedido. Um novo código de verificação é necessário por segurança.'];
}
// 5. Validação da expiração do tempo limite (TTL)
if (time() > $_SESSION['email2fa_expire']) {
unset($_SESSION['email2fa_hash'], $_SESSION['email2fa_expire'], $_SESSION['email2fa_ip'], $_SESSION['email2fa_csrf'], $_SESSION['email2fa_attempts']);
return ['error' => 'O código de segurança expirou. Solicite um novo.'];
}
$input = $_POST['email2fa_code'] ?? '';
// 6. Sanitização e validação regex do input (apenas números de 6 dígitos)
if (!preg_match('/^[0-9]{6}$/', $input)) {
$_SESSION['email2fa_attempts']++;
return ['error' => 'Formato de código inválido. Digite exatamente 6 dígitos numéricos.'];
}
// 7. Comparação estrita e segura usando password_verify para mitigar timing attacks e type juggling
if (password_verify($input, $_SESSION['email2fa_hash'])) {
unset($_SESSION['email2fa_hash'], $_SESSION['email2fa_expire'], $_SESSION['email2fa_ip'], $_SESSION['email2fa_csrf'], $_SESSION['email2fa_attempts']);
return ['success' => true];
}
// Incrementa tentativas em caso de falha
$_SESSION['email2fa_attempts']++;
$remaining = 5 - $_SESSION['email2fa_attempts'];
return ['error' => "Código inválido. Você tem mais {$remaining} tentativa(s)."];
}
4. O front-end: template.tpl#
O template precisa ser limpo, seguir os padrões de acessibilidade e sem nomes de classes CSS inválidos que comecem com dígitos. Utilizamos a injeção do token CSRF e atributos modernos para facilitar a inserção no mobile:
<div class="fa-2fa-container">
<h3>Verificação de Segurança</h3>
<p>Enviamos um código único para o seu e-mail de cadastro.</p>
<form method="post" action="login.php?backupcode=1">
<!-- Token CSRF integrado dinamicamente a partir da sessão -->
<input type="hidden" name="csrf_token" value="{$smarty.session.email2fa_csrf}">
<!-- Input otimizado com autocomplete, inputmode e pattern numérico -->
<input type="text"
name="email2fa_code"
class="form-control"
placeholder="000000"
maxlength="6"
autocomplete="one-time-code"
inputmode="numeric"
pattern="[0-9]*"
required
autofocus>
<button type="submit" class="btn btn-primary btn-block">Validar Acesso</button>
</form>
{if $error}
<div class="alert alert-danger mt-3">{$error}</div>
{/if}
</div>
5. Lições aprendidas e hardening de segurança#
Durante o refinamento do módulo, alguns pontos críticos de segurança e arquitetura foram implementados para mitigar riscos comuns em produção:
- Função de Envio Correta: A função
sendMessage()não existe no core do WHMCS para essa finalidade. O correto é usar a função globalsendmail($templateName, $userId, $mergeFields)ou a API localSendEmailpara herdar as configurações de SMTP do core. - Mitigação de Type Juggling e Timing Attacks: O uso de comparações frouxas (
==) em validações de tokens é perigoso em PHP. A verificação do código deve usarpassword_verifycomparando com o hash bcrypt gerado viapassword_hash()e armazenado na sessão. - Proteção contra Hijacking de Sessão: Vincular a sessão do 2FA ao IP remoto (
$_SERVER['REMOTE_ADDR']) garante que o formulário não seja validado a partir de outra máquina caso o ID de sessão seja exposto. - Proteção CSRF: Sem um token de autenticidade (CSRF), a validação de 2FA fica vulnerável. Usamos um token dinâmico persistido na sessão e validado com
hash_equals(). - Rate Limiting Ativo: Para deter ataques de força bruta, implementamos um teto de 5 tentativas. Se excedido, a sessão é destruída, a atividade é gravada no log de auditoria via
logActivity()e o acesso é bloqueado até que um novo fluxo de login seja iniciado.
Considerações práticas#
Desenvolver este módulo me mostrou que a arquitetura do WHMCS é robusta o suficiente para permitir extensões de segurança sem "hackear" o core. Ao implementar o 2FA por e-mail seguindo estas práticas de hardening, você ganha em adesão do usuário (UX alta) sem abrir mão da integridade das contas.
Para mim, segurança é o baseline de qualquer entrega profissional. Espero que esse mergulho técnico ajude você a blindar ainda mais seu WHMCS!
Este artigo foi útil?
Deixe uma reação rápida para apoiar o conteúdo:
Este post está licenciado sob CC BY-NC.



Comentários
Participe da discussão abaixo.
0 comentários