Această pagină poate fi doar citită. Poți vedea sursa, dar nu poți modifica pagina. Consultă administratorul dacă ești de părere că ceva este în neregulă. ====== 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. <note important> 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ă. </note> ===== 2. Anubis, pe scurt ===== [[https://anubis.techaro.lol/|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. <note tip> 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. </note> ===== 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'' <note important> 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ă. </note> ===== 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ă. <code bash> mkdir -p /opt/services/anubis cd /opt/services/anubis </code> ''docker-compose.yml'': <code yaml> 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" </code> Ajustează ''TARGET'' la portul real pe care ascultă aplicația ta, și ''COOKIE_DOMAIN'' la domeniul tău. <note tip> 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ă. </note> ===== 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ă: <code yaml> 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 </code> ^ 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 | <note important> 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. </note> <code bash> cd /opt/services/anubis docker compose up -d </code> ===== 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: <code> signup.exemplu.ro { reverse_proxy localhost:8923 } </code> <code bash> systemctl reload caddy </code> ===== 7. Confirmi cu teste directe, nu doar vizual ===== <code bash> curl -I -X POST https://signup.exemplu.ro/auth/signup curl -I -X POST https://signup.exemplu.ro/api/auth/signup </code> 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ă. <code> signup.exemplu.ro { log { output file /var/log/caddy/access.log format json } reverse_proxy localhost:8923 } </code> <code bash> mkdir -p /var/log/caddy chown caddy:caddy /var/log/caddy systemctl reload caddy </code> Dacă apare un cont nou nedorit, primul loc de verificat e acest jurnal, filtrat pe intervalul orar respectiv: <code bash> grep "AAAA-LL-ZZ HH:" /var/log/caddy/access.log | grep -E "signup|auth" </code> ===== 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'': <code bash> 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 </code> 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: <code bash> curl -I https://exemplu.ro/ </code> Faci regulile permanente, altfel dispar la restart: <code bash> apt install -y iptables-persistent netfilter-persistent save </code> ===== 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. <note important> 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. </note> Pentru WriteFreely cu SQLite: <code bash> cp /cale/catre/writefreely.db /cale/catre/backup-$(date +%Y%m%d-%H%M).db cd /cale/catre/writefreely docker compose stop </code> Ș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): <code sql> 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; </code> <code bash> docker compose up -d </code> ===== 11. Rezumat ===== Trei straturi, fiecare acoperind ce ratează celălalt: - **Anubis** - oprește scripturile simple, care nu pot rezolva un test de calcul - **Rate limiting la firewall** - oprește volumul mare, indiferent de rută sau metodă - **Verificare periodică** - jurnalul de acces și panoul de administrare, ca să prinzi din timp orice cale nouă găsită de un bot <note tip> 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. </note> {{tag>writefreely anubis securitate spam}}