Pular para o conteúdo

Account Protection

O Account Protection olha os formulários que os seus visitantes enviam, sem captcha no caminho. Ele faz três coisas:

  • No login, confere se a senha digitada já apareceu num vazamento conhecido e avisa o seu app num cabeçalho. O app decide o que fazer, por exemplo pedir uma senha nova. Só 5 caracteres do hash da senha saem do conector.
  • Limita as tentativas de login por conta, contadas pelo usuário digitado e não pelo IP. Uma botnet tentando uma conta a partir de mil endereços é barrada como uma só.
  • Faz o formulário recusar robô. Um envio que preenche um campo-isca escondido, ou chega menos de 2 segundos depois de a página carregar, é devolvido.

O Account Protection faz parte do plano Pro.

Ele roda no conector: o proxy do Qlyne ou o Worker da Cloudflare. Com o conector instalado:

  1. No painel, abra o seu site e vá em Account Protection, Configuração.
  2. Escolha um nível de proteção (abaixo), depois abra Avançado e acrescente as suas rotas. Sem rota, nada é conferido.
  3. Deixe o conector em “Só observando” por alguns dias e olhe os Relatórios do Account Protection. Quando os números fizerem sentido, mude para “Aplicar” em Conector.

Na Cloudflare, as rotas do Worker precisam cobrir o endereço do login e as páginas dos formulários. As tentativas por conta usam o limite de requisições da Cloudflare, que exige o conector publicado com o Wrangler e o wrangler.toml do painel (veja Instale o conector). O aviso de senha vazada e os formulários funcionam também na instalação pelo painel da Cloudflare.

  • Rotas de login: o endereço para onde o formulário de login ou o app envia, como /login, /api/auth/login ou /wp-login.php. * casa com qualquer coisa. Só envios POST são conferidos.
  • Campo do usuário e Campo da senha: os nomes dos campos no formulário, email e password por padrão. O WordPress usa log e pwd. Em JSON, use ponto para entrar no objeto: user.email. Corpos de formulário, multipart e JSON de até 64 KB são lidos.
  • Avisar o meu app quando uma senha está num vazamento conhecido: ligado por padrão. Veja “O que o meu app recebe?” abaixo.
  • Tentativas por conta: 10, 60 ou 300 por minuto, ou sem limite. Contadas pelo usuário digitado, de qualquer IP, em 60 segundos.
  • Rotas de formulário protegidas: a página do formulário e o endereço para onde ele envia, como /contact*, /signup ou /newsletter*. Liste as duas: a página recebe os campos escondidos, o endereço confere.
  • Recusar envios sem os campos que o Qlyne adiciona: desligado por padrão. Ligado, o robô que envia sem nunca abrir a página também é recusado. Ligue só quando todo formulário dessas rotas for escrito em HTML puro.

Use formulário protegido onde a pessoa digita, como contato, cadastro e comentário. Formulário de um clique (adicionar ao carrinho) costuma sair em menos de 2 segundos e seria recusado.

Um nível preenche a configuração abaixo com um clique:

ConfiguraçãoBásicoEquilibradoAgressivo
Aviso de senha vazadaligadoligadoligado
Tentativas por contasem limite60 por minuto10 por minuto
Recusar envios sem os campos que o Qlyne adicionadesligadodesligadoligado

O nível nunca mexe nas suas rotas nem nos nomes dos campos. Se você muda uma configuração à mão, o nível aparece como Personalizado.

  • Tentativas demais numa conta: 429 com Retry-After: 60. O navegador recebe a página “Muitas requisições”, que recarrega sozinha. Uma chamada de API recebe {"code": "RATE_LIMITED"}.
  • Formulário recusado: 403. O navegador recebe a página “Formulário não enviado”, que diz “Volte, confira o formulário e envie de novo.” Uma chamada de API recebe {"code": "FORM_REJECTED"}.
  • Senha vazada: nada. O login segue para o seu app, e o app decide o que mostrar.
  • A página com formulário protegido fica igual. Cada formulário POST ganha um campo-isca escondido e um carimbo de tempo, e a página sai com Cache-Control: no-store, para cada visita receber um carimbo novo. Nenhum cookie é gravado.
  • No modo observar ninguém é barrado. O relatório conta o que teria sido limitado ou recusado.
  • O aviso de senha vazada só vale se o seu app ler o cabeçalho Qlyne-Leaked-Password. O Qlyne nunca recusa o login por conta própria. Se a consulta passa de 1,5 segundo ou o serviço de vazamentos está fora, o login segue sem o cabeçalho.
  • O formulário protegido funciona em formulário escrito em HTML puro na página. Formulário montado por JavaScript, ou enviado em JSON, não recebe os campos escondidos. Ele passa sem conferência, ou é recusado quando “Recusar envios sem os campos que o Qlyne adiciona” está ligado.
  • O robô que abre a página, espera mais de 2 segundos e deixa a isca vazia passa. Um desafio na mesma página (Bot Management) faz cada tentativa custar trabalho.
  • As tentativas por conta contam todo POST na rota de login, inclusive login certo. Muita gente usando a mesma conta pode chegar ao limite.
  • No Next.js, o formulário protegido não funciona: o middleware nunca vê a página, então não consegue pôr os campos. Lá, use o captcha de formulário.

Não. O conector calcula o hash SHA-1 da senha e manda ao Have I Been Pwned só os 5 primeiros caracteres. Centenas de senhas têm esse mesmo começo, e a comparação acontece dentro do conector. A senha, o usuário e o formulário nunca vão ao Qlyne: o relatório recebe só a ação e a rota. As tentativas por conta são contadas sob um hash do usuário.

Se o plugin escreve um <form method="post"> normal na página, sim: liste a página e o endereço para onde ele envia. Alguns plugins mandam o formulário por JavaScript para outro endereço, como /wp-admin/admin-ajax.php no WordPress. Funciona quando o plugin manda todos os campos do formulário, os escondidos também, como formulário e não como JSON. Teste antes no modo observar: se os seus próprios envios aparecem como “Recusaria o formulário” no relatório, deixe essa rota de fora.

O login com senha vazada chega ao app com o cabeçalho Qlyne-Leaked-Password: 1, mesmo com os outros sinais desligados. Confira no código do login e, depois de aceitar a senha, peça uma nova:

// Node
const leaked = req.headers['qlyne-leaked-password'] === '1';
// Laravel
$leaked = $request->header('Qlyne-Leaked-Password') === '1';

O formulário protegido chega ao app com dois campos a mais, qlyne_hp (vazio) e qlyne_ft (o carimbo). O app pode ignorar os dois. Envio recusado nunca chega ao app.

As tentativas por conta barram muitos endereços tentando adivinhar uma conta. O credential stuffing costuma tentar muitas contas, poucas vezes cada, e fica abaixo desse limite. Duas outras configurações cobrem esse caso. O aviso de senha vazada marca o login que usa senha de um vazamento conhecido, que é do que as listas de stuffing são feitas. Um limite por IP na rota de login, em WAF & Firewall, pega um endereço tentando muitas contas.

Não, o Account Protection faz parte do Pro. Toda conta nova tem 7 dias de Pro, sem cartão. No Free, as rotas que você salvou ficam no painel, mas o conector não as aplica até o site voltar ao Pro. Veja Planos.

Gente de verdade recebe “Formulário não enviado”

Seção intitulada “Gente de verdade recebe “Formulário não enviado””

Três causas, da mais comum para a menos:

  • A pessoa enviou em menos de 2 segundos, como acontece em formulário de um clique. Deixe essas rotas de fora.
  • Só o endereço para onde o formulário envia está na lista, não a página, então o formulário não tem carimbo. Com “Recusar envios sem os campos que o Qlyne adiciona” ligado, ele é recusado. Liste a página também.
  • O formulário é montado por JavaScript ou enviado em JSON, com essa mesma opção ligada. Desligue a opção, ou deixe a rota de fora.

Todo POST na rota de login conta, inclusive login certo, e a conta é por usuário. Uma conta compartilhada pode chegar a 10 por minuto. Escolha 60 ou 300 por minuto, ou sem limite. A contagem recomeça depois de 60 segundos.

As tentativas por conta não fazem nada na Cloudflare

Seção intitulada “As tentativas por conta não fazem nada na Cloudflare”

O Worker conta as tentativas com o limite de requisições da Cloudflare, que a instalação pelo painel não configura. Publique com o Wrangler e o wrangler.toml do painel, e confira se uma rota do Worker cobre o endereço do login.