Nichts läuft, wenn du eine Seite aufrufst

Diese Woche hat das PHP-Projekt Fixes für PHP2Shell veröffentlicht, und laut Advisory kann der Angriff „before any application code runs“ zünden1. Was das für PHP bedeutet, habe ich in PHP: Neunundzwanzig Jahre mit offener Tür aufgeschrieben. Dieser Beitrag handelt von der anderen Hälfte der Antwort. Was, wenn gar nichts läuft?

So funktioniert dieser Blog. Als du diese Seite aufgerufen hast, ist kein Programm von mir gestartet, hat keine Datenbank gelesen, keinen Login geprüft und kein HTML gebaut. Eine Datei, die schon existierte, wurde zurückgeschickt. Das ist alles.

So sieht dieser Unterschied aus, gemessen am selben Tag, an einem frisch im Labor installierten Standard-WordPress und an dieser Site, so wie sie jetzt live ist:

Standard-WordPress 7.1.3Diese Site
REST-API-Routen1330
REST-Routen, die Schreibzugriffe annehmen620
XML-RPC-Methoden800
Dateien, die auf dem Server ausgeführt werden1.514 PHP0
Dateien, die der ausliefernde Benutzer schreiben kann3.785 von 3.785keine: es läuft nichts, das schreiben könnte
Datenbank12 Tabellen, App-Konto mit allen Rechtenkeine
Login-Seitewp-login.php, livekeine; der Pfad liefert 404
Ein POST, PUT oder DELETE an die StartseitePHP läuft und rendert die Seite trotzdeman der Edge mit 405 abgewiesen; nichts läuft

Keine dieser Nullen ist eine Einstellung, die ich abgeschaltet habe. Es sind Nullen, weil das, was sie zählen, hier nicht existiert. Eine Login-Seite, die nie gebaut wurde, kannst du nicht angreifen.

Was ein Standard-WordPress ins Internet stellt

Ich wollte das nicht aus dem Gedächtnis behaupten, also habe ich es gemessen. Die offiziellen Container-Images wordpress und mariadb, in Podman auf meinem eigenen Rechner gestartet und an localhost gebunden, installiert ohne irgendeine Ergänzung: keine Plugins über das Mitgelieferte hinaus, das Standard-Theme, ein Administrator. WordPress 7.1.3 auf PHP 8.3.36. Dann habe ich gezählt, was alles antwortet.

Was auf eine Anfrage antwortet: WordPress gegen eine statische SiteEine WordPress-AnfrageEine Anfrage an diese Sitedas InternetWebserverPHP führt Code aus, bei jeder AnfrageWordPress-Core: 1.514 PHP-Dateienplus jedes Plugin und Theme133 REST-Routen, 80 XML-RPC-Methodenalle Dateien vom selben User beschreibbarDatenbank, ALL PRIVILEGESLogin-Seite, Admin, Cron, Uploadsdas InternetCloudflare-Edgeeine Datei wird zurückgeschickt6.343 Dateien, vorab gebautkein Code von uns läuftPOST, PUT, DELETE: 405nichts auf dem Server zum Beschreibenkeine Datenbankkein Login, kein Admin, kein Cron
Was auf eine Anfrage antwortet. Bei WordPress läuft bei jedem Seitenaufruf PHP durch Core, Plugins und eine Datenbank, auf Dateien, die derselbe Benutzer umschreiben kann. Hier wird eine fertige Datei zurückgeschickt, und nichts läuft.

Die REST-API ist die größte einzelne Fläche. Fünf Namespaces, 133 Routen, 257 Kombinationen aus Route und Methode, und 62 Routen, die irgendeine Art von Schreibzugriff annehmen. Sie kam mit WordPress 4.7 in den Core, „with REST API endpoints for posts, comments, terms, users, meta, and settings“2. Und abschalten sollst du sie nicht: „You should not disable the REST API; doing so will break WordPress Admin functionality that depends on the API being active.“3 Also bleibt sie an.

Was standardmäßig offenliegtWas WordPress’ eigene Dokumentation sagt
XML-RPC, 80 Methodenseit 3.5 standardmäßig an: „Enable XML-RPC by default, remove Writing Settings option“4
die REST-API, 133 Routen„content that is public on your site is generally publicly accessible via the REST API“5
die BenutzerlisteGET /wp/v2/users liefert in der Standardansicht Anzeigename, Slug, Link und Avatar jedes Autors6
wp-login.php und xmlrpc.php„xmlrpc.php is a frequent brute‑force target“; „Apply rate limits to /wp-login.php and /xmlrpc.php globally.“7
wp-cron.php„WP-Cron works by checking, on every page load, a list of scheduled tasks to see what needs to be run.“8
der Theme- und Plugin-Editor„This is often the first tool an attacker will use if able to login, since it allows code execution.“9

Die Benutzerliste verdient einen zweiten Blick. In meinem Labor hat eine anonyme Anfrage an diesen Endpunkt das eine Administratorkonto zurückgegeben, mit Anzeigename und Slug. Jemand, der sich nie angemeldet hat, erfährt, welche Konten die Site hat, bevor er irgendetwas versucht hat, auf einer frischen Installation, so gewollt. Niemand musste etwas falsch konfigurieren.

Dann die Dateien. Das offizielle Image betreibt Apache als www-data, und www-data gehört das ganze Web-Root. Alle 3.785 Dateien, darunter die 1.514 PHP-Dateien, sind von demselben Benutzer beschreibbar, der den Code ausführt. Jede Datei. Der Code eingeschlossen. wp-config.php, mit dem Datenbankpasswort darin, liegt im Web-Root mit Modus 644. Das Datenbankkonto, das WordPress benutzt, hat ALL PRIVILEGES auf seiner Datenbank.

Das ist das Ein-Prozess-Modell aus dem PHP-Beitrag, live und unverändert, aus dem offiziellen Image. Das ist keine schlecht betriebene Site. Es ist der Ausgangspunkt, von dem jede WordPress-Site startet, bevor das erste Plugin dazukommt.

Und von den Plugins kommt das meiste. Patchstack hat 2025 im WordPress-Ökosystem 11.334 neue Schwachstellen gezählt. „91% of new vulnerabilities were found in plugins, and 9% were found in themes. There were only 6 vulnerabilities reported in the WordPress core.“10 Fast die Hälfte hatte keinen Fix, als sie öffentlich wurde, und bei denen, die Angreifer haben wollten, lag der Median bis zur Massenausnutzung bei fünf Stunden10.

Was diese Site ins Internet stellt

Dieser Blog besteht aus 6.343 Dateien. Hier ist alles, nach Art:

DateiartAnzahlWas es ist
.html3.486die Seiten, in fünf Sprachen
.xml1.576RSS-Feeds und Sitemaps
.svg1.181Diagramme, Cover, Icons
.png46Screenshots
.json10der Suchindex und die Build-Stempel
.js4kleine Skripte, die in deinem Browser laufen, nicht auf dem Server
.css, Schriften und der Rest40Styling und an Beiträge angehängte Dateien
Auf dem Server ausgeführt0

Ein paar .py- und .sh-Dateien sind dabei. Das sind Skripte, die an Beiträgen hängen, damit Leser sie herunterladen können, und der Server schickt sie als Text. Nichts auf dem Server führt sie aus. Auf dem Server gibt es schlicht nichts, womit man sie ausführen könnte.

Ausgeliefert wird sie bei Cloudflare als statische Assets, ganz ohne Worker-Skript. Die Deploy-Konfiguration hat keinen Einstiegspunkt, und Cloudflares Dokumentation sagt, was das heißt: „The main key is optional for assets-only Workers.“11 Wenn ein angefragter Pfad zu einer Datei passt, „that file will be served“, ohne dass Worker-Code aufgerufen wird, und „If no Worker script is present, a 404 Not Found response is returned.“12 Kosten tut es auch nichts: „Requests to static assets are free and unlimited.“13

Cloudflare ist eine Wahl, keine Voraussetzung. Die Ausgabe ist ein Ordner mit Dateien, und jeder HTTPS-Server, der eine Datei ausliefern kann, kann sie bedienen: nginx, Caddy, ein Apache, der nur statische Dateien ausliefert, oder ein Object-Storage-Bucket hinter einem CDN. Die Links sind relativ, also läuft derselbe Build unverändert unter einer Domain-Wurzel oder in einem Unterverzeichnis; geprüft, indem die gebaute Seite unter /preview/ ausgeliefert wurde und alle 592 lokalen Verweise auf sechs Seiten aufgelöst wurden. Den Host zu wechseln heißt, einen Ordner zu kopieren. Das einzige Cloudflare-spezifische Stück ist die _headers-Datei; anderswo gehören dieselben Header in die Konfiguration des Servers. Was mitwandert, ist die Eigenschaft, auf die es ankommt. Was die Seite ausliefert, muss nur Dateien senden, also nie deinen Code ausführen.

Die Live-Site antwortet also so:

$ for m in POST PUT DELETE; do curl -s -o /dev/null -w "$m %{http_code}\n" -X $m https://blogs.damiendye.uk/en/; done
POST 405
PUT 405
DELETE 405

$ for p in wp-login.php xmlrpc.php wp-admin/ wp-json/; do curl -s -o /dev/null -w "%{http_code} $p\n" https://blogs.damiendye.uk/$p; done
404 wp-login.php
404 xmlrpc.php
404 wp-admin/
404 wp-json/

Nichts antwortet. Genau so ist es gebaut.

Die Security-Header gehen mit jeder Datei raus, aus einer reinen Textdatei _headers im Repository, die Cloudflare beim Deploy liest14. Die Content Security Policy bindet jedes Skript, jeden Style, jedes Bild, jede Schrift und jede Verbindung an diesen einen Origin. Bei der Lücke darin bin ich ehrlich: script-src und style-src tragen noch 'unsafe-inline', weil das Theme ein paar kleine Skripte direkt in die Seite schreibt. Das ist die offene Aufgabe an dieser Datei, und sie steht genau so im Repository.

Und Veröffentlichen braucht keine Tür. Bei WordPress ist Veröffentlichen ein Schreibzugriff über HTTP: Editor, REST-API und XML-RPC gibt es, weil Beiträge so auf den Server kommen, und jeder davon ist ein Weg hinein. Hier schreibe ich einen Beitrag als Textdatei und pushe ihn in ein Git-Repository, und der Build lädt fertige Dateien über die Deploy-API von Cloudflare hoch. Nichts, was die Seite ausliefert, nimmt einen Schreibzugriff aus dem Web an, weil nichts einen braucht. Das Repository ist außerdem privat. Frag GitHub ohne meine Zugangsdaten danach, und die Antwort ist ein 404. Quellcode, Entwürfe und Historie liegen also auch nicht im Internet.

Angriffsfläche ist eine Zahl, kein Gefühl

OWASP definiert die Angriffsfläche als „the sum of all paths for data/commands into and out of the application, and the code that protects these paths“15 und sagt, man verkleinert sie, indem man „turning off features and interfaces that aren’t being used“15. Das britische National Cyber Security Centre sagt es direkter: „If would-be attackers can’t reach an interface, they can’t attack it.“16 Sein Cyber-Essentials-Programm macht daraus eine Anforderung: „remove or disable unnecessary software (including applications, system utilities and network services)“17.

Also zähl die Wege, nach dem, was sie sind, nicht danach, wie viele es sind:

Weg hinein oder hinausWordPressStatische Site
Login und Passwortratenwp-login.php, XML-RPCkeiner
Admin-Oberfläche/wp-admin/, mit Code-Editorkeine
programmierbare APIREST und XML-RPC, mit Schreibzugriffkeine
serverseitiger Code pro AnfrageCore, Theme, jedes Pluginkeiner
Datenbankja, von allem oben erreichbarkeine
beschreibbarer Speicher auf dem ServerUploads, und im Standard-Image alleskeiner
von Besuchern ausgelöste geplante Jobswp-cron.php beim Seitenaufrufkeine
Fremdcode, den du gepatcht halten musstjedes Plugin und Theme, auf dem Serverdie Build-Werkzeuge, abseits des Servers
was eine gestohlene Session einem Angreifer bringtdie Sitenichts, wo man sich anmelden könnte

Neun Zeilen. Keine davon führt Code auf dem Server aus. Jedes Mal aus demselben Grund. Die Arbeit war erledigt, bevor irgendjemand eine Seite angefragt hat.

Die vorletzte Zeile ist der ehrliche Haken, und um den geht es im Rest dieses Beitrags. Auch eine statische Site führt Code aus. Nur woanders, einmal, vor dem Veröffentlichen.

Wie Hugo den Build sauber hält

Hugo ist „a static site generator written in Go“18, das Programm, das baut, ist also selbst speichersicher und kompiliert, und nach dem PHP-Beitrag zählt das. Seine Sicherheitsseite zieht die Linie genau dort, wo dieser Beitrag sie zieht: Die Ausgabe „runs directly in the browser“, aber „during development and site building, the hugo executable itself is the runtime environment.“19 Und zum Ausliefern ganz klar: „We have no online production server“19. Gut.

Innerhalb des Builds ist standardmäßig alles verriegelt. „Hugo’s built-in security policy, which restricts access to os/exec, remote communication, and similar operations, is configured via allowlists. By default, access is restricted.“20

Hugos StandardWas er verhindert
„No arbitrary commands“19ein Template kann keine Shell auf dem Rechner aufrufen
exec.allow begrenzt auf Sass, Go, Git, Node und PostCSS20nur benannte Werkzeuge, mit festen Flags
funcs.getenv begrenzt auf ^HUGO_ und ^CI$20Templates können deine Geheimnisse nicht aus der Umgebung lesen
Remote-Abrufe verweigern IP-Adressen und localhost als Host20ein Template kann nicht in dein eigenes Netz greifen
Module gegen go.sum geprüft19eine manipulierte Abhängigkeit lässt den Build scheitern

Diese Site weitet eine davon mit Absicht auf. Sie nimmt die CI-Commit-Variablen in funcs.getenv auf, damit der Build-Stempel unter /en/version.json festhalten kann, welcher Commit die Live-Site gebaut hat. Das ist eine getroffene und aufgeschriebene Entscheidung, und genau dafür ist eine Allowlist da. Insofern ist es kein Loch. Es ist eine Entscheidung.

Wohin das Risiko wandert: in den Build

Nimm den Server weg, und das Risiko verschwindet nicht. Es wandert zu dem, was die Dateien baut. Das SLSA-Framework benennt die beiden Bedrohungen genau: eine „build integrity threat“, bei der ein Angreifer „behavior to an artifact without changing its source code“ einschleust, und eine „dependency threat“, durch „compromising some other artifact that the former depends on at build time“21.

Wo Code läuft: WordPress, diese Site und ein Offline-BuildWO CODE LÄUFTWAS DIE ÖFFENTLICHKEIT ERREICHTWordPressder öffentliche Server, bei jeder AnfragePHP, Plugins, DatenbankDiese SiteCloudflares Build-Container, pro Pushnur statische DateienOffline gebautein Rechner, der dir gehört, einmalnur statische Dateiender Build vertraut weiter Hugo, dem Theme und seinem Rechner
Wo in jedem Modell Code läuft. WordPress führt ihn auf dem öffentlichen Server für jeden Besucher aus. Diese Site führt ihn einmal pro Push in Cloudflares Build-Container aus. Offline gebaut, läuft er einmal auf einem Rechner, der dir gehört.

Diese Site steht in der mittleren Zeile, und ich werde nicht so tun, als wäre es anders. Sie wird bei jedem Push ins Repository in Cloudflares Build-Container gebaut. Dieser Container ist nicht der Server, der dir antwortet, und er ist weg, sobald der Build fertig ist. Aber für die Dauer des Builds wird ihm alles anvertraut, was am Ende auf der Site landet.

Und der Build hatte zwei Lücken, beide habe ich beim Schreiben dieses Beitrags gefunden. Eine ist behoben. Eine ist eine Entscheidung.

LückeWarum sie zähltStatus
dem Hugo-Binary wurde blind vertraut: der eigene Download des Skripts hatte keine Prüfsummenkontrolle, und in der CI übersprang es den Download sowieso und nahm die Kopie, die Cloudflares Build-Image installiert hatteein ausgetauschtes Binary würde bauen, was es willbehoben, 23. Oktober 2026: der Build lädt Hugo immer selbst herunter, prüft es gegen die veröffentlichte SHA-256 des Releases22 und verweigert den Build bei Abweichung
das Theme folgt bei jedem Build seinem Upstream-Branch mastereine Änderung upstream kann die Live-Site ohne einen Commit hier verändernoffen, mit Absicht; der Fix ist, es auf einen Commit festzunageln oder in ein Hugo-Modul zu verlegen, das go.sum abdeckt19

Die erste ist von der Sorte, die du nur findest, wenn du dein eigenes Build-Skript Zeile für Zeile liest. Das Skript hat die Version geprüft. Aber ein Versionsstring ist etwas, das ein Binary über sich selbst sagt. Über die Bytes beweist er nichts. Jetzt werden die Bytes geprüft:

Fetching Hugo Extended 0.167.0...
Hugo verified: sha256 0163f5c3deddac1f494a1629ddc40c65d18de9d5794facd98f7f96ac2c7d8957

Und eine Kopie desselben Tarballs mit einem angehängten Byte wird abgelehnt:

Refusing to build: hugo_extended_0.167.0_linux-amd64.tar.gz does not match the release checksum
  expected 0163f5c3deddac1f494a1629ddc40c65d18de9d5794facd98f7f96ac2c7d8957
  got      92682d765d9d34bc53d6f818d56e598d34b5b8267a1cc3b9971da0f53c792251

Das Theme ist ein bewusster Tausch, Aktualität gegen Reproduzierbarkeit, und es ist als solcher aufgeschrieben. Aber es ist genau die Dependency-Bedrohung, die SLSA beschreibt, und ehrlich ist, das auch zu sagen. Keine der beiden Lücken hat die Live-Site je einem Besucher ausgesetzt. Beide waren Vertrauen in den Build, und ich zähle sie lieber selbst auf, als dass ein Leser sie findet.

Selbst die Prüfsumme hat eine Grenze. Sie beweist, dass die heruntergeladene Datei die Datei auf der Release-Seite ist. Sie beweist nicht, wer sie dort abgelegt hat: Unter den Dateien des Releases gibt es keine Signaturen22. Das ist Integrität, nicht Authentizität, und mehr bietet das Projekt heute nicht.

Offline bauen, einmal

Die strengste Variante holt den Build von den Rechnern anderer Leute. Bau auf einem Rechner, der dir gehört und den du kontrollierst, prüf, was du gebaut hast, und lad nur die fertigen Dateien hoch. Nichts im Internet führt jemals deinen Build aus, und der Hoster sieht nie etwas anderes als die Ausgabe.

SchrittWarum
das exakte Hugo-Release installieren und gegen die veröffentlichte SHA-256 prüfendu weißt, welches Binary deine Site gebaut hat
Theme und Module auf Commits festnageln und go.sum committender Build ändert sich nur, wenn du ihn änderst
in ein sauberes Ausgabeverzeichnis bauen, ohne --buildDraftswas du veröffentlichst, ist das, was du veröffentlichen wolltest
vor dem Hochladen in der Ausgabe nach allem Ausführbaren sucheneine statische Site sollte nichts enthalten, was der Server ausführen würde
nur das Ausgabeverzeichnis hochladen, mit einem reinen Deploy-Zugangder Hoster hat nie deinen Quellcode, deine Werkzeuge oder deine Historie
von einem Hoster ausliefern, der für statische Dateien keinen Code ausführtbeim Ausliefern bleibt nichts zu patchen

Das sind ein paar Befehle, kein Projekt. Und es ändert, wo ein Angreifer sein muss, um dir zu schaden. Um eine WordPress-Site zu verändern, braucht er eine Anfrage an den Server. Um diese Art Site zu verändern, braucht er deinen Build-Rechner. Das ist ein viel kleineres Ziel, und es ist eins, dem du den Stecker ziehen kannst.

Zehntausend Seiten, gemessen

Die Angriffsfläche ist ein Preis dafür, bei jeder Anfrage Code laufen zu lassen. Der Strom ist ein anderer, der Plattenplatz noch einer. Also habe ich beide unter dieselbe Last gesetzt: 10.000 Anfragen an die Startseite, zehn gleichzeitig, vom selben Laptop, über einfaches HTTP auf localhost, damit keine Seite für TLS bezahlt. Auf der einen Seite das Standard-WordPress aus dem Labor. Auf der anderen die echten gebauten Dateien dieser Seite, ausgeliefert von nginx in einem Container, denn an die Edge von Cloudflare kann ich kein Messgerät hängen. Die CPU-Zeit jedes Containers habe ich vor und nach jedem Lauf aus seiner cgroup gelesen, drei Läufe pro Seite.

10.000 SeitenanfragenStandard-WordPress 7.1.3Diese Seite auf nginx
CPU-Zeit, drei Läufe409 s, 530 s, 575 s0,15 s, 0,18 s, 0,19 s
CPU-Zeit pro Seite41 bis 57 ms15 bis 19 µs
Seiten pro Sekunde168 bis 23623.679 bis 28.044
Speicher unter Last302 bis 310 MiB, plus 145 MiB für die Datenbank15 MiB
Fehlgeschlagene Anfragen00

Das ist das Zwei- bis Dreieinhalbtausendfache an Prozessorzeit für dieselben zehntausend Seiten. Jede dieser CPU-Sekunden kam aus der Steckdose und wurde zu Wärme. Die statische Seite hat alle 10.000 in ungefähr der Prozessorzeit ausgeliefert, die WordPress für drei oder vier seiner Seiten brauchte.

Die Platte erzählt dieselbe Geschichte:

Auf der Platte und im SpeicherStandard-WordPressDiese Seite
Container-Images zum Betrieb803 MB WordPress, 334 MB MariaDB166 MB nginx, oder gar nichts bei Cloudflare
Die Seite selbst115 MB Webroot, für eine leere Seite114 MB, fünf Sprachen, jeder Beitrag und jedes Diagramm
Datenbank163 MB Dateien mit 0,72 MB Datenkeine
Speicher im Leerlauf170 MiB, plus 139 MiB für die Datenbank13 MiB

Die WordPress-Zahlen sind eine frische Installation mit einem Beispielbeitrag. Die statischen Zahlen sind dieses ganze Blog.

Fairerweise: Ein Plugin für einen vollständigen Seiten-Cache würde einen Großteil des CPU-Abstands schließen. Aber schau dir an, was diese Lösung ist. Es ist eine statische Kopie jeder Seite, vor das PHP gestellt, damit das PHP nicht läuft. Das Heilmittel ist genau das, wofür dieser Beitrag argumentiert, seitlich angeschraubt an das, wogegen er argumentiert, mit Login-Seite, API und Datenbank, die alle noch dahinter stehen.

Was du aufgibst

Die ehrliche Liste, denn eine statische Site ist nicht für alles das richtige Werkzeug:

Du gibst aufWas diese Site stattdessen macht
Kommentarekeine; Leser können mailen
serverseitige Sucheein JSON-Index, mit der Site gebaut und in deinem Browser durchsucht
Formulare, die an den Server schickenkeine
Logins und Mitgliederbereichekeine; hier ist alles öffentlich
Bearbeiten im BrowserBeiträge sind Textdateien in einem Git-Repository
sofortiges Veröffentlichen ohne Buildein Build dauert ein oder zwei Minuten

Wenn du einen Shop, einen Mitgliederbereich oder Benutzerkonten brauchst, brauchst du etwas, das auf einem Server läuft, und dieser Beitrag erzählt dir nichts anderes. Was er dir erzählt: Ein Blog, eine Firmenbroschüre im Netz oder eine Dokumentation braucht nichts davon. Die meisten WordPress-Sites sind eins von diesen dreien. Sei ehrlich, welches deine ist.

Einmal bauen und tot ausliefern

Was mich stört, ist, wie viel Risiko ohne jeden Grund mitgeschleppt wird. Eine Firmenseite, die sich zweimal im Jahr ändert, und dahinter laufen eine Datenbank, eine Login-Seite, eine API mit 62 beschreibbaren Routen und ein Job-Runner, der jedes Mal feuert, wenn jemand vorbeischaut. Alles dafür da, dasselbe HTML zu erzeugen wie gestern. Jeden Tag.

Das ist keine technische Entscheidung. Es ist eine Gewohnheit, und es ist eine Gewohnheit, die jemand in Rechnung stellt. Hosting, Plugins, Sicherheits-Add-ons, das Aufräumen, wenn es schiefgeht. Der Preis ist ein Managed-WordPress-Tarif. Was es kostet, ist jeder dieser Wege, offen gehalten, gepatcht und überwacht, für Inhalte, die ein Ordner voller Dateien hätten sein können.

Der Maßstab, an dem ich diese Site messe, ist einfach. Nichts läuft, was nicht laufen muss. Jede Lücke steht da, wo der Nächste sie findet, auch die beiden im Build, und eine davon ist schon geschlossen. Mit Cleverness hat das nichts zu tun. Ein Standard ist das, was du einhältst, wenn keiner nachsieht, und bei einem Blog sieht keiner nach, bis zu dem Tag, an dem er den Code von jemand anderem ausliefert.

Also bau es einmal, prüf, was du gebaut hast, und liefer es tot aus. Was dann noch anzugreifen bleibt, steht auf deinem Rechner, und da gehört es hin.


  1. PHP — News archive 2026, 21st October — „We consider the risk highest for mod_php, with a demonstrated exploit where an unauthenticated request can trigger the attack before any application code runs“. ↩︎

  2. WordPress 4.7 release notes — „WordPress 4.7 comes with REST API endpoints for posts, comments, terms, users, meta, and settings.“ ↩︎

  3. WordPress REST API Handbook — Frequently Asked Questions — „You should not disable the REST API; doing so will break WordPress Admin functionality that depends on the API being active.“ ↩︎

  4. WordPress 3.5 release notes — „Enable XML-RPC by default, remove Writing Settings option“. ↩︎

  5. WordPress REST API Handbook — „content that is public on your site is generally publicly accessible via the REST API“. ↩︎

  6. WordPress REST API reference — Users — GET /wp/v2/users, Standardkontext „view“; name, slug, link, avatar_urls im View-Kontext. ↩︎

  7. WordPress — Brute force attacks — „xmlrpc.php is a frequent brute‑force target“; „Apply rate limits to /wp-login.php and /xmlrpc.php globally.“ ↩︎

  8. WordPress Plugin Handbook — Cron — „WP-Cron works by checking, on every page load, a list of scheduled tasks to see what needs to be run.“ ↩︎

  9. WordPress — Hardening WordPress — der Dashboard-Editor „is often the first tool an attacker will use if able to login, since it allows code execution.“ ↩︎

  10. Patchstack — State of WordPress Security in 2026 — Daten für 2025: 11.334 neue Schwachstellen; 91 % Plugins, 9 % Themes, 6 im Core; 46 % nicht rechtzeitig zur Offenlegung behoben; Median 5 Stunden bis zur Massenausnutzung. ↩︎ ↩︎

  11. Cloudflare — Wrangler configuration — „The main key is optional for assets-only Workers.“ ↩︎

  12. Cloudflare — Workers static assets — eine passende Datei „will be served“, ohne Worker-Code aufzurufen; „If no Worker script is present, a 404 Not Found response is returned.“ ↩︎

  13. Cloudflare — Static assets billing and limitations — „Requests to static assets are free and unlimited.“ ↩︎

  14. Cloudflare — Static assets headers — Header, gesetzt über „a plain text file called _headers“. ↩︎

  15. OWASP — Attack Surface Analysis Cheat Sheet — „the sum of all paths for data/commands into and out of the application, and the code that protects these paths“; verkleinern durch „turning off features and interfaces that aren’t being used“. ↩︎ ↩︎

  16. NCSC — Making compromise difficult, 2.2 Reduce attack surface — „If would-be attackers can’t reach an interface, they can’t attack it.“ ↩︎

  17. NCSC — Cyber Essentials: Requirements for IT infrastructure v3.2 — „remove or disable unnecessary software (including applications, system utilities and network services)“. ↩︎

  18. Hugo — Introduction — „Hugo is a static site generator written in Go, optimized for speed and designed for flexibility.“ ↩︎

  19. Hugo — Security model — „during development and site building, the hugo executable itself is the runtime environment“; „We have no online production server“; „No arbitrary commands“; eine abweichende go.sum-Prüfsumme lässt den Build scheitern. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  20. Hugo — Configure security — „By default, access is restricted“; Standard-Allowlists für exec, getenv und http. ↩︎ ↩︎ ↩︎ ↩︎

  21. SLSA v1.2 — Threats — Build-Integritätsbedrohung: „behavior to an artifact without changing its source code“; Dependency-Bedrohung: „compromising some other artifact that the former depends on at build time“. ↩︎

  22. Hugo v0.167.0 release — 36 Assets, darunter hugo_0.167.0_checksums.txt (SHA-256); keine Signatur-, SBOM- oder Attestation-Dateien. ↩︎ ↩︎