Willow SoftwareEntwicklungWebsicherheit

I found some AJAX code from 2013. 2026 security had opinions.

A Google Drive clean-out uncovered a tiny 2013 PHP and jQuery AJAX demo. In 2026 it was promptly blocked as an XSS risk, so here is the amusing before-and-after modernisation.

Veröffentlicht 2 Min. LesezeitGraeme Moignard
Zum Lesen scrollen

Entwicklung • Websicherheit • Code-Archäologie

Ich fand AJAX-Code von 2013. Die Sicherheit von 2026 hatte dazu eine Meinung.

Geschrieben von Graeme Moignard

Veröffentlicht:

Zuletzt aktualisiert:

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.

Auf einen BlickEin praktischer SicherheitsablaufEine einfache Ansicht des in diesem Beitrag beschriebenen Ablaufs.
01Anfrage
02Prüfen
03Schützen
04Dokumentieren

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.

Ursprüngliche ajax.phpPHP · 2013
<?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();
?>
Ursprünglicher Browser-CodejQuery · 2013
$('#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:

POST

1. Dem POST vertrauen

field1 kommt vom Browser und wird ohne Validierung oder Ausgabe-Escaping verwendet.

PHP

2. HTML daraus bauen

PHP verkettet den Wert in eine <li>-Zeichenkette und sendet sie als JSON zurück.

.html()

3. Das Ergebnis ausführen

jQuery .html() weist den Browser an, die zurückgegebene Zeichenkette als Markup statt als reinen Text zu behandeln.

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)

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.

Moderne ajax.phpPHP · 2026
<?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);
Moderner Browser-CodeJavaScript · 2026
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

2013: eine 93-KB-jQuery-Datei einbinden, um einen AJAX-Aufruf zu machen, dem zurückgegebenen Markup vertrauen und hoffen, dass sich alle benehmen.

VS
2026

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.

Das Ergebnis erscheint hier ohne Neuladen der Seite.

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.

Modernes PHP- + Fetch-Beispiel herunterladen (.zip)

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.

Weitere Beiträge von Willow Software

Im vollständigen Blog finden Sie Neuigkeiten zu Salix Monitor 360, Sicherheitshinweise und Artikel zur Webentwicklung.

Blogübersicht öffnen