Logo run_as_root - Magento B2B Agentur Würzburg

Die 7 Security-Findings, die wir in fast jedem Magento-2-Code-Audit sehen

Die sieben häufigsten Magento-2-Sicherheitslücken aus unseren Code-Audits. Von Adminer im Webroot bis still abgeschaltetem reCAPTCHA, mit Detection-Snippets.


On this page

    Jedes Magento-2-Code-Audit, das wir fahren, fördert grob dieselben sieben Findings zutage. Nicht weil die Codebasen identisch wären, sondern weil es die Fehlermuster sind. Dieselbe Handvoll Anti-Patterns wird in Marketplace-Modulen ausgeliefert, in Custom-Code kopiert, überlebt ein Dutzend Plattform-Upgrades und landet vor uns, sobald jemand nach einem Vorfall oder einer Procurement-Prüfung endlich einen Audit beauftragt.

    Das ist der technische Begleitartikel zu Ist mein Magento-2-Shop wirklich sicher? Ein 10-Minuten-Selbstcheck für Händler. Den reichst du an deinen Händler weiter. Dieser hier ist für dich.

    TL;DR

    Jedes Magento-2-Code-Audit, das wir im vergangenen Jahr gefahren haben, hat mindestens vier dieser sieben Findings gezeigt. Meistens fünf.

    1. Ein Datenbank-Tool im Webroot. Adminer, phpMyAdmin oder eine umbenannte Datei wie _db.php1. pub/ dagegen greppen, auf Sicht entfernen.
    2. Hartkodierte Basic-Auth-Credentials in Admin-Blocks. Vendor-„Versionscheck"-Module, die von ihrem eigenen Server laden, mit Klartext-Credentials, die in PHP ausgeliefert werden.
    3. Raw-SQL-Konkatenation in Marketplace-Modulen. Klassisches SQLi, meistens in Admin-Controllern, die der Vendor für „admin-only, also sicher" hielt.
    4. @escapeNotVerified-Altlast im großen Stil. Templates tragen noch den Magento-2.1-Migrationsmarker, gelegentlich im vierstelligen Bereich, jeder Vorkommen ist ein potenzielles XSS.
    5. Frontend-POST-Controller ohne CsrfAwareActionInterface. Öffentliche Endpoints, die alles von jedem Origin annehmen.
    6. reCAPTCHA still per <preference> deaktiviert. Ein einziger DI-Override schaltet Captcha auf REST aus, ohne ein Signal im Admin-UI.
    7. Drittanbieter-Profiler, die rohe POST-Bodies in Produktion auf Platte loggen. Inklusive Passwörter und Kartendaten, je nach Flow. DSGVO allein reicht als Grund.

    Arbeite sie in dieser Reihenfolge ab. Die früheren Findings sind sowohl häufiger als auch schwerwiegender als die späteren.

    1. Adminer, phpMyAdmin oder ein umbenanntes Datenbank-Tool in Ihrem Webroot

    Ein Einzeldatei-Datenbank-Tool in pub/ oder im Document-Root abgelegt, geschützt von dem Passwort, das der Entwickler beim Ablegen gerade griffbereit hatte. Die URL ist meist erratbar: _db.php1, adminer.php, pma/, phpmyadmin/ oder der persönliche Favorit, dbadmin.php. Eine Cloudflare-Regel kann den Request challengen, aber die Regel ist eine Fehlkonfiguration von bypassbar entfernt. Die Datei selbst ist eine voll interaktive SQL-Konsole auf den Connection-String, mit dem sie abgelegt wurde.

    Das ist fast immer das schlimmste Finding in einem Audit. Die Angriffsfläche ist nicht „eine Lücke im Tool". Sie ist: „Das Tool ist eine Admin-Konsole auf deine Datenbank, dem gesamten Internet ausgesetzt, hinter einem Passwort."

    1# Detection: den öffentlichen Document-Root nach Einzeldatei-PHP-Dumps greppen,
    2# die nicht zu Magento oder einem bekannten Entry-Point gehören. pub/ an Ihren Webroot anpassen.
    3find pub/ -maxdepth 2 -name '*.php*' \
    4 ! -name 'index.php' ! -name 'get.php' ! -name 'health_check.php' \
    5 ! -name 'static.php' ! -name 'cron.php' ! -name 'errors/*' -print
    6 
    7# Außerdem die üblichen Namen vom Edge aus prüfen:
    8for path in adminer.php _db.php1 pma/ phpmyadmin/ dbadmin.php; do
    9 curl -s -o /dev/null -w "%{http_code} $path\n" "https://yourstore.example.com/$path"
    10done

    Incident-Response-Framing: das ist das eine Finding, auf das du am selben Tag handelst. Jedes andere Finding in diesem Artikel geht in ein Backlog-Ticket. Dieses eine wird aus dem Webroot entfernt und aus der Backup-Historie rotiert, bevor du irgendetwas anderes tust.

    2. Hartkodierte Basic-Auth-Credentials in Vendor-Admin-Blocks

    Eine Handvoll Marketplace-Vendors liefern Module aus, die bei jedem Admin-Seitenaufruf „nach Hause telefonieren", meistens für Lizenzchecks oder Update-Benachrichtigungen. Die Authentifizierung ist HTTP Basic. Benutzername und Passwort sind String-Literale im PHP-Code, in app/code/Vendor/Module/Block/Adminhtml/, und werden in jedem Release des Moduls ausgeliefert.

    Mageants_ExtensionVersionInformation ist das kanonische Beispiel, das uns regelmäßig begegnet. Die Credentials sind admin / admin@324, genutzt, um Versionsmetadaten von mageants.com zu ziehen. Sie stehen im veröffentlichten Source des Moduls auf Magento Marketplace und GitHub. Jeder Shop, der das Modul fährt, sendet diese Credentials bei jedem Admin-Request an eine Domain außerhalb deiner Kontrolle, in einem Header, den jeder Marketplace-Plugin-Autor inspizieren kann. Dieselben Strings liegen auch in deiner Produktionsbasis, deiner Git-History und jedem Support-Dump, den je jemand gezogen hat.

    Der Fix hängt vom Modul ab. Wenn es wirklich gebraucht wird, die Credentials durch env.php führen und aus Git heraushalten; ist es ein „Versionscheck"-Side-Feature, das Modul abschalten. Die allgemeinere Regel: jedes Modul, das Credentials irgendwo im Source hartkodiert, ist ein Modul, dem du nicht genug vertraust, um es laufen zu lassen.

    1# Detection: nach Basic-Style-Auth-Headern mit Inline-Credentials suchen.
    2grep -rnE "Authorization.*Basic|base64_encode\(['\"][^'\"]+:" app/code/ vendor/ | grep -v Test

    3. Raw-SQL-Konkatenation in Marketplace-Modulen

    Das ist der Klassiker. Ein Vendor-Modul baut SQL per PHP-String-Konkatenation, nutzerseitig gelieferter Input fließt ohne Parameter-Binding ein, und niemand nutzt das ORM. Fast immer in einem Adminhtml-Controller, den der Vendor für „sicher, weil admin-only" hielt und dabei vergaß, dass Admins kompromittiert werden und dass Admin-CSRF ein reales Risiko ist.

    Itoris_DynamicProductOptions ist das Muster, das wir immer wieder zitieren, weil das Modul auf dem Marketplace ist und die Konkatenationsform im veröffentlichten Source steht. Die Form ist ein ->query()-Aufruf, bei dem das SQL aus "SELECT ... WHERE id = " . $postedId ohne Platzhalter zusammengebaut wird. Mehrere Dateien im Modul liefern dasselbe Anti-Pattern. Neuere Versionen patchen einen Teil davon; ältere Shop-Installationen schleppen es mit.

    Für den ORM-First-Fix (und die breitere Einordnung, warum Magentos Query-Builder existiert) haben wir die SQL-Queries-Erklärung separat geschrieben. Die Kurzfassung: Jede Query läuft über $this->_connection->select()->where('id = ?', $id) oder die höherstufige Collection-API. Keine Ausnahmen für „admin-only"-Caller.

    1# Detection: ->query()-Aufrufe mit Konkatenation daneben finden.
    2grep -rnE "->query\s*\(\s*['\"][^'\"]*\"\s*\." app/code/ vendor/
    3 
    4# Außerdem rohe exec-Muster markieren:
    5grep -rn "->exec(" app/code/ | grep -E "\.|\\\$" | head

    4. @escapeNotVerified-Template-Altlast im großen Stil

    @escapeNotVerified (und die Block-Methoden-Schwester $block->escapeNotVerified(...)) ist ein Migrationsmarker aus dem Upgrade von Magento 2.0 auf 2.1, als Magento begann, explizites Output-Escaping in Templates zu verlangen. Der Marker heißt: „Diese Ausgabe war früher unescaped, und niemand hat bestätigt, ob sie escapeHtml, escapeHtmlAttr, escapeJs oder escapeUrl braucht." Jedes Vorkommen ist ein potenzieller XSS-Sink, der auf Triage wartet. Auf Shops, die 2017 die Migrationsarbeit übersprungen haben, liegen die Counts im Hundert- bis Tausenderbereich.

    Kein Notfall im Adminer-Sinn, aber eine Schuld, die aufzinst. Jedes Mal, wenn ein Entwickler eines dieser Templates öffnet und das Muster in eine neue Datei kopiert, wächst der Count. Der Fix ist mechanisch, aber zeitintensiv: jedes Vorkommen lesen, die kontext-richtige escape*-Methode wählen, den Marker entfernen. In Scheiben, nicht ein einziger PR, damit Reviewer Diffs lesen können.

    1# Detection: Vorkommen zählen und Templates auflisten.
    2grep -rn --include='*.phtml' -E '(@|->)escapeNotVerified' app/code/ vendor/ | wc -l
    3grep -rlE '(@|->)escapeNotVerified' --include='*.phtml' app/code/ vendor/ | head -30
    ⚠️
    Schreiben Sie kein Shell-Skript, das escapeNotVerified global durch escapeHtml ersetzt. Die richtige Escape-Methode hängt davon ab, wo die Ausgabe landet: Attribut, JS-Kontext, URL oder Body-Text. Ein pauschaler Swap fixt den Marker und führt echtes XSS ein, indem das Falsche in den falschen Kontext escaped wird. Per Hand, pro Datei.

    5. Frontend-POST-Controller ohne CsrfAwareActionInterface

    Seit Magento 2.3 soll jeder Nicht-GET-Controller Magento\Framework\App\CsrfAwareActionInterface implementieren oder von einer Basisklasse erben, die es tut. Das Interface gibt dem Entwickler zwei Hooks: validateForCsrf() (gibt eine InvalidRequestException zurück, wenn der Request abgelehnt werden soll) und createCsrfValidationException() (optionaler Helper). Ohne das Interface greift ab 2.3 die Default-CSRF-Validation, die den Request üblicherweise ablehnt. Und genau deshalb „fixen" Entwickler das Problem manchmal, indem sie das Interface implementieren, immer null zurückgeben und den Controller damit als sicher deklarieren.

    Das Finding sieht entweder wie ein komplett fehlendes Interface auf einem Custom-Frontend-POST-Controller aus, oder wie eines, das die Validation bedingungslos umgeht. Beides sind aktive CSRF-Lücken. Die höchste Risikoklasse sind File-Upload-Controller: ein Custom-FAQ-Formular, ein Custom-RMA-Flow, ein „Kontakt"-Endpoint mit Anhängen. Die lassen einen Angreifer Dateien aus dem Browser eines Opfers von jedem Origin aus hochladen.

    1# Detection: alle Frontend-Controller-Action-Klassen auflisten, die
    2# CsrfAwareActionInterface nicht implementieren. Auf solche filtern, die Action erweitern.
    3grep -rln --include='*.php' 'extends \\Magento\\Framework\\App\\Action' app/code/ \
    4 | xargs grep -L 'CsrfAwareActionInterface'
    5 
    6# Dann jeden durchgehen: akzeptiert er POST? etc/frontend/routes.xml prüfen
    7# und die HTTP-Method-Hinweise der Klasse.

    6. reCAPTCHA still per <preference> deaktiviert

    Magento liefert reCAPTCHA-Validation für seine REST-Endpoints (Admin-Login, Kunden-Login, Formular-Submissions) als Plugin: Magento\ReCaptchaWebapiRest\Plugin\RestValidationPlugin. Ein einzelner <preference>-Knoten in der di.xml eines Moduls (die DI-Anweisung, die eine Klasse global durch eine andere ersetzt) kann dieses Plugin gegen ein No-op austauschen und reCAPTCHA auf jedem REST-fronted Flow still abschalten. Das Admin-UI zeigt reCAPTCHA weiter als „aktiviert" an. Die Validator-Klasse wird umgangen. Niemand merkt es, bis eine Password-Stuffing-Kampagne durchkommt.

    Das ist eine konkrete Ausprägung eines breiteren Anti-Patterns: <preference> für Probleme zu nutzen, die Plugins besser lösen. Wir haben den ausführlichen Preference-vs-Plugin-Text geschrieben, falls du das Gesamtbild willst. Für das Security-Finding ist die Detection schlichter: nach jeder Preference suchen, die eine reCAPTCHA-Klasse nimmt.

    1# Detection: nach Preferences auf jeder ReCaptcha*-Klasse greppen.
    2grep -rn --include='di.xml' -E 'preference.*ReCaptcha' app/code/
    3 
    4# Außerdem jede Preference im Codebase listen, damit Sie den Rest triagieren können:
    5find app/code -name di.xml | xargs grep -l '<preference' | while read f; do
    6 echo "--- $f ---"
    7 grep -oE '<preference[^>]+for="[^"]+"' "$f"
    8done

    7. Drittanbieter-Profiler, die rohe POST-Bodies in Produktion loggen

    Das letzte Finding ist Nische, aber heftig, wenn es auftaucht. Ein Performance-Profiling-Modul, irgendwann für eine Debug-Session installiert, nie entfernt, das jeden Request-Payload komplett in eine Datei auf Platte schreibt. Mirasvit Profiler ist das kanonische Beispiel: es schreibt den POST-Body in ein rollendes Log unter var/log/ auf Produktions-Sites, weil das Modul für Dev konfiguriert war und jemand vergessen hat, es abzuschalten.

    Das Problem ist, was in diesen Logs landet. Login-POSTs enthalten das Passwort im Klartext. Payment-POSTs enthalten die Kartennummer (kurz, bevor Magento sie strippt, aber „kurz" heißt „im Log"). Checkout-POSTs enthalten Adresse, E-Mail und in vielen Märkten passähnliche Identifier. Alles liegt unverschlüsselt in var/log, bis jemand die Datei rotiert, und „jemand" ist oft „Log-Rotate auf einem 30-Tage-Fenster".

    ⚠️
    Die DSGVO- und PCI-DSS-Implikationen liegen auf der Hand. Die weniger offensichtliche Implikation: „rohe POST-Logs" landen auch in deinen Backup-Volumes, deinen Support-Dumps und deinen Error-Aggregator-Exports. Ein Entwickler, der einen var/log-Tarball auf ein Laptop zieht, um einen Bug zu debuggen, wird zu einem Kundendaten-Leak in dem Moment, in dem das Laptop irgendetwas Schlechtes tut.

    Der Fix ist, jedes Profiling- oder WebAPI-Logging-Modul aus Produktions-Installationen zu entfernen. Wenn du Profiling brauchst, fahr es auf Staging gegen einen produktionsnahen Datensatz, nicht auf der kundenseitigen Seite.

    1# Detection: nach Dateischreibvorgängen von $_POST / $request->getContent() suchen.
    2grep -rn --include='*.php' -E 'file_put_contents|fwrite|error_log' app/code/ vendor/ \
    3 | grep -E '\$_POST|getPost|getContent|getParams'
    4 
    5# Und in einem Staging-Snapshot von var/log nach bedenklichen Inhalten scannen:
    6grep -rE '(password=|"password"\s*:|card_number)' var/log/ 2>/dev/null | head

    Weiterführende Artikel

    Die 7-Punkte-Audit-Checkliste

    1. 01

      Den Webroot nach Datenbank-Tools greppen

      find pub/ -maxdepth 2 -name '*.php*' mit ausgeschlossenen Standard-Entrypoints. Die üblichen Namen (adminer.php, _db.php1, phpmyadmin/, pma/, dbadmin.php) vom Edge aus curlen. Jeder Nicht-404 ist ein Incident. Vor allem anderen entfernen.

    2. 02

      Nach hartkodiertem Basic Auth im Source greppen

      grep -rnE "Authorization.*Basic|base64_encode\(['\"][^'\"]+:" app/code/ vendor/. Jeden Treffer lesen. Klartext-Credentials im PHP-Source gehen in env.php oder fliegen mit dem Modul raus.

    3. 03

      Rohe Query-Konkatenation finden

      Nach ->query(...)- und ->exec(...)-Aufrufen mit String-Konkatenation oder Variablen nahe am SQL greppen. Jeder Match ist ein SQLi-Triage-Kandidat. Mit parametrisierten ORM-Calls fixen, nicht mit Inline-Escaping.

    4. 04

      escapeNotVerified zählen und den Abbau planen

      grep -rn --include='*.phtml' nach @escapeNotVerified und der Method-Variante. Eine Scheibe pro Sprint. Jedes Vorkommen per Hand mit der kontext-richtigen Escape-Methode ersetzen. Niemals ein globales sed replace.

    5. 05

      Frontend-Controller ohne CsrfAwareActionInterface auflisten

      Nach Action-Subklassen greppen und die abziehen, die CsrfAwareActionInterface implementieren. POST-akzeptierende Controller priorisieren, besonders File-Upload-Flows. Das Interface zu implementieren und immer null zurückzugeben, ist dasselbe wie es nicht zu implementieren.

    6. 06

      Preference-Overrides auf reCAPTCHA und andere Security-Plugins finden

      di.xml-Dateien nach Preferences auf Magento\ReCaptcha*-Klassen greppen. Wenn du schon drin bist, jede Preference in app/code auflisten und als safe oder riskant klassifizieren. Security-kritische Plugin-Replacements sind immer riskant.

    7. 07

      Request-Body-Logging auditieren

      Nach file_put_contents- / fwrite- / error_log-Aufrufen nahe $_POST- oder getContent-Nutzung greppen. Auf einem Staging-Snapshot von var/log nach „password=" und Kartenmustern greppen. Jeden Profiler oder verbosen WebAPI-Logger aus Produktion entfernen.

    8. 08

      Die Snippets in CI behalten

      Die Detection-Einzeiler oben gehören in einen CI-Job oder einen Pre-Merge-Hook, der den Build scheitern lässt, wenn ein neuer Treffer auftaucht. Günstige Versicherung gegen den nächsten Entwickler, der eines der sieben wieder einführt.

    Drei von sieben gefunden? Zeit für einen vollen Audit.

    Wir fahren fokussierte Magento-2-Sicherheits-Audits. Du bekommst einen priorisierten Maßnahmenplan, Detection-Snippets auf Grep-Ebene, die du in CI behalten kannst, und Fixes auf Code-Ebene. Zwei bis drei Tage. Direkt an Grep-Output und PR-Diffs, keine 80-seitige PDF.

    Sicherheits-Audit buchen