Naar hoofdinhoud
TIEMAN.IT
Terug naar blog

Hoe ik een Magento-shop overeind hield onder 600.000 scraper-IP's

Kevin Tieman7 min leestijd
  • security
  • magento
  • ddos
  • ops

Vorige week kreeg ik een berichtje van een klant: "de webshop voelt traag, klanten kunnen niet betalen". Ik logde in en zag dat de server bijna lag. Vijf cores, load van 22. Cloudflare gaf intermitterend 502 Bad Gateway terug aan iedereen die de homepage opende.

Het was geen storing van ons. Een externe scraper had het zoekendpoint van de Magento-shop ontdekt en stuurde er met 160.000 verschillende IP-adressen tegelijk verzoeken op af. Op de piekdag waren dat ruim 530.000 zoekopdrachten in 24 uur. Een normale dag is rond de 8.000 echte bezoekers. De verhouding tussen mens en bot lag in één klap op 4 procent mens, 96 procent bot.

Hieronder zet ik op een rij wat er precies gebeurde, wat ik in de logs zag, hoe we het stopten zonder de echte bezoekers buiten te sluiten, en wat ik zou doen als jouw shop morgen hetzelfde overkomt.

Wat ik in de access-logs zag

Het patroon was te kenmerkend om aan een ongelukkige Googlebot-crawl te wijten:

  • 94 procent van álle verzoeken die de origin bereikten, ging naar één pad: /catalogsearch/. Dat endpoint is de zoekfunctie. Niet cachebaar. Elke hit kost een PHP-worker en een dure Elasticsearch-query.
  • De zoektermen waren onze eigen merk- en productnamen, machinaal gefuzzt. Patronen als "merknaam productcode 500ml", "merknaam productcod 500ml", "merknaa productcode 500ml" en "merknaam productcode 500 ml". Duizenden permutaties, systematisch herhaald over meerdere dagen.
  • Achter elke serie zoekopdrachten zat een ander IP-adres. 70 procent van de IP's deed maar twee verzoeken voordat het roteerde. 15 procent één. Geen enkel IP overtrof de tien verzoeken per minuut.
  • Een derde van de IP's was IPv6, verdeeld over tientallen consumenten- en mobiele ISP's wereldwijd. Dat is geen datacenter. Dat is een residential-proxy-netwerk: gehuurde IP-toegang via verbruikersapparaten die meestal niet weten dat ze meedraaien.

De campagne startte exact om drie uur 's nachts op een dinsdag, met een sprong van twaalf naar bijna 3.000 verzoeken per uur. Tussen 23:00 en 03:00 is er nog niemand wakker om in te grijpen, en de aanvaller wist dat.

Verloop van het aantal unieke scraper-IP's per dag op het zoekendpoint, met de stapfunctie op 27 mei en het effect van de Cloudflare-challenge op 4 juni.

Waarom dit geen gewone DDoS was

De motivatie was niet om de site om te krijgen. De buit was de productdata. Van elke verzoekserie haalde de scraper de zoekresultaatpagina op, inclusief de prijzen in de productlijst, en bladerde door tot pagina 64. Slechts 4,4 procent van de verzoeken raakte een productdetailpagina rechtstreeks. Alles werd uit de zoekresultaten gedolven.

Voor de aanvaller zijn 600.000 IP's geen probleem: residential proxies reken je per GB verkeer, niet per IP. Voor ons betekent het dat klassieke verdediging niet werkt. Per-IP-rate-limit doet niets als één IP maar twee verzoeken per minuut doet. Captcha's vóór de zoekfunctie kosten ons echte bezoekers. Een geoblock blokkeert ook trouwe klanten in Frankrijk en België waar de aanvaller mensen omkocht om hun verbinding te delen.

De DDoS was een neveneffect. De server lag plat omdat de zoekfunctie nu eenmaal de zwaarste route in elke Magento-stack is, en duizend gelijktijdige verzoeken per minuut breken een vijf-core bak.

Wat we deden om het te stoppen

Eerste maatregel: Cloudflare op "Under Attack Mode". Dat is een JavaScript-challenge vóór elke bezoeker, en het werkt direct. De load zakte van 22 naar 3. Maar Under Attack challenget ook serverwebhooks. Tracking-callbacks van de logistieke partner, betaalbevestigingen en marketplace-syncs: allemaal geblokkeerd aan de rand omdat een server geen JavaScript uitvoert. We zagen het binnen een uur terug in de orderafhandeling: tracking-nummers kwamen niet meer binnen.

Onmisbare les: een nucleaire knop laat je niet langer aanstaan dan strikt nodig.

Wat we daarna deden, in deze volgorde:

  1. Origin-rate-limit op alleen het zoekendpoint, niet per IP maar per host. De aanvaller roteert IP's, dus je bouwt een budget per shop in plaats van per bezoeker. In nginx: limit_req_zone $host met een burst die echte zoekverzoeken doorlaat.
  2. Cloudflare Managed Challenge op de zoek-URL, niet sitebreed. Een uitdrukking als (http.request.uri.path contains "/catalogsearch/" and not cf.client.bot) laat geverifieerde zoekmachines door, daagt de rest. Webhooks raken het niet.
  3. /catalogsearch/ op noindex en robots-disallow. Dat verkleint het aanvalsoppervlak en vermindert de crawlbudget-druk van legitieme zoekmachines op een pad dat sowieso geen zoekverkeer hoort op te leveren.
  4. Bewijspakket veilig stellen. De nginx-logs roteren na een paar dagen weg. Voor de aangifte heb ik elke regel die met de scraper te maken had geëxporteerd naar een aparte CSV, met timestamps, IP's, zoektermen en statuscodes. 1,7 miljoen regels in één bestand, dat is je bewijs.

Na de Managed Challenge zakten de scraper-hits van zes- tot tienduizend per uur naar tachtig à honderdvijftig per uur. Dat resterende verkeer was de optelsom van echte bezoekers die hun zoekopdracht legitiem deden, en Googlebot.

Wat ik zou doen als het bij jouw shop gebeurt

Niet wachten tot het je voorbij waait. Een residential-proxy-botnet vertrekt pas als het commercieel niet meer rendabel is om jouw catalogus te scrapen. Concreet:

Direct, dezelfde dag:

  • Zet Cloudflare op Under Attack Mode (of de Bot Fight Mode-equivalent van je proxy) om uit de acute druk te komen. Accepteer dat webhooks tijdelijk niet meer binnenkomen.
  • Verzamel access-logs van de afgelopen 14 dagen op één plek. Logs roteren, en zonder logs heb je geen aangifte.
  • Bel je hostingpartij. Niet om ze om hulp te vragen (ze kunnen meestal niets), maar om ze te informeren dat een aanval loopt en dat hun monitoring kan oversturen.

Binnen de eerste dag:

  • Identificeer welk endpoint de aanval krijgt. Eén awk op je access-log laat zien welk pad de top-zoveel procent van het verkeer trekt. In bijna alle e-commerce-gevallen is dat /catalogsearch/, een product-filter-URL of een specifieke productpagina.
  • Bouw een per-host rate-limit op dat endpoint, niet per IP. Per IP werkt niet bij residential proxies.
  • Vervang de sitebrede challenge zo snel mogelijk door een gerichte challenge op dat endpoint. Sitebrede challenges breken integraties.

Binnen de campagne (die meestal dagen duurt):

  • Doe aangifte. Justitie vordert vaak proxy-providers waar individuele eigenaren dat niet kunnen.
  • Markeer het aanvalsendpoint noindex als het toch geen SEO-waarde hoeft te leveren.
  • Beperk de read-timeout op je php-fpm zo dat één traag verzoek geen worker 15 minuten gijzelt.

Als je shop draait op een platform dat dit soort knoppen niet biedt (Wix, Squarespace, sommige Shopify-templates), is de aanvalsoppervlakte kleiner maar je verdedigingsruimte ook. In dat geval is "wachten tot de aanvaller stopt" praktisch de enige optie, en je hostpartij zal je waarschijnlijk een tijdelijke ban op het gewraakte pad opleggen.

Wat ik niet heb gedaan

Origin-firewalls die alleen Cloudflare-IP's toelaten zijn een verleidelijke knop. Doen we niet, want het breekt je health-monitor, je eigen ssh als je het verkeerd configureert, en het lost het probleem niet op (de aanvaller komt al via Cloudflare binnen). Datzelfde geldt voor cargo-cult-defenses zoals block_ua op willekeurige user-agents: deze scraper draait dezelfde Chrome-strings als jouw echte bezoekers.

Wat ik wél heb gedaan is consequent live blijven meekijken in de logs terwijl de mitigatie deployde. Een aanvaller die ziet dat zijn campagne ineens 99 procent fail-rate krijgt, kan binnen het uur van tactiek wisselen. Drie keer in vier dagen hebben we de rate-limit aangepast omdat de aanvaller meer IP's in de pool gooide. Een fix-en-vergeten-aanpak werkt niet bij iemand die actief tegen je werkt.

Rechtstreeks praten?

Loopt er op dit moment iets op je eigen webshop dat je niet vertrouwt, of wil je een tweede paar ogen op je logs? Stuur me een bericht. Ik kijk binnen één werkdag mee, geen verkoopgesprek.

Klaar om te beginnen?

Neem contact op voor een vrijblijvend gesprek over de mogelijkheden.