Google-Drive-Archäologie, offenbar
Beim Aufräumen in Google Drive fand ich ein winziges PHP-und-AJAX-Beispiel, das ich 2013 geschrieben hatte. Natürlich musste ich es sofort wieder ausführen.
Das moderne Web sah es sich kurz an und sagte sinngemäß: auf keinen Fall. Die Anfrage wurde als Cross-Site-Scripting-Risiko blockiert. Etwas unhöflich, aber technisch durchaus berechtigt.
Das Beste daran: Der Graeme von 2013 hatte das Problem bereits in einem Kommentar erkannt und war dann weitergezogen, ohne es tatsächlich zu beheben.
Die Version von 2013: jQuery, $.ajax() und Optimismus
Für ihre Zeit war das ein völlig normales kleines Beispiel: jQuery 1.10.1, ein POST an ajax.php, JSON zurück zum Browser und die Antwort direkt in die Seite. Das Problem: Der vom Benutzer gelieferte Wert wurde direkt in HTML verkettet und anschließend mit .html() eingefügt.
<?php
class ajaxValidate {
function formValidate() {
//Put form elements into post variables (this is where you would sanitize your data)
$field1 = @$_POST['field1'];
//Establish values that will be returned via ajax
$return = array();
$return['msg'] = '';
$return['error'] = false;
//Begin form validation functionality
if (!isset($field1) || empty($field1)){
$return['error'] = true;
$return['msg'] .= '<li>Error: Field1 is empty.</li>';
}
//Begin form success functionality
if ($return['error'] === false){
$return['msg'] = '<li>Success Message - You typed in '.$field1.'</li>';
}
//Return json encoded results
return json_encode($return);
}
}
$ajaxValidate = new ajaxValidate;
echo $ajaxValidate->formValidate();
?>
$('#form1').submit(function(event) {
event.preventDefault();
$.ajax({
type: 'POST',
url: 'ajax.php',
data: $(this).serialize(),
dataType: 'json',
success: function (data) {
console.log(data);
$('#info1').html(data.msg);
}
});
});
Warum 2026 Einspruch erhob
Die verwundbare Kette ist herrlich kurz:
1. Dem POST vertrauen
field1 kommt vom Browser und wird ohne Validierung oder Ausgabe-Escaping verwendet.
2. HTML daraus bauen
PHP verkettet den Wert in eine <li>-Zeichenkette und sendet sie als JSON zurück.
3. Das Ergebnis ausführen
jQuery .html() weist den Browser an, die zurückgegebene Zeichenkette als Markup statt als reinen Text zu behandeln.
Die Version von 2026: weniger Abhängigkeiten, sicherere Standards
Das modernisierte Beispiel erledigt denselben kleinen Job, aber die Annahmen sind völlig anders. Keine jQuery-Abhängigkeit. Der Browser verwendet fetch() und FormData. PHP validiert Anfrage und Eingabe, liefert sinnvolle HTTP-Statuscodes und klares JSON, und der Browser rendert die Nachricht mit textContent, statt sie als HTML zu interpretieren.
<?php
declare(strict_types=1);
header('Content-Type: application/json; charset=utf-8');
header('X-Content-Type-Options: nosniff');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
header('Allow: POST');
echo json_encode([
'error' => true,
'message' => 'Method not allowed.',
], JSON_THROW_ON_ERROR);
exit;
}
$field1 = trim((string)($_POST['field1'] ?? ''));
if ($field1 === '') {
http_response_code(422);
echo json_encode([
'error' => true,
'message' => 'Field1 is empty.',
], JSON_THROW_ON_ERROR);
exit;
}
if (preg_match_all('/./us', $field1, $characters) === false) {
http_response_code(422);
echo json_encode([
'error' => true,
'message' => 'Field1 must be valid UTF-8.',
], JSON_THROW_ON_ERROR);
exit;
}
if (count($characters[0]) > 500) {
http_response_code(422);
echo json_encode([
'error' => true,
'message' => 'Field1 must be 500 characters or fewer.',
], JSON_THROW_ON_ERROR);
exit;
}
echo json_encode([
'error' => false,
'message' => 'Success: you typed: ' . $field1,
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
const form = document.getElementById('example-form');
const message = document.getElementById('message');
form.addEventListener('submit', async (event) => {
event.preventDefault();
message.textContent = 'Sending…';
message.className = '';
try {
const response = await fetch('ajax.php', {
method: 'POST',
headers: {
'Accept': 'application/json'
},
body: new FormData(form)
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || 'The request failed.');
}
// textContent is intentional: user input is rendered as text, not HTML.
message.textContent = data.message;
message.className = data.error ? 'error' : 'success';
} catch (error) {
message.textContent = error instanceof Error
? error.message
: 'An unexpected error occurred.';
message.className = 'error';
}
});
Dreizehn Jahre in einem winzigen Beispiel
2013: eine 93-KB-jQuery-Datei einbinden, um einen AJAX-Aufruf zu machen, dem zurückgegebenen Markup vertrauen und hoffen, dass sich alle benehmen.
2026: native Browser-APIs verwenden, an den Grenzen validieren, Daten als Daten behandeln und unsicheres HTML-Rendering zu einer bewussten Entscheidung statt zum Standard machen.
Der hochwissenschaftliche Test
Gib die klassische Payload unten in die moderne Version ein. Sie sollte wortwörtlich als Text erscheinen. Wenn ein Dialog aufpoppt, ist etwas gründlich schiefgelaufen.
<img src=x onerror=alert(1)>Die 2026-Version direkt hier ausprobieren
Diese Version läuft auf dem Willow-Software-Server, daher muss nichts installiert werden. Geben Sie unten etwas ein; die Seite sendet es mit fetch() an PHP, erhält JSON zurück und aktualisiert das Ergebnis ohne Neuladen.
Möchtest du das moderne Beispiel?
Laden Sie den Quellcode herunter, wenn Sie ihn ausprobieren möchten. Er besteht absichtlich nur aus zwei Dateien: index.html und ajax.php. Da das Beispiel PHP verwendet, benötigen Sie lokal installiertes und konfiguriertes PHP oder einen anderen PHP-fähigen Webserver, um die heruntergeladene Version auszuführen. Das direkte Öffnen von index.html als file:// führt PHP nicht aus.
Es ist erstaunlich befriedigend, alten Code wie diesen wiederzufinden. Es ist nicht wirklich “schlechter Code aus der Vergangenheit”; es ist ein kleines Fossil, das zeigt, was damals üblich, bequem und akzeptabel war. Das Web änderte sich, Browser änderten sich, Sicherheitsanforderungen änderten sich und zum Glück änderte sich auch der Code.
Und falls du dir jemals den Kommentar “this is where you would sanitize your data” hinterlässt: Vielleicht erledigst du es besser vor 2039.
Und dann fand ich diesen Kommentar…
Ja. Im Quellcode steht wörtlich: “this is where you would sanitize your data”. Offenbar war das eine Aufgabe für Zukunfts-Graeme. Zukunfts-Graeme traf dreizehn Jahre später ein.
//Put form elements into post variables (this is where you would sanitize your data)