Implementando 2FA por e-mail no WHMCS: arquitetura, código e boas práticas
Voltar para blog

Implementando 2FA por e-mail no WHMCS: arquitetura, código e boas práticas

07/06/2026 · 4 min · Desenvolvimento

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:

  1. As credenciais primárias são validadas.
  2. O sistema detecta que o 2FA está ativo para aquele usuário.
  3. Ele dispara a função *_verify().
  4. Exibe o seu template customizado.
  5. 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)#

ComponenteMinha EstratégiaPor que?
Geração de Códigorandom_int()Evita a previsibilidade do rand() comum.
Expiração5 minutos (TTL)Tempo suficiente para o e-mail chegar, mas curto para ataques.
Armazenamentopassword_hash() em $_SESSIONO código nunca é salvo em texto plano na sessão, mitigando sequestros ou vazamentos.
Enviosendmail() nativoUso a API correta do WHMCS para herdar as configurações de SMTP do core.
Proteção de SessãoVínculo de IP (REMOTE_ADDR)Previne ataques de Session Hijacking vinculando a validação ao IP de origem.
Proteção CSRFToken Criptográfico ÚnicoImpede que atacantes forjem a submissão do formulário de validação.
Rate LimitingMáximo de 5 tentativasBloqueia 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:

  1. 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 global sendmail($templateName, $userId, $mergeFields) ou a API local SendEmail para herdar as configurações de SMTP do core.
  2. 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 usar password_verify comparando com o hash bcrypt gerado via password_hash() e armazenado na sessão.
  3. 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.
  4. 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().
  5. 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:

CC BY-NC

Este post está licenciado sob CC BY-NC.

Comentários

Participe da discussão abaixo.

0 comentários