Pular para o conteúdo

Qlyne no Cloudflare

Um conector que fica na frente do seu site, dentro da sua conta Cloudflare, e aplica as regras que você define no painel do Qlyne:

  • sala de espera nas páginas escolhidas (/ingressos/*, /checkout);
  • limite de quantas pessoas ficam no site ao mesmo tempo: com vaga, a pessoa entra direto; lotado, espera na fila;
  • desafio próprio (proof-of-work invisível, sem Google nem Cloudflare Turnstile) nas páginas escolhidas, e um modo “sob ataque” que desafia toda navegação;
  • limite de requisições por página, contado por IP (IPv6 pelo /64) ou por chave de API, token ou sessão;
  • IPs bloqueados e de confiança, e uma regra para as saídas da rede Tor (desafiar ou bloquear);
  • robôs de IA barrados por categoria (treino, busca com IA, assistentes), também no robots.txt;
  • proteção de formulário sem captcha: campo-isca invisível e tempo mínimo de preenchimento;
  • navegador controlado por ferramenta de automação (Selenium, Puppeteer, Playwright) recusado no desafio;
  • modo “só observar”, que registra o que seria barrado sem barrar ninguém;
  • contagens no painel para cada uma dessas regras.

O conector não fala com o Qlyne a cada requisição. Ele guarda as regras em memória por 60 segundos, e o passe da fila e a liberação do desafio são assinados com um segredo que só o seu conector e o Qlyne conhecem, então ele confere sozinho. Com limite de pessoas no site, ele também pede uma vaga ao Qlyne quando chega alguém sem passe. Se o Qlyne cair, o conector deixa passar: a proteção pode falhar, o seu site não.

A proteção é na camada HTTP (L7). Ataque volumétrico, de rede, é com o Cloudflare.

  • O plano grátis do Qlyne cobre modo observar, IPs, robôs de IA e limite por IP. Sala de espera, limite de pessoas no site, desafio, modo sob ataque, formulários e Tor pedem o plano Pro.
  • O site precisa passar pelo Cloudflare (registro DNS com a nuvem laranja).
  • O conector é um Worker do Cloudflare. O plano gratuito de Workers tem 100 mil requisições por dia e pico de 1.000 por minuto, e cada requisição nas rotas do Worker conta. Cubra só as páginas que precisam de proteção, não imagens nem scripts. Site com mais tráfego precisa do plano pago de Workers. Estourado o limite diário, cada rota faz o que estiver configurado nela no Cloudflare: segue para o seu site sem o Worker (fail open) ou responde o erro 1027 (fail closed).
  1. Abra a página de cadastro e escolha o identificador da conta, o nome do site e o seu e-mail.
  2. Abra o link de confirmação do e-mail e clique em “Ativar e gerar minha chave”. Guarde a chave de API: ela aparece uma única vez.
  3. Na mesma página, clique em “Definir minha senha” e escolha a senha do painel. O link vale 60 minutos; depois disso, use “Forgot your password?” na tela de login (o painel está em inglês).

Entre no painel e abra o seu site. Cada item abaixo está no menu do site:

  1. Em Connector, clique em “Generate secret” e guarde o segredo. Ele aparece uma única vez.
  2. Cadastre as páginas. * casa qualquer sequência: /checkout* cobre /checkout e /checkout/pagar.
    • Bots, páginas com desafio: as que os robôs atacam, como login e checkout.
    • Waiting room, “Routes on your site”: a página e a fila (evento) que ela usa. Crie a fila antes, em Waiting room. O “Target URL” dela precisa ser do mesmo site das páginas protegidas, para a pessoa voltar à página exata de onde saiu. Para limitar quantas pessoas ficam no site ao mesmo tempo, veja a seção com esse nome mais abaixo.
    • Firewall, limites de requisições: strict (10 por minuto), normal (60) ou loose (300), contados por IP. Em API, ou onde muita gente divide o mesmo endereço (operadora de celular, escritório), conte por header (Authorization, X-API-Key) ou pelo cookie da sessão. Requisição sem esse header ou cookie conta pelo IP, e um IP nunca passa de 10 vezes o limite: inventar uma chave nova a cada requisição não fura a regra.
    • Logins and forms, formulários protegidos: a página do formulário e o endereço para onde ele envia. Use em formulário que 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.
  3. Se quiser, liste IPs bloqueados e de confiança e escolha a regra para as saídas Tor (Firewall), e as categorias de robô de IA a barrar (Bots). Os IPs de confiança (seu escritório, monitor de disponibilidade, parceiro que chama sua API) não passam por regra nenhuma.
  4. Deixe o modo em Connector em “Observe only” por enquanto.

Em Connector, “Install on Cloudflare”, clique em Download connector. O arquivo já vem com o identificador da sua conta e o endereço do servidor do Qlyne.

Pelo painel do Cloudflare, sem terminal:

  1. Workers & Pages, Create, Worker: publique o exemplo “Hello World”.
  2. Edit code: troque tudo pelo conector baixado e clique em Deploy.
  3. Settings, Variables and Secrets: adicione um Secret chamado QLYNE_SECRET com o segredo do conector.
  4. Settings, Domains & Routes: adicione uma rota para cada página a proteger, por exemplo loja.com.br/checkout*.

Assim funcionam a sala de espera, o desafio e o modo sob ataque. Os limites de requisições precisam do Wrangler, porque o Cloudflare não deixa criar esse recurso pelo painel (baixe o wrangler.toml de novo ao atualizar o conector: a versão 0.6.0 traz o teto por IP dos limites contados por header ou cookie):

  1. Clique também em Download wrangler.toml e ponha os dois arquivos na mesma pasta.
  2. Coloque as suas rotas no wrangler.toml.
  3. Rode:
Janela do terminal
npx wrangler login
npx wrangler secret put QLYNE_SECRET
npx wrangler deploy

Acesse uma página protegida e confira as últimas 24 horas na Overview do site. Muito “would be challenged” em página comum costuma ser rota larga demais; muito “would be rate limited” pede uma faixa mais folgada. Com os números fazendo sentido, mude o modo para “Enforce” em Connector.

Durante um ataque, o botão “Under attack” desafia toda navegação sem cookie de liberação (o conector pega a mudança em até um minuto). Chamadas de API não são desafiadas, para não quebrar app nem integração.

Limite quantas pessoas ficam no site ao mesmo tempo

Seção intitulada “Limite quantas pessoas ficam no site ao mesmo tempo”

Se o seu servidor só aguenta um certo número de pessoas, abra a fila em Waiting room e preencha “Max users on the site at once”; depois cadastre uma rota para essa fila em Waiting room, “Routes on your site”. Enquanto houver vaga e ninguém esperando, a pessoa vai direto para o site, sem ver a sala de espera. Com o site lotado, quem chega espera na fila e entra quando abre vaga, na ordem de chegada.

Quem passa “Free a slot after” segundos (5 minutos, por padrão) sem nenhuma requisição perde a vaga, e o passe vence junto. O painel mostra “X / Y on the site” na fila.

  • Só funciona no modo “Enforce”, pelo conector.
  • Escolha um número que o servidor aguente com folga. A conta é aproximada (a atividade chega ao Qlyne a cada 10 segundos, mais ou menos), e a taxa de liberação só controla quem sai da fila: com o site vazio, uma rajada de visitantes pode ocupar todas as vagas livres no mesmo segundo.
  • Um mesmo endereço IP (um /64 no IPv6) pega no máximo 10 vagas assim; o resto dele espera na fila.
  • Se o Qlyne não responder em 1,5 segundo, o conector deixa entrar e pergunta de novo 10 segundos depois.

Para cada requisição nas rotas do conector:

  1. Volta da sala de espera com ?qlyne_pass=: o conector confere o passe, guarda num cookie do evento e redireciona para o mesmo endereço sem o parâmetro. Passe inválido mostra uma página de erro em vez de mandar de novo para a fila, então um segredo errado nunca vira um loop.
  2. IP de confiança: segue para o seu site sem passar por regra nenhuma. IP bloqueado, ou saída Tor com a regra de bloqueio: 403 (página “Acesso bloqueado”, ou {"code": "ACCESS_BLOCKED"} para chamada de API).
  3. Endereço da lista de reputação entre sites, com “Block” (Pro): 403, com a mesma página ou {"code": "ACCESS_BLOCKED"}. Com “Challenge”, as visitas às páginas dele recebem o desafio do passo 8.
  4. Requisição que casa com uma regra gerenciada (WAF) no modo “Block”: 403, com a mesma página “Acesso bloqueado” ou {"code": "ACCESS_BLOCKED"}. No “Observe only” ela segue e é contada.
  5. Com a nota de robô ligada (Pro), a nota média ou alta recebe a ação escolhida para ela: atraso, desafio, 403 ou a página-isca. Formulário ou chamada de API numa rota que exige a nota, de um navegador que não rodou o script, recebe 403 com {"code": "SENSOR_REQUIRED"}.
  6. Robô de IA de uma categoria barrada, pelo User-Agent: 403. O /robots.txt sai com um grupo Disallow: / para esses robôs acrescentado no fim.
  7. Página com limite, acima do limite: 429.
  8. Página com desafio (ou modo sob ataque, ou saída Tor com a regra de desafio; só para navegação), sem cookie de liberação: a página do desafio. Chamada de API recebe 403 com {"code": "CHALLENGE_REQUIRED"}. Googlebot e Bingbot passam quando o IP está nas faixas que o Google e o Bing publicam. Navegador controlado por programa de automação não recebe a liberação. O desafio vem antes da sala de espera, então robô que não resolve não pega lugar na fila nem vaga no site.
  9. Formulário protegido: a página ganha o campo-isca e o carimbo de tempo em cada formulário POST. Envio com a isca preenchida, ou menos de 2 segundos depois de a página abrir, recebe 403 (página “Formulário não enviado”, ou {"code": "FORM_REJECTED"}). Envio sem o carimbo, como formulário montado por JavaScript ou mandado em JSON, passa. Essas páginas saem com Cache-Control: no-store, para cada visita receber um carimbo novo.
  10. Página com sala de espera, evento ativo e não encerrado, sem passe válido: redireciona para a sala de espera. Chamada de API (que não pede HTML) recebe 403 com {"code": "QUEUE_REQUIRED", "waiting_room_url": ...}. Com limite de pessoas no site, o conector antes pede uma vaga ao Qlyne; conseguindo, a requisição segue para o site e o passe já é gravado.
  11. O resto segue para o seu site.

Arquivos estáticos (scripts, estilos, imagens, fontes) não recebem fila, desafio nem checagem de formulário; bloqueio de IP, robôs de IA e limite valem.

Para barrar robôs de IA, o conector precisa rodar no site inteiro (uma rota como exemplo.com/*), robots.txt incluído: os robôs buscam todas as páginas. No plano gratuito de Workers isso gasta mais da cota diária.

Cookies, todos HttpOnly; Secure; SameSite=Lax:

CookieConteúdoValidade
qlyne_pass_<evento>passe da fila, assinado pelo Qlynea do access token do evento (“Free a slot after” quando a fila limita as pessoas no site), renovada enquanto a pessoa navega, até 2 horas depois de emitido
qlyne_clrliberação do desafio, amarrada ao IP (o /64 no IPv6) e ao navegador“Clearance” do painel (padrão 30 minutos)
qlyne_bsnota de robô, assinada pelo conector e amarrada ao IP e ao navegador (com a nota ligada)30 minutos

Em Access, tranque um caminho como /admin* ou um staging inteiro sem mexer no app. Dois jeitos de entrar:

  • Senha: uma senha compartilhada, guardada só como hash. As tentativas têm limite por IP.
  • Link por e-mail: o visitante digita o endereço; se estiver na sua lista (um endereço, ou @empresa.com.br para todo mundo de lá), chega por e-mail um link que vale uma vez, por 15 minutos. Todo mundo vê a mesma mensagem de “confira seu e-mail”, então a página não revela quem tem acesso.

Quem entra fica dentro pelo tempo que você escolher, por um cookie assinado pelo conector, e o app recebe Qlyne-Gate-User com o e-mail. O portão vale também no modo observar, porque é uma tranca que você pôs, não um palpite. Endereços de confiança passam direto.

Em Logins and forms, “Login protection”, liste as rotas para onde o formulário de login (ou o app) envia e os nomes dos campos de usuário e senha (user.email entra em JSON). Formulário, multipart e JSON são lidos.

  • Senha vazada: o conector calcula o SHA-1 da senha e manda ao Have I Been Pwned só os 5 primeiros caracteres, que valem para centenas de senhas; a senha nunca sai do conector. Se ela aparece em vazamentos, o login chega ao seu app com Qlyne-Leaked-Password: 1, e o app decide, por exemplo pedindo uma senha nova. Se a consulta demora ou cai, o login segue sem o header.
  • Tentativas por conta: contadas pelo usuário, de qualquer IP, para uma conta não ser adivinhada de muitos endereços. Passou do limite, o visitante recebe 429.

Em Firewall, “Managed rules”, o Qlyne procura assinaturas de ataques comuns no caminho, na query string e em corpos de formulário ou JSON de até 16 KB, depois de decodificar (duas vezes, para quem codifica duas vezes para escapar) e sem os comentários de SQL: SQL injection (UNION SELECT, condições 1=1, comandos empilhados, atrasos de tempo, leitura do esquema), cross-site scripting (tag script, handlers de evento, links javascript:, iframes), subida de pasta (../, arquivos do sistema, byte nulo) e injeção de comando ($(…) com comandos do sistema, curl ou bash encadeados, Log4Shell). Corpo em outros formatos (envio de arquivo) e corpo sem tamanho declarado não são lidos.

Todo site começa em “Observe only”: o que casa aparece em Events como “Would block by managed rules”, com a assinatura, e o seu app recebe no Qlyne-Rule. Quando lá só aparecer ataque, mude para “Block” (Pro; no grátis as regras continuam observando). Página que manda HTML ou código de propósito, como um editor de texto rico, vai em “Routes the managed rules skip”. As regras gerenciadas rodam depois das suas, então uma regra de liberar também pula elas.

As assinaturas pegam os ataques comuns e automatizados; não substituem validar a entrada e usar consulta parametrizada no app.

Em Bots, “Bot score”, o conector põe um script pequeno, /__qlyne/s.js, nas suas páginas HTML, servido do seu próprio domínio. Alguns segundos depois de a página abrir, depois de um clique ou tecla, e quando o visitante envia um formulário ou sai, ele manda um resumo para /__qlyne/s: se o navegador diz que é automatizado, quantos idiomas, plugins, núcleos de processador e pontos de toque ele informa, o tamanho da tela, se tem fuso horário, e quantas vezes o ponteiro mexeu, o visitante clicou, digitou, rolou ou tocou, e quantos desses eventos um script disparou. Ele manda só contagens: nunca teclas, texto ou onde o ponteiro estava.

Desse resumo, e de os cabeçalhos do pedido baterem ou não com o navegador do user agent, o conector dá ao visitante uma nota de 0 a 100: baixa abaixo de 30, média de 30 a 59, alta de 60 para cima. A nota fica 30 minutos no cookie qlyne_bs, assinado pelo conector e amarrado ao IP e ao navegador. O seu app recebe a nota em Qlyne-Bot-Score, com os motivos em Qlyne-Bot-Reasons (como automation,no_interaction), e a tela Events lista as notas média e alta com os motivos. A primeira página vista ainda não tem nota: as ações valem a partir do pedido seguinte.

No Pro, escolha o que a nota média e a alta recebem: nada, o pedido atrasado em 3 segundos, o desafio, o desafio com clique, 403, ou a página-isca. A isca é uma página do seu site que o robô recebe no lugar da que pediu, no mesmo endereço, e ele segue raspando algo que não vale nada. No modo observar isso só é contado.

“Routes that need the score” recusam formulário ou chamada de API (tudo que não é GET) de um navegador que não rodou o script, com 403 e {"code": "SENSOR_REQUIRED"}. Use nas rotas que robô chama direto, como a API de checkout ou de cadastro. Quem resolveu o desafio passa.

“Click to confirm”, em “Challenge”, define como a página do desafio e o captcha de formulário se comportam: nunca (o navegador faz a conta sozinho), na dúvida (um clique em “Sou uma pessoa” só quando a nota não é baixa ou o endereço pediu muitos desafios) ou sempre. Clique disparado por script, ou no computador sem o ponteiro ter mexido antes, é recusado.

Em Firewall, “Shared reputation”, o Qlyne lista os endereços que sites de outras contas viram atacando nas últimas 24 horas: pegos pela armadilha para scanner ou pelas regras gerenciadas, com nota de robô alta, usando navegador automatizado ou recusados pelo captcha de formulário. Um endereço só entra na lista quando pelo menos duas contas o relatam, então nenhuma conta sozinha põe alguém nela. No IPv6 a lista guarda o /64.

Por padrão, o endereço da lista só chega ao seu app com Qlyne-Reputation: 1. No Pro, desafie as visitas às páginas dele (chamadas de API passam, como nas saídas Tor) ou bloqueie com 403; Googlebot e Bingbot nunca são bloqueados, e os IPs de confiança passam direto. A lista vem junto com as regras, em até um minuto.

“Report attacks on this site” (ligado por padrão) manda ao Qlyne só o endereço desses ataques, guardado por 24 horas, nunca o caminho, o user agent ou qual site foi. Desligar não impede você de usar a lista. Como esses dados são tratados está nos termos e na política de privacidade.

Em Firewall, “Your rules”, cada regra é uma lista de condições que precisam valer todas, e o que fazer: bloquear, desafiar, liberar (pulando todas as outras regras, sala de espera inclusive) ou só registrar. As condições olham o caminho, o host, o método, o país, a rede (ASN), o IP ou faixa, o user agent, a impressão digital do TLS (JA4, só com Bot Management) e se o visitante é um robô de busca verificado. Para “ou”, ponha vários valores na mesma condição (BR, PT) ou escreva duas regras. As regras rodam em ordem, depois dos endereços bloqueados e da armadilha para scanner; a primeira que bloqueia, desafia ou libera decide.

Exemplos: bloquear o login para redes de empresas de hospedagem (path starts with /login e ASN is any of AS14061, AS16509); desafiar o checkout fora dos seus países (path matches /checkout* e country is none of BR, PT); bloquear chamada à API sem user agent. País e rede vêm do Cloudflare.

Scanners pedem caminhos que nenhum visitante pede: /.env, /.git/config, /phpmyadmin, /wp-login.php num site sem WordPress. Em Bots, “Scanner trap”, escolha os grupos de caminhos e acrescente os seus (um endereço de admin antigo, um link que só robô segue). Quem pede um deles leva um 404 comum, sem sinal de que era armadilha, e fica bloqueado em todas as rotas por 15 minutos, 1 hora ou 24 horas. No IPv6, o bloqueio vale para o /64. A instância do Worker que pegou bloqueia na hora; as outras ficam sabendo em até um minuto. Googlebot, Bingbot e endereços de confiança nunca são bloqueados, e no modo observar a armadilha só conta. Deixe o grupo WordPress desligado se o seu site usa WordPress.

Cabeçalhos de segurança e Content Security Policy

Seção intitulada “Cabeçalhos de segurança e Content Security Policy”

Em Page security, ligue HSTS, X-Content-Type-Options, Referrer-Policy, proteção contra iframe e Permissions-Policy. Eles entram nas suas páginas só quando o app ainda não manda. A nota (de A+ a F) sai das páginas que passaram pelo conector, então conta os cabeçalhos do próprio app também.

“Content Security Policy” (Pro) recebe a sua política e um modo. Em “Report only”, o navegador manda o que a política bloquearia para a própria página com ?__qlyne=csp; o conector recebe e lista em Page security por 7 dias, e nada quebra. Quando a lista só mostrar o esperado, mude para “Enforce”. O conector troca qualquer report-uri da sua política pelo dele.

As requisições que chegam ao site levam headers que o seu código pode usar (desligue em Connector, “Signals to your app”):

HeaderValor
Qlyne-CountryPaís, em duas letras
Qlyne-ASNNúmero da rede do visitante
Qlyne-Verified-Botgooglebot ou bingbot, confirmado pelo IP
Qlyne-Challenge-Passed1 quando o visitante resolveu o desafio, em qualquer página
Qlyne-Trusted-IP1 para um endereço da lista de confiança
Qlyne-RuleNo modo observar, o que teria acontecido, como would_challenge=/checkout*, would_rate_limit=/api/*
Qlyne-Leaked-Password1 num login cuja senha aparece em vazamentos (proteção de login; vai mesmo com os outros sinais desligados)
Qlyne-Gate-UserO e-mail de quem entrou por um portão de acesso (vai mesmo com os outros sinais desligados)
Qlyne-Bot-ScoreCom a nota de robô ligada, a nota do visitante de 0 a 100
Qlyne-Bot-ReasonsO que subiu a nota, como automation,no_interaction
Qlyne-Reputation1 para endereço que outras contas do Qlyne viram atacando (reputação entre sites)
Qlyne-JA4A impressão digital do TLS, só nos planos do Cloudflare com Bot Management (Enterprise)

Headers com esses nomes mandados pelo visitante são sempre apagados. Confie neles só se a origem aceitar só as faixas de IP do Cloudflare; senão alguém chega direto à origem e manda os headers por conta própria.

Todas as regras de um site também são um arquivo YAML (ou JSON) para guardar no git, autenticado com a chave de API da conta, de Plan and API key (uma chave para todos os sites da conta; o site vai em X-Tenant-ID):

Janela do terminal
# baixar
curl -H "Authorization: Bearer $QLYNE_API_KEY" -H "X-Tenant-ID: sua-conta" \
"https://app.qlyne.com/api/protection?format=yaml" > qlyne.yaml
# conferir sem salvar e depois aplicar (sem o ?dry_run=1)
curl -X PUT -H "Authorization: Bearer $QLYNE_API_KEY" -H "X-Tenant-ID: sua-conta" \
-H "Content-Type: application/yaml" --data-binary @qlyne.yaml \
"https://app.qlyne.com/api/protection?dry_run=1"

O PUT troca as regras: seção que falta no arquivo volta ao padrão, e lista no arquivo substitui a lista inteira. Erro volta como 422 com o campo que falhou (challenge.routes, rules.0.conditions.1.values), e configuração desconhecida também é erro, para um erro de digitação não passar calado. Num CI, rode a chamada com ?dry_run=1 no pull request e a de verdade no merge.

GET /api/metrics, com a mesma chave, devolve as métricas Prometheus só da sua conta (decisões por ação, tamanho das filas e o resto), para o seu Grafana. A configuração de coleta está em Connector, “Rules as code”.

  • O desafio encarece o robô (proof-of-work), não impede um robô determinado. Ele fica mais difícil no modo sob ataque e para o endereço que pede muitos em 10 minutos, e a solução mandada por um cliente que só copia o user agent de um navegador (sem os cabeçalhos que esse navegador sempre manda em HTTPS) é recusada. A checagem de automação pega Selenium, Puppeteer e Playwright do jeito que vêm, mas um navegador de verdade que esconde essas marcas passa depois de resolver o proof-of-work. Ainda não há impressão digital do navegador nem análise de comportamento, e não há impressão TLS (no Cloudflare, só no Enterprise).
  • O passe não é amarrado ao navegador: um cookie de passe copiado vale em outro lugar até vencer (no máximo 2 horas depois de emitido). Com limite de pessoas no site, todos os que usam a cópia contam como uma pessoa só.
  • O limite de pessoas no site pode ser ocupado por quem tem muitos endereços IP. Desafio nas mesmas páginas faz cada vaga custar um proof-of-work.
  • O robô de IA é reconhecido pelo User-Agent que declara. O que finge ser navegador é tratado como navegador e cai nas outras regras.
  • A proteção de formulário barra o robô que preenche todos os campos ou envia na hora. Robô que abre a página, espera e deixa a isca vazia passa; desafio na mesma página aumenta o custo.
MensagemO que é
the config is not signed by the Qlyne serverO arquivo do conector ou o QLYNE_CORE_URL não batem com o servidor do Qlyne. Baixe o conector de novo; nada é barrado até corrigir.
the Qlyne server refused the config (401)O QLYNE_SECRET não é o segredo atual do conector. Nada é barrado até corrigir.
QLYNE_SECRET is not set, or this is not the connector file downloaded from the dashboardFalta o segredo, ou o arquivo não é o baixado do painel. Nada é barrado.
waiting room pass refusedAlguém voltou com um passe inválido ou vencido. Se acontece com todo mundo, confira o segredo.
could not reach the Qlyne serverO conector usa as últimas regras que tinha; sem nenhuma, deixa passar.
could not reach the Qlyne server for an admission, the Qlyne server answered ... to an admission requestO Qlyne não deu a vaga a tempo. O conector deixa entrar e pergunta de novo 10 segundos depois.