Eingabevalidierung fehlt auf dem Server
CWE-20CWE-602OWASP A04:2021Aktualisiert 4. September 20265 Min. Lesezeit
Prüfungen, die nur im Browser stattfinden, Pflichtfelder, Höchstlängen, Auswahllisten, Preisberechnungen, lassen sich mit einer direkten Anfrage vollständig umgehen. Der Browser ist eine Erleichterung für den Nutzer, keine Sicherheitsmaßnahme. Jeder Wert, den der Server erhält, muss dort erneut und vollständig validiert werden.
Ein Formular, das ordentlich vor einem zu langen Feld warnt und in einer Auswahlliste nur vorhandene Optionen zeigt, erweckt den Eindruck, die Eingabe sei unter Kontrolle. Dieser Eindruck gilt nur für Nutzer, die das Formular verwenden. Wer die Anfrage selbst zusammenstellt, hat mit keiner dieser Regeln zu tun. Im Folgenden lesen Sie, was daraus folgt und wie Sie die Prüfung dorthin zurückholen, wo sie zählt.
Warum zählt Validierung im Browser nicht?
Alles, was im Browser geschieht, steht unter der Kontrolle des Nutzers. Das HTML lässt sich ändern, das JavaScript abschalten oder umschreiben, und die Anfrage lässt sich vollständig außerhalb der Seite zusammenstellen. Fehlende Eingabevalidierung auf dem Server bedeutet, dass die Anwendung sich auf Prüfungen verlässt, die in dieser Umgebung stattfinden.
Die Unterscheidung, die weiterhilft: Der Browser validiert für den Komfort, der Server validiert für die Richtigkeit. Ein maxlength an einem Eingabefeld verhindert, dass ein Nutzer versehentlich zu viel tippt. Es verhindert nicht, dass jemand eine Anfrage mit einem Wert von zehntausend Zeichen sendet. Beide Prüfungen dürfen bestehen, doch nur die zweite ist eine Maßnahme.
Denken Sie an ein Bestellformular mit Kästchen fester Größe. Wer das Formular ausfüllt, wird von diesen Kästchen geführt. Die Bestellung wird aber letztlich von jemandem bearbeitet, der nur auf die eingetragenen Werte sieht, und dieser erhält auch die Bestellung, die ohne Kästchen auf ein eigenes Blatt geschrieben wurde.
Wie umgeht ein Angreifer die Prüfung im Browser?
Verwundbar:
<!-- Die einzige Prüfung steckt im Formular -->
<form action="/bestellen" method="post">
<input name="menge" type="number" min="1" max="10" required>
<input name="preis" type="hidden" value="49.95">
<select name="versandart">
<option value="standard">Standard</option>
<option value="express">Express (+ 9,95)</option>
</select>
<button>Bestellen</button>
</form>
app.post('/bestellen', erfordertAnmeldung, async (req, res) => {
const gesamt = req.body.menge * req.body.preis;
await bestellungen.erstelle({
nutzer: req.nutzer.id,
menge: req.body.menge,
versandart: req.body.versandart,
gesamt,
});
res.send('Danke für Ihre Bestellung');
});
Der Server übernimmt alles, was hereinkommt, einschließlich des Preises aus einem verborgenen Feld. Eine Anfrage außerhalb des Formulars ist trivial:
POST /bestellen HTTP/1.1
Content-Type: application/json
{"menge": -5, "preis": 0.01, "versandart": "gratis-kurier"}
Hier geschehen drei Dinge zugleich. Der Preis wird vom Kunden bestimmt. Die Menge ist negativ, was je nach Verarbeitung zu einer Gutschrift führen kann. Und die Versandart ist ein Wert, der in der Auswahlliste nicht vorkam und von dem niemand weiß, wie der Rest des Systems darauf reagiert.
Sicher:
import { z } from 'zod';
const Bestellung = z.object({
artikelId: z.string().uuid(),
menge: z.number().int().min(1).max(10),
versandart: z.enum(['standard', 'express']),
}).strict(); // unbekannte Felder werden abgewiesen
app.post('/bestellen', erfordertAnmeldung, async (req, res) => {
const ergebnis = Bestellung.safeParse(req.body);
if (!ergebnis.success) {
return res.status(400).json({ fehler: 'Ungültige Eingabe' });
}
const { artikelId, menge, versandart } = ergebnis.data;
// Preise stammen aus der eigenen Verwaltung, nie aus der Anfrage
const artikel = await artikelRepo.finde(artikelId);
if (!artikel || !artikel.verfuegbar) return res.status(404).json({ fehler: 'Unbekannter Artikel' });
const gesamt = artikel.preis * menge + VERSANDKOSTEN[versandart];
await bestellungen.erstelle({ nutzer: req.nutzer.id, artikelId, menge, versandart, gesamt });
res.json({ ok: true, gesamt });
});
Das Schema beschreibt die vollständige Form der Anfrage: welche Felder es gibt, von welchem Typ, in welchen Grenzen und mit welchen zulässigen Werten. Mit strict() werden unbekannte Felder abgewiesen, was unmittelbar davor schützt, dass Eigenschaften mitgesendet werden, die nicht vorgesehen waren. Und das Wichtigste: Der Preis stammt nicht mehr aus der Anfrage, sondern aus Ihrer eigenen Verwaltung. Daten, die der Nutzer nicht bestimmen soll, sollten Sie auch nicht von ihm annehmen.
Welche Auswirkungen hat fehlende Eingabevalidierung?
Der Schweregrad reicht von mittel bis hoch und bestimmt sich danach, was hinter der fehlenden Prüfung geschieht. Fehlende Validierung ist selten die Schwachstelle selbst; sie ist die Voraussetzung, unter der andere Schwachstellen entstehen.
Auf der technischen Seite ist sie der erste Schritt praktisch jedes Einschleusungsangriffs. Werte, die ungeprüft an eine Datenbank, einen Systemaufruf, eine Vorlage oder einen Dateipfad weitergereicht werden, sind genau das, was SQL-Injection, Befehlseinschleusung und Path Traversal ermöglicht. Ein zu langer oder unerwartet geformter Wert kann zudem zu einer Störung führen, in der die Anwendung mehr preisgibt als beabsichtigt.
Auf der fachlichen Seite sind die Folgen konkreter und mitunter unmittelbar messbar. Ein vom Kunden bestimmter Preis, eine negative Menge, die zu einer Rückzahlung führt, ein Rabatt, der sich selbst aufaddiert, ein Status, der auf “bezahlt” gesetzt wird. Das sind keine technischen Schwachstellen, sondern logische, und gerade diese werden von automatisierten Werkzeugen so gut wie nie gefunden.
Wie spürt man fehlende Eingabevalidierung auf?
Ein Tester arbeitet nicht über das Formular, sondern sendet die Anfragen unmittelbar. Für jedes Feld wird ausprobiert, was bei Werten geschieht, die die Oberfläche nicht zulässt: eine negative Zahl, eine extrem große Zahl, Text, wo eine Zahl erwartet wird, ein leerer Wert, ein fehlendes Feld, ein Wert, der nicht in der Auswahlliste stand.
Ebenso wird auf verborgene Felder geachtet und auf Werte, die der Client nicht bestimmen sollte: Preise, Rabatte, Status, Kennungen von Eigentümern, Rollen. Jeder davon wird geändert, um zu sehen, ob der Server ihn übernimmt. Zusätzlich werden Felder mitgesendet, die im Formular nicht vorkamen, um zu prüfen, ob sie stillschweigend verarbeitet werden. Ferner wird geprüft, ob die Validierung auf jedem Kanal gleich ist, denn Weboberfläche und API sind häufig getrennt umgesetzt und unterscheiden sich dann in der Strenge. AssistSec widmet den fachlichen Regeln besondere Aufmerksamkeit, denn diese Prüfungen unterscheiden sich je Anwendung und werden deshalb von Standardwerkzeugen nicht abgedeckt.
Wie verhindert man fehlende Eingabevalidierung?
- Validieren Sie jede Anfrage serverseitig mit einem Schema, das die vollständige Form beschreibt.
- Weisen Sie unbekannte Felder ab, statt sie zu übergehen.
- Arbeiten Sie mit zulässigen Werten und Bereichen, nicht mit einer Liste des Verbotenen.
- Übernehmen Sie Preise, Rabatte, Status und Rollen nie aus der Anfrage; holen Sie sie aus Ihrer eigenen Verwaltung.
- Validieren Sie neben der Form auch die fachlichen Regeln: Bestand, Gültigkeit, Zusammenhang zwischen Feldern.
- Führen Sie die Validierung an einer Stelle je Endpunkt durch, damit kein Pfad daran vorbeikommt.
- Betrachten Sie Validierung im Browser als Bedienkomfort und wiederholen Sie alles auf dem Server.
- Das Maskieren bei der Ausgabe bleibt nötig; Validierung ersetzt es nicht.
- Wenden Sie dieselben Regeln auf APIs, Webhooks und Hintergrundverarbeitung an, nicht nur auf Webformulare.
Quellen
Häufige Fragen
Muss ich dann alles doppelt validieren?
Ja, aber mit unterschiedlichem Zweck. Im Browser validieren Sie für den Bedienkomfort: unmittelbare Rückmeldung, weniger Verkehr, ein angenehmeres Formular. Auf dem Server validieren Sie für Richtigkeit und Sicherheit. Dass sich die Regeln überschneiden, ist keine Verschwendung; es sind zwei verschiedene Aufgaben.
Ist eine Erlaubnisliste besser als eine Verbotsliste?
Nahezu immer. Eine Liste dessen, was nicht erlaubt ist, ist naturgemäß unvollständig: Es gibt stets eine Variante, an die niemand gedacht hat. Eine Liste dessen, was erlaubt ist, schließt alles darüber hinaus aus, auch das Unvorhergesehene. Genau an diesem Unterschied scheitern Filter auf verbotene Zeichen.
Ersetzt Validierung das Maskieren bei der Ausgabe?
Nein, und diese Verwechslung verursacht Schwachstellen. Validierung bestimmt, ob ein Wert bei der Ankunft annehmbar ist; Maskieren sorgt dafür, dass ein Wert in dem Zusammenhang, in dem er landet, sicher verarbeitet wird. Ein gültiger Name wie O'Brien muss zugelassen und zugleich in einer Abfrage korrekt verarbeitet werden.
Wo validiere ich am besten?
So früh wie möglich auf der Serverseite, an einer Stelle je Endpunkt, mit einem Schema, das die Form der gesamten Anfrage beschreibt. Das ist verlässlicher als einzelne Prüfungen, die über den Code verstreut sind, denn dann kann kein Pfad die Validierung umgehen.
Verwandte Artikel
- SchwachstellenCWE-79A03:2021Cross-Site-Scripting (XSS)Cross-Site-Scripting (XSS) schleust schädliche Skripte in Webseiten ein, die im Browser der Besucher laufen. So funktioniert XSS und so verhindern Sie es.
- SchwachstellenCWE-1236A03:2021CSV-Injection in ExportdateienEine exportierte CSV kann Formeln enthalten, die Excel auf dem Rechner Ihrer Kollegen ausführt. Lesen Sie, wie das abläuft und wie Sie es verhindern.
- SchwachstellenCWE-434A04:2021Unbeschränkter Datei-UploadEine Uploadfunktion ohne Beschränkungen kann zur Codeausführung auf Ihrem Server führen. Lesen Sie, welche Prüfungen nötig sind und welche nicht genügen.
- SchwachstellenCWE-915A01:2021Mass AssignmentWer einen ganzen Anfragekörper auf ein Modell bindet, macht isAdmin oder guthaben beschreibbar. Lesen Sie, wie eine Positivliste das verhindert.
- SchwachstellenCWE-89A03:2021SQL InjectionSQL Injection erklärt: wie Angreifer über ungefilterte Eingaben Ihre Datenbank auslesen oder verändern und wie Sie es mit Prepared Statements verhindern.