În articolul despre atacul asupra formularului nostru de contact am povestit cum site-ul a devenit, pentru o zi, infrastructura de phishing a altcuiva: 109 emailuri trimise către victime de pe domeniul nostru, de pe 41 de IP-uri închiriate în cloud. Partea cea mai incomodă nu a fost atacul în sine, ci bilanțul apărării: honeypot-ul a prins 2 mesaje din 167, timpul minim de completare a prins 50 până când bot-ul a învățat să aștepte, iar limita de trei mesaje pe oră per IP n-a contat deloc în fața a 41 de adrese care se rotesc.
Concluzia greșită ar fi că măsurile astea sunt inutile. Concluzia corectă e alta: fiecare măsură oprește un singur tip de atacator, iar un formular public are nevoie de mai multe straturi, fiecare pentru alt tip. Seria asta e partea practică de după postmortem: la ce folosește un atacator un formular, ce straturi de apărare există, cum arată fiecare în ASP.NET Core și un checklist pe care îl poți aplica pe orice formular, de la contact și newsletter la resetare de parolă.
Seria Securitate aplicată pentru formulare web:
- Partea 1: ce vrea atacatorul și designul formularului (articolul de față)
- Partea 2: validare, tokenuri semnate și challenge
- Partea 3: rate limiting, scorare, email și monitorizare
- Partea 4: concluzii și checklist
La ce îi folosește unui atacator formularul tău
Primul pas nu e să alegi un CAPTCHA, ci să înțelegi ce poate obține cineva din formular. Un formular public e un endpoint care acceptă date de la oricine și declanșează efecte: trimite emailuri, scrie în baza de date, afișează conținut într-un panou de administrare, uneori costă bani. Fiecare efect e o țintă.
| Abuz | Ce câștigă atacatorul | Ce te costă |
|---|---|---|
| Releu de email (confirmări trimise la adresa introdusă) | Phishing trimis de pe domeniul tău, cu SPF și DKIM valide | Reputația domeniului, contul de email restricționat |
| Spam și linkuri SEO către tine | Backlinkuri, oferte, tentative de fraudă | Timp, zgomot, mesaje reale îngropate |
| Injecție în emailul primit de echipă | Linkuri sau HTML controlat de el în inbox-ul vostru | Phishing intern, credibil, pentru că vine din propriul formular |
| Stored XSS în panoul de administrare | Cod executat în browserul administratorului | Sesiuni furate, acces la date |
| Enumerare (login, reset, newsletter) | Lista adreselor care au cont la tine | Ținte pentru credential stuffing și phishing |
| Endpoint-uri care costă (SMS, AI, upload) | Venit din fraudă sau simplu sabotaj | Factura ta |
Observă că doar primul rând a fost incidentul nostru. Celelalte sunt la fel de reale, doar că fac mai puțin zgomot.
Formularele care costă bani
Un formular de contact abuzat îți strică reputația. Alte formulare îți golesc direct contul, pentru că fiecare trimitere declanșează o operație plătită:
- Coduri prin SMS (verificare de telefon, autentificare în doi pași). Frauda numită SMS pumping sau trafic artificial umflat folosește formularul tău ca să trimită mii de SMS-uri către numere cu tarif special, din care atacatorul încasează o parte. Contramăsurile: limite per număr de destinație și per prefix de țară, o listă de țări permise și un plafon zilnic de cost la furnizorul de SMS.
- Endpoint-uri AI (chatboți, generare de conținut). Fiecare cerere înseamnă tokeni plătiți. Ai nevoie de cote per utilizator sau per licență, de un cache pentru întrebările repetate și de o alertă de buget la furnizorul de model.
- Upload de fișiere. Stocare, scanare antivirus, procesare. Limitează dimensiunea și tipul fișierului înainte de a-l accepta, nu după.
Regula generală: pentru orice endpoint public care declanșează o operație plătită, pune un plafon de cost, nu doar o limită de cereri.
Straturi, nu ziduri
Atacatorii nu sunt toți la fel. Un bot naiv completează toate câmpurile instant. Un bot răbdător așteaptă câteva secunde. Un bot serios rotește IP-uri din cloud. Iar la capătul scării stă un om plătit, sau o fermă care rezolvă CAPTCHA-uri pe bandă, pentru fracțiuni de cent. Tabelul de mai jos arată de ce niciun strat nu ajunge singur:
| Strat | Bot naiv | Bot răbdător | IP-uri rotative | Om plătit |
|---|---|---|---|---|
| Design (nimic trimis la adresa vizitatorului) | Elimină câștigul | Elimină câștigul | Elimină câștigul | Elimină câștigul |
| Honeypot și timp minim | Oprește | Parțial | Parțial | Nu |
| Challenge (Turnstile, reCAPTCHA, hCaptcha) | Oprește | Oprește | Oprește | Nu |
| Rate limiting per IP | Oprește | Oprește | Nu | Nu |
| Rate limiting global pe endpoint | Plafonează | Plafonează | Plafonează | Plafonează |
| Scorarea conținutului | Depinde de reguli | Depinde de reguli | Depinde de reguli | Parțial |
În serie trecem prin opt straturi: designul formularului, validarea și encoding-ul, honeypot-ul și timpul minim semnate pe server, challenge-ul, rate limiting-ul, scorarea conținutului, izolarea trimiterii de emailuri și monitorizarea. Primul rând din tabel e singurul care funcționează împotriva tuturor, pentru că nu încearcă să oprească atacatorul. Îi ia motivul de a veni. De acolo începem.
Stratul 1: designul formularului
Regula cea mai importantă din toată seria: nu trimite niciodată conținut controlat de vizitator la o adresă controlată de vizitator. Un formular care trimite o confirmare la adresa introdusă, cu numele sau mesajul introdus, este un serviciu gratuit de trimis emailuri, semnat cu domeniul tău. Exact asta a exploatat atacul nostru, iar eliminarea confirmării a fost decizia cu cel mai mare efect.
- Confirmarea se afișează pe pagină, nu prin email. Vizitatorul vede imediat că mesajul a ajuns, iar tu nu mai trimiți nimic în afară.
- Dacă trebuie neapărat un email (newsletter, cont nou), folosește double opt-in, cu un text complet fix: fără numele introdus, fără mesaj, doar linkul de confirmare. Un atacator poate trimite cel mult un email plictisitor, pe care nu-l controlează.
- Răspunsuri identice pentru existent și inexistent. La resetare de parolă și la newsletter, mesajul trebuie să fie același, de exemplu „Dacă adresa există, vei primi un email”. Altfel formularul devine un instrument de enumerare.
- Ce nu există nu poate fi abuzat. Câmpurile opționale, atașamentele și selectorul de destinatar sunt toate suprafață de atac. Păstrează doar ce folosești.
Un exercițiu util înainte de a scrie vreo linie de cod: ia fiecare formular de pe site și răspunde la două întrebări. Ce efecte declanșează? Și ar putea fi vreunul dintre ele folosit în numele altcuiva? Dacă răspunsul la a doua întrebare e da, schimbă designul înainte să adaugi protecții.
Ce urmează
Designul elimină motivul celui mai periculos abuz, dar formularul tot primește date ostile. În partea a doua intrăm în cod: limite de lungime și encoding, inclusiv în emailul primit de echipă, un token de timp semnat pe server cu Data Protection din ASP.NET Core și verificarea pe server a unui challenge Cloudflare Turnstile.