Zum Inhalt springen

16. Sicherheits-Review

Zu Zen-Modus wechseln

In Kapitel 9 haben Sie Angriffsvektoren einzeln kennengelernt, jeden an einem präparierten Beispiel. Diese Woche wenden Sie alles zusammen auf ein echtes System an: auf Ihres.

Ein Review ist kein Blick über den Code. Es ist ein Verfahren mit festgelegtem Umfang, einer abzuarbeitenden Liste und einem Dokument am Ende. Der Unterschied ist praktisch bedeutsam: „Wir haben uns das angeschaut” lässt sich weder überprüfen noch wiederholen, und in vier Wochen weiß niemand mehr, was geprüft wurde und was nicht.

Zuerst schreiben Sie auf, was geprüft wird, und ebenso, was nicht. Das zweite ist wichtiger, als es klingt: Ohne diese Grenze fehlt am Ende die Aussage, ob eine Lücke übersehen oder bewusst ausgeklammert wurde.

Im UmfangAußerhalb
VPS samt SSH, Firewall, UpdatestandNetz der Schule, Hetzner-Infrastruktur
Docker-Aufbau, Dokploy, Geheimnisse, SicherungenBasis-Images der Hersteller
Eigene Anwendung samt SchnittstelleFremde Dienste, die Sie nur aufrufen
CMS mit ErweiterungenQuellcode des CMS selbst
Abhängigkeiten aus package.json
Gespeicherte Daten und Zugriffsrechte darauf

Erst die Breite, dann die Tiefe. Ein vollständiger Durchgang über alle Bereiche findet die groben Sachen und zeigt, wo es sich lohnt, genauer hinzusehen. Wer stattdessen sofort in ein Thema eintaucht, verbringt drei Stunden mit der Content-Security-Policy und übersieht, dass die Datenbank am offenen Port hängt.

Für die Woche hat sich diese Aufteilung bewährt: ein Durchgang über die Checkliste, danach Tiefenprüfung der zwei riskantesten Bereiche, danach beheben und nachweisen. Behebung braucht mehr Zeit als das Finden, planen Sie entsprechend.

Die folgenden Tabellen sind Ihre Checkliste. Jede Zeile hat eine Prüfung und ein erwartetes Ergebnis, damit die Antwort nicht „passt schon” lautet, sondern eine Ausgabe ist, die Sie in den Bericht kopieren können.

PrüfungKommandoErwartet
SSH-Anmeldungsudo sshd -T | grep -Ei "permitrootlogin|passwordauth"no, und zwar in der wirksamen Konfiguration
Offene Portsss -tulpnnur 22, 80, 443 nach außen
Firewallsudo ufw status verboseRegeln aktiv, Standard eingehend deny
Updatestandapt list --upgradableleer oder nur Unkritisches
Automatische Updatessystemctl status unattended-upgradesaktiv, letzte Ausführung sichtbar
Benutzer mit sudogetent group sudogenau die Personen, die es brauchen
Fehlanmeldungensudo fail2ban-client status sshdSperren greifen
Protokollgrößejournalctl --disk-usagebegrenzt, Platte nicht voll

Prüfen Sie die Ports von außen nach, nicht nur auf dem Server selbst. Ein docker ps mit einer Zeile 0.0.0.0:5432->5432/tcp ist der Befund aus Kapitel 2, der am häufigsten unbemerkt zurückkommt, weil Docker die ufw-Regeln umgeht.

PrüfungWieErwartet
Geheimnisse im Repositorygit log -p | grep -Ei "passwort|secret|api[_-]key"keine Treffer, auch nicht in alten Ständen
Dokploy-ZugangAnmeldung prüfeneigenes Passwort, zweiter Faktor wenn möglich
Image-Standdocker imagesBasis-Images nicht ein Jahr alt
Rollbackeinmal auf die Vorversion und zurückfunktioniert, Dauer bekannt
Sicherung vorhandenAblage prüfenDatenbank und Uploads, aktuell
Sicherung brauchbarin eine leere Datenbank einspielenDaten vollständig, Zeitbedarf bekannt

Die letzte Zeile ist die einzige, die zählt. Eine Sicherung, die nie zurückgespielt wurde, ist eine Vermutung über eine Datei. Machen Sie das einmal, mit Uhr, und schreiben Sie die Dauer auf.

PrüfungWieErwartet
Objektrechtefremde ID mit eigenem Token abrufen404, kein fremder Datensatz
RollenprüfungVerwaltungsendpunkt als normaler Benutzer403
Serverseitige PrüfungEingaben direkt per curl, ohne FormularZod-Validierung greift
FehlerausgabenFehler provozierenproblem+json, kein Stacktrace, keine SQL-Meldung
Sicherheitskopfzeilencurl -sI https://…CSP, HSTS, X-Content-Type-Options gesetzt
CSP wirksamBrowserkonsolekeine Verstöße, kein unsafe-inline
SitzungenAbmelden, altes Cookie erneut sendenabgelehnt
RatenbegrenzungAnmeldung mehrfach falsch429 mit Retry-After
UploadsDatei mit falschem Typ und großer Dateiabgelehnt, Typ serverseitig geprüft
Ausgabe von Benutzertext<img src=x onerror=alert(1)> als Nameerscheint als Text, führt nichts aus
Terminal-Fenster
npm audit --omit=dev # known vulnerabilities in the shipped state
npm outdated # how far behind we are
npx depcheck # what is not used at all any more

Der dritte Befehl ist der unterschätzte. Jedes Paket, das im Projekt liegt und nicht benutzt wird, ist Angriffsfläche ohne Gegenwert. Entfernen ist die billigste Sicherheitsmaßnahme, die es gibt.

Der letzte Bereich läuft ohne Kommando. Schreiben Sie auf, welche personenbezogenen Daten Ihr System speichert, wer sie sehen kann, wo sie überall liegen (Datenbank, Sicherungen, Protokolle, CMS) und wie lange sie bleiben.

Zwei Befunde kommen dabei fast immer heraus: Protokolle, in denen vollständige Anfragedaten samt Namen stehen, und eine Löschfunktion, die es nicht gibt. Beides ist hier nur zu benennen, die rechtliche Seite kommt in der 5. Klasse.

Der Schweregrad ergibt sich aus zwei Fragen: Wie schlimm wäre es, und wie wahrscheinlich ist es?

Auswirkung geringAuswirkung mittelAuswirkung hoch
Wahrscheinlichmittelhochkritisch
Möglichgeringmittelhoch
Unwahrscheinlichgeringgeringmittel

„Wahrscheinlich” heißt: Ein automatisierter Scanner findet das, ohne Ihr System zu kennen. Genau deshalb ist eine offene Datenbank mit Standardpasswort kritisch und nicht mittel, obwohl niemand es bisher versucht hat.

Jeder Befund bekommt genau eine Einordnung, und jede braucht eine Begründung.

  • Sofort beheben. Kritisch und hoch, innerhalb dieser Woche.
  • Geplant beheben. Mit Termin und verantwortlicher Person. Ohne beides ist es keine Planung, sondern eine Hoffnung.
  • Bewusst tragen. Legitim, wenn der Aufwand außer Verhältnis steht. Dann gehört aufgeschrieben, wer das entschieden hat, ab wann, und was passieren müsste, damit die Entscheidung neu getroffen wird.

Dasselbe Format wie in Kapitel 9, um zwei Felder erweitert. Jeder Befund kommt als eigene Datei nach docs/befunde/.

# B-04: Buchungen fremder Personen abrufbar
Bereich: Anwendung / Autorisierung
Schweregrad: hoch (Auswirkung hoch, Wahrscheinlichkeit möglich)
Gefunden: 2026-05-12, Prüfung Objektrechte
## Nachweis
curl -H "Cookie: sitzung=<eigenes Token>" \
https://projekt.example.at/api/buchungen/812
→ 200, Datensatz einer fremden Person mit Name und Telefonnummer
## Ursache
Der Handler lädt die Buchung über die ID und prüft nur, ob eine gültige
Sitzung besteht. Der Vergleich mit der Besitzerin fehlt.
## Auswirkung
Jede angemeldete Person kann durch Hochzählen der ID sämtliche Buchungen
samt Kontaktdaten auslesen.
## Empfehlung
Prüfung im Service statt im Handler, Antwort 404 bei fremden Objekten
(Kapitel 8).
## Status
Behoben am 2026-05-13, Nachweis unten, Regressionstest angelegt.

Behebung ohne erneuten Test ist keine Behebung, sondern eine Absicht. Der Nachweis ist derselbe Weg, der zum Befund geführt hat, nur mit dem erwarteten Ergebnis:

curl -i -H "Cookie: sitzung=<eigenes Token>" \
https://projekt.example.at/api/buchungen/812
HTTP/2 404
content-type: application/problem+json

Und dann der Schritt, der einen Befund dauerhaft erledigt: Aus dem Befund wird ein Test. Sonst kommt er beim nächsten Umbau zurück, und zwar unbemerkt.

tests/authorization.test.ts
it("does not hand out other people's bookings", async () => {
const other = await createBooking({ userId: userB.id });
const response = await app.request(`/api/bookings/${other.id}`, {
headers: { Cookie: await sessionFor(userA) },
});
expect(response.status).toBe(404);
});

Ein solcher Test ist mehr wert als der Bericht, in dem der Befund steht. Der Bericht dokumentiert einen Zustand, der Test hält ihn.

Zwei Teams prüfen einander. Das findet mehr als jede Selbstprüfung, weil fremde Augen keine Betriebsblindheit mitbringen und weil niemand die Stelle auslässt, an der er selbst geschlampt hat.

Davor steht eine schriftliche Vereinbarung, unterschrieben von beiden Seiten. Das ist keine Formalie, sondern die Übung für genau das Dokument, das in der Berufspraxis vor jedem Penetrationstest steht.

## Prüfvereinbarung
Prüfendes Team: Team B
Geprüftes System: https://projekt-a.example.at, VPS 203.0.113.7
Zeitraum: 13.05.2026, 08:00 bis 12:00
Erlaubt: Zugriff über die Weboberfläche und die Schnittstelle,
Anmeldung mit den bereitgestellten Testkonten,
Portscan des genannten Servers
Nicht erlaubt: Lasttests, Löschen von Daten, Zugriff auf andere
Systeme, Weitergabe gefundener Daten
Bei Befund: sofort an <Kontakt>, keine Veröffentlichung
Abbruch: bei Ausfall des Systems sofort melden und stoppen
Datum, Unterschriften beider Teams

Und für die Besprechung danach eine einzige Regel: Der Befund gilt dem System. „Diese Route prüft die Berechtigung nicht” ist ein Befund. „Ihr habt schlampig gearbeitet” ist keiner. Wer prüft, bringt Nachweise mit; wer geprüft wird, hört zu und diskutiert die Einstufung, nicht die Person.

  1. Prüfen, was ohnehin gut ist, und die unangenehmen Bereiche überspringen. Deshalb arbeiten Sie die Liste ab, statt zu wählen.
  2. Befunde ohne Nachweis. Nach zwei Wochen weiß niemand mehr, welche ID mit welchem Token gemeint war.
  3. Alles als kritisch einstufen und die Priorisierung damit entwerten.
  4. Behebung ohne erneuten Test.
  5. Behebung ohne Regressionstest. Der Befund kommt beim nächsten Umbau zurück.
  6. Sicherung als vorhanden erklärt, ohne sie je zurückgespielt zu haben.
  7. Nur die Anwendung geprüft und Server, CMS und Abhängigkeiten ausgelassen. Angreifer suchen sich die einfachste Stelle aus.
  8. Nur auf dem Server geprüft, welche Ports offen sind, statt von außen.
  9. Fremde Systeme geprüft, auch nur ein Portscan, ohne Vereinbarung.
  10. Bericht ohne Restrisiken. Ein Bericht ohne offene Punkte ist selten vollständig und wirkt entsprechend.
  • Anwenden: einen Prüfumfang festlegen und begründen, was außerhalb bleibt.
  • Analysieren: ein Gesamtsystem strukturiert gegen eine Checkliste prüfen, über alle Ebenen von SSH bis Ausgabe.
  • Beurteilen: Befunde nach Auswirkung und Wahrscheinlichkeit einstufen und die Einstufung vertreten.
  • Beurteilen: zwischen sofortiger Behebung, geplanter Behebung und bewusst getragenem Risiko entscheiden.
  • Anwenden: Befunde beheben und die Behebung mit einem wiederholbaren Nachweis belegen.
  • Erschaffen: aus einem Befund einen Regressionstest ableiten.
  • Anwenden: ein gegenseitiges Review nach schriftlicher Vereinbarung durchführen.
  • Erklären: verbleibende Restrisiken benennen und die eigene Sicherheitslage begründen.

Ein Sicherheitsbericht in docs/sicherheitsbericht.md, der auf die Einzelbefunde in docs/befunde/ verweist:

  • Geprüfter Umfang und was ausdrücklich außerhalb lag.
  • Abgearbeitete Checkliste über alle fünf Bereiche, mit Ergebnis je Zeile.
  • Befundliste mit Schweregrad nach der Matrix.
  • Behebungsnachweise für alle Befunde ab „hoch”.
  • Mindestens ein Regressionstest, der aus einem Befund entstanden ist.
  • Nachweis einer eingespielten Sicherung samt Zeitbedarf.
  • Restrisiken mit Begründung und der Angabe, wer sie wann entschieden hat.
  • Aufgabe - Checkliste abarbeiten und Befundbericht schreiben
  • Aufgabe - Gegenseitiges Review mit Vereinbarung
  • Aufgabe - Aus einem Befund einen Regressionstest machen