Statische und dynamische Analyse richtig kombinieren
Wer Magento-Anwendungen ohne systematische Sicherheitsprüfung entwickelt, verlässt sich auf Zufallsfunde statt auf Werkzeuge, die Schwachstellen zuverlässig aufdecken. Statische Analyse mit PHPStan und Psalm findet gefährliche Datenflüsse direkt im Quellcode, während dynamische Scanner wie OWASP ZAP echte Angriffe gegen eine laufende Staging-Umgebung fahren. Dieser Artikel zeigt, wie beide Ansätze zusammenwirken, wo ihre Grenzen liegen und wann sich welche Investition lohnt.
Inhaltsverzeichnis
- 1. Statische vs. dynamische Analyse: die Grundmechanik
- 2. Wie SAST arbeitet: AST, Kontrollfluss und Taint-Tracking
- 3. Wie DAST arbeitet: Crawling, Fuzzing und echte HTTP-Angriffe
- 4. Was SAST erkennt und was DAST findet: blinde Flecken im Vergleich
- 5. False-Positive-Raten und Tuning
- 6. SAST in die CI-Pipeline integrieren: PHPStan-Security-Regeln
- 7. Psalm Taint Analysis: Datenfluss von Source zu Sink
- 8. DAST gegen eine laufende Staging-Umgebung
- 9. SAST und DAST im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Statische vs. dynamische Analyse: die Grundmechanik
SAST (Static Application Security Testing) und DAST (Dynamic Application Security Testing) lösen dieselbe Aufgabe, Schwachstellen vor einem Angreifer zu finden, mit fundamental unterschiedlicher Mechanik. SAST analysiert den Quellcode, ohne ihn auszuführen: Ein Parser baut einen Abstract Syntax Tree, ein Analyzer verfolgt Kontrollfluss und Datenfluss durch Funktionen, Klassen und Aufrufketten hinweg. Das Tool sieht jede Zeile Code, unabhängig davon, ob sie zur Laufzeit jemals erreicht wird. DAST dagegen behandelt die Anwendung als Blackbox: Es sendet echte HTTP-Requests gegen eine laufende Instanz und wertet die tatsächlichen Antworten aus, genau wie ein externer Angreifer es täte.
Der Unterschied zeigt sich deutlich bei komplexem Code: SAST erkennt eine SQL-Injection, sobald unsanitierte Nutzereingabe in einer Query-Zeichenkette landet, egal ob dieser Codepfad in der Praxis je aufgerufen wird. DAST erkennt dieselbe Schwachstelle nur, wenn der Scanner den betroffenen Endpunkt tatsächlich findet und mit einem passenden Payload testet, dafür im tatsächlichen Laufzeitkontext mit aktiver Session und echtem HTTP-Response.
2. Wie SAST arbeitet: AST, Kontrollfluss und Taint-Tracking
Moderne SAST-Tools für PHP wie PHPStan und Psalm arbeiten auf mehreren Ebenen gleichzeitig. Zunächst wird der Code in einen Abstract Syntax Tree überführt, der die syntaktische Struktur jeder Datei abbildet. Darauf aufbauend berechnet der Analyzer einen Kontrollflussgraphen, um zu verstehen, welche Codepfade unter welchen Bedingungen erreichbar sind. Die eigentliche Sicherheitsprüfung erfolgt über Taint Analysis: Variablen, die aus einer nicht vertrauenswürdigen Quelle wie $_GET oder einer Request-Klasse stammen, werden als "tainted" markiert und durch den gesamten Datenfluss verfolgt.
Erreicht eine solche markierte Variable eine gefährliche Senke wie eine SQL-Query, einen echo-Aufruf oder eine unserialize()-Funktion, ohne vorher durch eine anerkannte Sanitierungsfunktion geflossen zu sein, meldet das Tool einen Fund. Dieser Mechanismus arbeitet rein auf Basis des Codes, benötigt keine laufende Anwendung und keine Testdaten, dafür aber präzise Typinformationen und gepflegte Stub-Definitionen für Frameworks wie Magento.
<?php
declare(strict_types=1);
namespace Mironsoft\SeoSuite\Model;
use Magento\Framework\App\ResourceConnection;
/**
* Example: SAST-detectable SQL injection via string concatenation.
* A static analyzer flags this because tainted request data flows
* directly into a raw SQL query without going through a sanitizer.
*/
final class ProductLookup
{
public function __construct(
private readonly ResourceConnection $resourceConnection
) {
}
/**
* VULNERABLE: user-controlled $sku is concatenated into raw SQL.
* Taint source: HTTP request parameter.
* Taint sink: Zend_Db_Adapter::query().
*
* @param string $sku Raw SKU value taken directly from $_GET.
* @return array<int, array<string, mixed>>
*/
public function findBySkuUnsafe(string $sku): array
{
$connection = $this->resourceConnection->getConnection();
// TaintedSql: never build queries via string concatenation
$query = "SELECT * FROM catalog_product_entity WHERE sku = '" . $sku . "'";
return $connection->fetchAll($query);
}
/**
* SAFE: parameter binding removes the taint before it reaches the sink.
*
* @param string $sku SKU value, still untrusted at this point.
* @return array<int, array<string, mixed>>
*/
public function findBySkuSafe(string $sku): array
{
$connection = $this->resourceConnection->getConnection();
$select = $connection->select()
->from('catalog_product_entity')
->where('sku = ?', $sku);
return $connection->fetchAll($select);
}
}
3. Wie DAST arbeitet: Crawling, Fuzzing und echte HTTP-Angriffe
Ein DAST-Scanner startet ohne Kenntnis des Quellcodes und muss die Angriffsoberfläche erst selbst erschließen. Im ersten Schritt crawlt das Tool die Anwendung wie ein Browser: Es folgt Links, füllt Formulare aus, ruft JavaScript-generierte Routen auf und protokolliert jeden gefundenen Endpunkt inklusive Parameter. Aus diesem Katalog entsteht der sogenannte Site-Tree, die Grundlage für den anschließenden aktiven Scan, bei dem jeder Parameter mit einer Bibliothek bekannter Angriffs-Payloads bestückt wird, etwa SQL-Metazeichen, XSS-Payloads oder Pfad-Traversal-Sequenzen.
Anders als SAST bewertet DAST das tatsächliche Verhalten des Systems: veränderte Response-Zeiten, abweichende Statuscodes, reflektierte Payloads im HTML oder SQL-Fehlermeldungen im Response-Body gelten als Indiz für eine echte, ausnutzbare Schwachstelle. Diese Blackbox-Perspektive ist technologieunabhängig, funktioniert also unabhängig davon, ob die Anwendung in PHP, Java oder Node.js geschrieben ist, erkennt dafür aber nur, was der Crawler tatsächlich erreicht und mit den vorhandenen Zugangsdaten testen kann.
4. Was SAST erkennt und was DAST findet: blinde Flecken im Vergleich
SAST ist stark bei Schwachstellenklassen, die sich direkt aus dem Datenfluss im Code ableiten lassen: SQL-Injection durch Stringverkettung, Cross-Site Scripting durch fehlende Ausgabe-Kodierung, unsichere Deserialisierung und hartcodierte Zugangsdaten. Der entscheidende Vorteil ist Vollständigkeit: Ein Codepfad, der nur unter einer seltenen Fehlerbedingung erreicht wird, wird trotzdem analysiert, weil SAST nicht auf tatsächlichen Traffic angewiesen ist. Die Kehrseite: Laufzeitkonfiguration, Server-Header, TLS-Einstellungen oder die Interaktion mehrerer Microservices liegen außerhalb des Sichtfelds eines reinen Code-Scanners.
DAST deckt genau diese Lücke ab: fehlende Security-Header, schwache TLS-Konfiguration, Session-Fixation, Authentifizierungs-Bypässe über manipulierte Cookies und Fehlkonfigurationen, die erst im Zusammenspiel von Webserver, Framework und Infrastruktur entstehen. Was DAST dafür systematisch übersieht, sind Codepfade, die der Crawler nicht erreicht, etwa Admin-Funktionen ohne verlinkten Einstiegspunkt, sowie tiefere Business-Logic-Fehler, die kein generischer Payload auslöst. Beide Tool-Klassen ergänzen sich deshalb, statt sich zu ersetzen.
5. False-Positive-Raten und Tuning
SAST-Tools neigen ohne Tuning zu hohen False-Positive-Raten, weil sie konservativ jeden potenziellen Datenfluss melden, auch wenn eine Sanitierung an einer Stelle erfolgt, die dem Analyzer unbekannt ist. Ein typisches Beispiel: ein eigenes Escaping-Helper-Modul, das PHPStan oder Psalm nicht als vertrauenswürdige Sanitierungsfunktion kennt. Die Lösung liegt in Baselines, die den aktuellen Bestand an Funden einfrieren, damit nur neue Verstöße den Build brechen, kombiniert mit expliziten Annotationen wie @psalm-taint-escape, die dem Analyzer mitteilen, welche Funktionen Daten tatsächlich bereinigen.
Bei DAST-Scannern sind False Positives seltener, aber teurer in der Triage, weil jeder Fund manuell gegen die echte Anwendung verifiziert werden muss. OWASP ZAP erlaubt dafür Alert-Filter, die bekannte, bewusst akzeptierte Findings dauerhaft ausblenden, sowie eine Context-Konfiguration, die Authentifizierung und Scope präzise eingrenzt. Ohne diese Pflege sammeln sich in beiden Tool-Klassen über Monate hinweg so viele ignorierte Meldungen an, dass echte Funde im Rauschen untergehen, ein Zustand, der als Alert Fatigue bekannt ist.
6. SAST in die CI-Pipeline integrieren: PHPStan-Security-Regeln
PHPStan selbst ist in erster Linie ein Typ-Checker, entfaltet seine Sicherheitswirkung aber in Kombination mit spezialisierten Regelsets wie phpstan/phpstan-security oder eigenen Custom Rules, die gefährliche Funktionsaufrufe wie eval(), unserialize() ohne allowed_classes oder system() mit dynamischen Argumenten markieren. Für Magento-Projekte lohnt sich zusätzlich eine Regel, die direkte SQL-Stringverkettung außerhalb des Zend-Db-Select-Objekts erkennt, weil dieses Muster in Legacy-Modulen besonders häufig vorkommt. Entscheidend ist, das Level schrittweise zu erhöhen, damit Bestandscode nicht sofort hunderte Findings produziert.
Als Quality Gate eingebunden, bricht ein fehlgeschlagener PHPStan-Lauf den Merge, bevor unsicherer Code überhaupt in den Hauptzweig gelangt, deutlich früher als jeder DAST-Scan, der erst gegen eine deployte Umgebung laufen kann. Die Kombination aus Baseline-Datei und striktem Gate für neue Verstöße hält die Einführung praktikabel, selbst in gewachsenen Codebasen mit tausenden bestehenden Dateien.
# phpstan.neon: security-relevant rules for a Magento 2 codebase
parameters:
level: 6
paths:
- app/code/Mironsoft
# Freeze existing findings, only fail the build on new violations
baselineFile: phpstan-baseline.neon
ignoreErrors:
# Known Magento interface gap, documented and accepted
- '#Call to an undefined method .*PageInterface::getData\(\)#'
includes:
- vendor/phpstan/phpstan-security/extension.neon
services:
-
class: Mironsoft\PhpStanRules\NoRawSqlConcatenationRule
tags:
- phpstan.rules.rule
-
class: Mironsoft\PhpStanRules\NoUnsafeUnserializeRule
tags:
- phpstan.rules.rule
# .github/workflows/sast.yml: run static analysis as a required check
name: SAST
on:
pull_request:
branches: [main]
jobs:
phpstan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
tools: composer:v2
- name: Install dependencies
run: composer install --no-progress --prefer-dist
# Fail the pipeline on any error above the current baseline
- name: Run PHPStan security analysis
run: vendor/bin/phpstan analyse app/code/Mironsoft --level=6 --error-format=github
- name: Run Psalm taint analysis
run: vendor/bin/psalm --taint-analysis --report=psalm-report.sarif
7. Psalm Taint Analysis: Datenfluss von Source zu Sink
Psalm bietet mit seiner Taint-Analysis-Engine einen expliziteren Ansatz als PHPStan: Statt nur Typen zu prüfen, modelliert Psalm den kompletten Datenfluss zwischen definierten Quellen und Senken. Quellen sind unter anderem Superglobals, Request-Objekte und Datenbankergebnisse, Senken sind gefährliche Funktionen wie exec(), echo in HTML-Kontext oder PDO::query(). Wird Taint-Analysis aktiviert, meldet Psalm konkrete Fund-Typen wie TaintedSql für SQL-Injection-Risiken oder TaintedHtml für reflektiertes XSS, jeweils mit vollständigem Aufrufpfad vom Eingabepunkt bis zur Senke.
Der Vorteil gegenüber generischen Regeln liegt in der Nachvollziehbarkeit: Ein Entwickler sieht nicht nur, dass eine Funktion unsicher ist, sondern die exakte Kette von Methodenaufrufen, über die die unsichere Eingabe dorthin gelangt ist. Das erleichtert sowohl die Bewertung als echter Fund als auch die gezielte Behebung. Eigene Sanitierungsfunktionen lassen sich über @psalm-taint-escape-Annotationen als vertrauenswürdig markieren, wodurch die False-Positive-Rate mit jeder gepflegten Annotation sinkt.
<?xml version="1.0"?>
<!-- psalm.xml: enable taint analysis for the whole codebase -->
<psalm
errorLevel="4"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="app/code/Mironsoft" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<!-- Run with: vendor/bin/psalm --taint-analysis --
Findings surface as TaintedSql, TaintedHtml, TaintedShell, etc. -->
<issueHandlers>
<TaintedSql errorLevel="error" />
<TaintedHtml errorLevel="error" />
<TaintedShell errorLevel="error" />
<TaintedUnserialize errorLevel="error" />
</issueHandlers>
</psalm>
8. DAST gegen eine laufende Staging-Umgebung
Ein DAST-Scan gehört niemals gegen Produktion, sondern gegen eine Staging-Umgebung, die dem Live-System so nahe wie möglich kommt, inklusive realistischer Testdaten, aber ohne echte Kundendaten. OWASP ZAP lässt sich als CLI im Docker-Container betreiben und unterscheidet zwischen einem schnellen Baseline-Scan, der nur passiv Traffic beobachtet, und einem vollständigen aktiven Scan, der Payloads gegen jeden gefundenen Parameter feuert und dabei deutlich mehr Zeit sowie Last auf der Zielumgebung erzeugt. Für authentifizierte Bereiche ist eine Context-Datei mit Login-Sequenz und Session-Erkennung notwendig, sonst scannt ZAP nur die öffentlich erreichbaren Seiten.
Ein aktiver Scan sollte außerhalb der Kernarbeitszeiten laufen, weil er die Staging-Umgebung spürbar belastet und im schlimmsten Fall Testdaten verändert. Die Ergebnisse landen als HTML- oder JSON-Report, idealerweise automatisiert als Artefakt der CI-Pipeline, damit Findings nicht in einer separaten Konsole verloren gehen, sondern direkt im gewohnten Review-Prozess landen.
#!/usr/bin/env bash
# run-dast-scan.sh: run an OWASP ZAP scan against the staging environment
set -euo pipefail
readonly STAGING_URL="https://staging.mironsoft.de"
readonly CONTEXT_FILE="zap/mironsoft-staging.context"
readonly REPORT_DIR="zap-reports/$(date +%Y%m%d-%H%M%S)"
mkdir -p "$REPORT_DIR"
# Fast baseline scan: passive checks only, safe to run on every deploy
docker run --rm -v "$(pwd)/${REPORT_DIR}:/zap/wrk:rw" \
ghcr.io/zaproxy/zaproxy:stable zap-baseline.py \
-t "$STAGING_URL" \
-r baseline-report.html \
-J baseline-report.json
# Full active scan: authenticated, off-hours only, higher load
docker run --rm \
-v "$(pwd)/${CONTEXT_FILE}:/zap/context.context:ro" \
-v "$(pwd)/${REPORT_DIR}:/zap/wrk:rw" \
ghcr.io/zaproxy/zaproxy:stable zap-full-scan.py \
-t "$STAGING_URL" \
-n /zap/context.context \
-r active-report.html \
-J active-report.json \
-z "-config api.disablekey=true"
echo "[OK] Reports written to ${REPORT_DIR}"
9. SAST und DAST im direkten Vergleich
Beide Tool-Klassen kosten Zeit, Infrastruktur und Pflegeaufwand, aber an unterschiedlichen Stellen im Entwicklungszyklus. SAST lohnt sich fast immer zuerst, weil die Einstiegshürde niedrig ist: keine laufende Umgebung nötig, Ergebnisse innerhalb von Sekunden, direkte Integration in jeden Pull Request. DAST erfordert dagegen eine stabile Staging-Umgebung, Testdaten, Authentifizierungs-Setup und Zeit für die Scan-Laufzeit, liefert dafür aber Aussagen über das System als Ganzes, die kein Code-Scanner treffen kann.
| Dimension | SAST | DAST |
|---|---|---|
| Analyseumfang | Kompletter Quellcode, auch unerreichte Pfade | Nur tatsächlich erreichbare Endpunkte zur Laufzeit |
| False-Positive-Rate | Hoch ohne Baseline und Tuning | Niedriger, da reale Server-Antworten geprüft werden |
| Frühester CI-Integrationspunkt | Pre-Commit oder Pre-Merge | Erst nach Deployment auf Staging |
| Laufzeit pro Durchlauf | Sekunden bis wenige Minuten | Minuten bis Stunden je nach Scanumfang |
| Typische Schwachstellenklassen | Taint-Flows, SQLi/XSS im Code, unsichere Deserialisierung | Auth-Bypass, Server-Fehlkonfiguration, Session-Handling |
| Business-Logic-Fehler | Kaum erkennbar ohne Kontextwissen | Nur mit gezielt geschriebenen Testszenarien |
Wer nur ein Budget für ein Tool hat, sollte mit SAST beginnen, weil es Schwachstellen vor dem Merge stoppt und pro gefundenem Problem am günstigsten ist. DAST wird spätestens dann zur Pflicht, sobald eine Anwendung öffentlich erreichbar ist und Compliance-Anforderungen wie PCI-DSS regelmäßige externe Scans verlangen. Die wirtschaftlich sinnvollste Strategie ist selten "entweder oder", sondern SAST bei jedem Commit und DAST in einem festen Rhythmus, etwa wöchentlich oder vor jedem größeren Release.
Mironsoft
Application Security Testing für Magento- und Hyvä-Projekte
SAST und DAST in eurer Magento-Pipeline etablieren?
Wir richten PHPStan-Security-Regeln und Psalm Taint Analysis in eurer CI-Pipeline ein, konfigurieren OWASP-ZAP-Scans gegen eure Staging-Umgebung und helfen, False Positives dauerhaft unter Kontrolle zu halten.
Security-Audit
PHPStan- und Psalm-Konfiguration mit Security-Regeln für euren Codebestand
CI-Integration
Taint Analysis und Quality Gates in GitLab CI oder GitHub Actions einrichten
DAST-Scans
OWASP-ZAP-Scans gegen Staging-Umgebungen planen und Ergebnisse triagieren
10. Zusammenfassung
SAST- und DAST-Tools lösen keine konkurrierenden, sondern sich ergänzende Aufgaben. SAST analysiert den Quellcode ohne Ausführung und findet Taint-Flows, unsichere Funktionsaufrufe und Datenlecks, bevor Code überhaupt gemergt wird, deckt aber keine Laufzeitkonfiguration ab. DAST testet die laufende Anwendung wie ein echter Angreifer und findet Server-Fehlkonfigurationen, Auth-Bypässe und Session-Probleme, übersieht dafür Codepfade, die der Crawler nicht erreicht. PHPStan-Security-Regeln und Psalm Taint Analysis gehören in jede CI-Pipeline, weil sie schnell, günstig und früh im Entwicklungsprozess greifen.
OWASP ZAP gegen eine realistische Staging-Umgebung ergänzt diese frühe Prüfung um die Perspektive von außen, insbesondere für Fehlkonfigurationen, die sich nie allein aus dem Quellcode ableiten lassen. Der entscheidende Erfolgsfaktor ist konsequentes Tuning auf beiden Seiten: Baselines und Sanitierungs-Annotationen bei SAST, Alert-Filter und präzise Context-Konfiguration bei DAST, damit echte Funde nicht im Rauschen aus False Positives untergehen.
SAST- und DAST-Tools im Vergleich: Das Wichtigste auf einen Blick
SAST: Quellcode-Analyse
Fängt Taint-Flows und unsichere Funktionsaufrufe vor dem Merge ab. PHPStan und Psalm sind die Standardwerkzeuge für PHP.
DAST: Laufzeit-Analyse
Deckt reale Angriffsflächen und Fehlkonfigurationen im Staging auf. OWASP ZAP simuliert echte HTTP-Angriffe.
False Positives im Griff
Baselines, Sanitierungs-Annotationen und Alert-Filter senken den Rauschpegel spürbar auf beiden Seiten.
Kombinierte Strategie
SAST bei jedem Commit, DAST regelmäßig gegen Staging. Beide Ansätze ergänzen sich, keiner ersetzt den anderen.