Cum protejezi o instanță WriteFreely de înregistrări automate cu Anubis

Aceasta e o versiune anterioară a paginii.


Cum protejezi o instanță WriteFreely de înregistrări automate cu Anubis

Ghidul de mai jos strânge la un loc tot ce e nevoie ca să oprești valul de conturi spam de pe o instanță WriteFreely publică. Nu e o soluție teoretică, sunt exact pașii de urmat, cu greșelile tipice și corecturile lor. Presupune un server Linux cu Docker instalat și Caddy ca reverse proxy, fie ca serviciu systemd, fie tot în container - ambele variante sunt acoperite mai jos.

1. Problema

O instanță WriteFreely cu înregistrare deschisă (open_registration = true) atrage devreme sau târziu conturi create automat, folosite pentru spam SEO - articole cu linkuri către pariuri online, produse dubioase sau ghiduri generice fără nicio legătură cu comunitatea. WriteFreely nu are captcha nativ și nici verificare de email obligatorie, deci bariera trebuie pusă din afara aplicației.

Varianta simplă - open_registration = false - rezolvă spam-ul, dar taie și posibilitatea ca oameni reali să-și facă cont fără să ceară o invitație manuală. Ghidul de mai jos e pentru cazul în care vrei să păstrezi înregistrarea deschisă.

2. Anubis, pe scurt

Anubis e un program mic care stă în fața aplicației și cere navigatorului să rezolve un test de tip proof-of-work înainte să lase cererea să treacă mai departe. Un navigator normal rezolvă testul automat, în fundal, în una-două secunde. Un script simplu, care doar trimite cereri HTTP fără să execute JavaScript, nu poate trece deloc.

Anubis nu oprește un bot sofisticat, cu navigator real în spate (de tip headless Chromium controlat automat). Oprește scripturile simple, care sunt marea majoritate a spam-ului de volum mare. Pentru boți mai sofisticați, ai nevoie de straturi suplimentare, discutate la pasul 7.

3. Identifici toate rutele de înregistrare, din sursă, nu din presupunere

Cea mai frecventă greșeală aici - presupui numele rutei de înregistrare dintr-o sursă neverificată, în loc să te uiți direct în codul aplicației. Rezultatul e o barieră care nu protejează nimic, fiindcă botul folosește altă rută decât cea presupusă.

Pentru WriteFreely, uitându-te direct în routes.go din codul sursă oficial, există exact două rute care creează un cont nou:

  • POST /auth/signup - formularul web, cel completat de un vizitator obișnuit
  • POST /api/auth/signup - rută programatică, folosită de aplicații externe; există doar dacă open_registration = true
Lecția generală, valabilă pentru orice aplicație, nu doar WriteFreely: înainte să pui o barieră, verifică exhaustiv în codul sursă (sau în documentația oficială de API) toate punctele de intrare care ar putea crea conținut sau conturi. Nu te opri la prima rută care pare evidentă și nu te baza pe un nume de rută găsit într-un tutorial vechi sau într-o discuție neactualizată.

4. Pornești Anubis ca serviciu Docker separat

Dacă reverse proxy-ul (Caddy, în acest exemplu) rulează ca serviciu systemd direct pe server, nu ca și container, cel mai simplu e ca Anubis să asculte tot pe 127.0.0.1, la fel ca aplicația pe care o protejează.

mkdir -p /opt/services/anubis
cd /opt/services/anubis

docker-compose.yml:

services:
  anubis:
    image: ghcr.io/techarohq/anubis:latest
    container_name: anubis-writefreely
    restart: unless-stopped
    network_mode: host
    environment:
      BIND: "127.0.0.1:8923"
      DIFFICULTY: "4"
      METRICS_BIND: "127.0.0.1:9099"
      TARGET: "http://127.0.0.1:8080"
      POLICY_FNAME: "/data/cfg/botPolicy.yaml"
      COOKIE_DOMAIN: "exemplu.ro"
      COOKIE_EXPIRATION_TIME: "1h"
      SERVE_ROBOTS_TXT: "true"
    volumes:
      - "./botPolicy.yaml:/data/cfg/botPolicy.yaml:ro"

Ajustează TARGET la portul real pe care ascultă aplicația ta, și COOKIE_DOMAIN la domeniul tău.

Implicit, Anubis ține cookie-ul de verificare valabil 7 zile. E prea mult pentru o rută de înregistrare, la care un utilizator nu are nevoie de sesiune lungă. Reducerea la o oră (COOKIE_EXPIRATION_TIME: „1h“) limitează cât timp poate fi refolosit un cookie obținut o singură dată.

5. Politica de reguli, botPolicy.yaml

Aici pui doar rutele reale identificate la pasul 3, cu dificultate crescută specific pe ele, și lași restul situl fără nicio barieră:

bots:
  - name: protect-registration
    expression:
      any:
        - 'path.startsWith("/auth/signup")'
        - 'path.startsWith("/api/auth/signup")'
    action: CHALLENGE
    challenge:
      algorithm: fast
      difficulty: 5

  - name: allow-everything-else
    expression: "true"
    action: ALLOW

status_codes:
  CHALLENGE: 200
  DENY: 403
Dificultate Timp aproximativ de rezolvare Observație
4 (implicit) sub 1 secundă prea ușor, orice script trece dacă rulează JavaScript
5 câteva secunde echilibru bun pentru o rută vizitată rar
6-7 15-30 secunde riscă să descurajeze și utilizatori reali
Testează valoarea aleasă cu propriul navigator înainte s-o lași definitivă - dificultatea prea mare pe o rută pe care un om o accesează o singură dată (înregistrare) poate face mai mult rău decât bine.
cd /opt/services/anubis
docker compose up -d

6. Caddy trimite traficul prin Anubis, nu direct la aplicație

Găsești blocul din Caddyfile pentru domeniul tău și schimbi ținta de la aplicație direct, la portul pe care ascultă Anubis:

signup.exemplu.ro {
	reverse_proxy localhost:8923
}
systemctl reload caddy

7. Confirmi cu teste directe, nu doar vizual

curl -I -X POST https://signup.exemplu.ro/auth/signup
curl -I -X POST https://signup.exemplu.ro/api/auth/signup

Ambele ar trebui să răspundă cu status 200, tip text/html, și cookie-uri de la Anubis (techaro.lol-anubis-*). Dacă vezi în schimb un răspuns direct de la aplicație (de exemplu un 302 cu redirect), înseamnă că ruta respectivă tot ocolește Anubis.

8. Logging pe Caddy, pentru diagnosticare ulterioară

Merită activat din start, nu doar când apare o problemă - fără el, nu ai cum să vezi exact ce cale a folosit un bot care a reușit să treacă.

signup.exemplu.ro {
	log {
		output file /var/log/caddy/access.log
		format json
	}
	reverse_proxy localhost:8923
}
mkdir -p /var/log/caddy
chown caddy:caddy /var/log/caddy
systemctl reload caddy

Dacă apare un cont nou nedorit, primul loc de verificat e acest jurnal, filtrat pe intervalul orar respectiv:

grep "AAAA-LL-ZZ HH:" /var/log/caddy/access.log | grep -E "signup|auth"

9. Un al doilea strat, independent de Anubis: rate limiting la firewall

Anubis verifică dacă un client poate rezolva un puzzle. Nu limitează câte cereri trimite același client într-un interval de timp, odată ce a rezolvat puzzle-ul o dată. Pentru asta ai nevoie de un strat separat.

Dacă serverul nu are deja ufw sau nftables active, cea mai simplă variantă e direct în iptables, cu modulul recent:

iptables -I INPUT -p tcp --dport 443 -m conntrack --ctstate NEW -m recent --name https_limit --set
iptables -I INPUT -p tcp --dport 443 -m conntrack --ctstate NEW -m recent --name https_limit --update --seconds 60 --hitcount 20 -j DROP

Asta limitează la maxim 20 de conexiuni noi pe minut, per adresă IP, către portul 443. E o limită generoasă pentru un vizitator normal, dar taie clar un bot care insistă constant.

Testezi imediat că situl tot răspunde normal:

curl -I https://exemplu.ro/

Faci regulile permanente, altfel dispar la restart:

apt install -y iptables-persistent
netfilter-persistent save

10. Curățarea conturilor deja existente

Dacă instanța a acumulat deja conturi spam înainte să pui bariera, curățarea se face direct în baza de date, cu backup obligatoriu înainte de orice ștergere.

Nu șterge niciodată fără backup, și fără să verifici manual un eșantion de conținut - unele conturi cu tipare aparent suspecte pot fi totuși legitime.

Pentru WriteFreely cu SQLite:

cp /cale/catre/writefreely.db /cale/catre/backup-$(date +%Y%m%d-%H%M).db
 
cd /cale/catre/writefreely
docker compose stop

Ștergi tot ce nu e contul tău de admin, în ordinea corectă (postări, apoi bloguri, apoi useri, ca să nu rămână rânduri orfane):

BEGIN TRANSACTION;
 
DELETE FROM posts
WHERE owner_id IN (SELECT id FROM users WHERE username != 'contul-tau-de-admin');
 
DELETE FROM collections
WHERE owner_id IN (SELECT id FROM users WHERE username != 'contul-tau-de-admin');
 
DELETE FROM users
WHERE username != 'contul-tau-de-admin';
 
COMMIT;
docker compose up -d

11. Rezumat

Trei straturi, fiecare acoperind ce ratează celălalt:

  1. Anubis - oprește scripturile simple, care nu pot rezolva un test de calcul
  2. Rate limiting la firewall - oprește volumul mare, indiferent de rută sau metodă
  3. Verificare periodică - jurnalul de acces și panoul de administrare, ca să prinzi din timp orice cale nouă găsită de un bot
Niciun strat singur nu e suficient. Un bot cu navigator real trece prin Anubis dacă dificultatea e prea mică, dar e oprit de rate limiting dacă insistă. Un bot care găsește o rută neacoperită de Anubis e totuși limitat de firewall dacă trimite volum mare. Verificarea periodică rămâne singura plasă care prinde ce n-ai anticipat deloc.