Tech-Stack-Checker

Deine Seite erzählt allen längst, woraus sie gebaut ist.

URL eingeben, und wir benennen den Baukasten, das Framework, die Datenbank, den Auth-Anbieter, das Hosting und den Server, die sie über sich selbst veröffentlicht, und führen dann die vier Prüfungen aus, die erst danach überhaupt Sinn ergeben. Nichts davon stammt aus einer Sondierung: alles steht in der Antwort, die deine Besucher ohnehin bekommen.

Prüfe nur Seiten, die dir gehören oder die du testen darfst. Wir fordern die Seite an, wie es ein Browser täte, und lesen, was zurückkommt. Sieh dir unsere Nutzungsbedingungen.

Was hier geprüft wird, und wie eine gute Antwort aussieht

Was sichtbar ist, und warum überhaupt

Nichts davon ist versteckt. Ein Framework kündigt sich an, weil es muss: Next.js lädt seine Bundles aus /_next/static/, Vite erzeugt einen vorhersehbaren Asset-Pfad, Supabase wird über eine Projekt-URL angesprochen, die in deinem JavaScript stehen muss, damit der Browser sie erreicht, und sehr viele Server schicken immer noch einen Server-Header mit einer Versionsnummer darin. Wir lesen dieselbe Seite und dieselben Skripte wie ein Browser und gleichen das Zurückgekommene mit einer Tabelle von Merkmalen ab. Hier wird kein Pfad geraten, und es gibt keine Anfrage, die deine eigenen Besucher nicht ohnehin stellen.

Ein Entwicklungsserver, der deployt wurde

Der stärkste Befund dieser Seite, und erstaunlich häufig. Der Entwicklungsserver von Vite ist kein Webserver: er liest bei jeder Anfrage aus dem Projektverzeichnis, kompiliert im Vorbeigehen und stellt eine /@fs/-Route bereit, die aus dem Projektverzeichnis heraus greifen kann. Es gibt ihn, damit das Speichern einer Datei deinen Browser aktualisiert, und er wurde nie geschrieben, um im Internet zu stehen. Er landet immer auf dieselbe Weise in der Produktion: jemand startet npm run dev, richtet einen Tunnel oder einen Hoster darauf, um es einer Kollegin zu zeigen, und daraus wird das Deployment. Wenn die Seite /@vite/client lädt, ist genau das passiert.

Ein Entwicklungs-Build, wo der Produktions-Build hingehört

React, Vue und Angular liefern jeweils zwei Builds aus. Der Entwicklungs-Build behält die Warnungen, die Komponentennamen und die Prüfung der Property-Typen; der Produktions-Build entfernt all das. Den Entwicklungs-Build auszuliefern ist kein Loch, durch das ein Angreifer spaziert, aber es ist ein Vielfaches an JavaScript, das die Seite nicht braucht, messbar langsamer in der Ausführung, und es beschreibt jedem, der die Devtools öffnet, deinen Komponentenbaum. Wir stellen diese Frage erst, wenn wir ein Framework erkannt haben, das überhaupt zwei Builds zur Auswahl hat, denn auf einer statischen Seite gibt es kein Entwicklungs-Bundle, und dir zu sagen, du hättest bestanden, hieße dir nichts zu sagen.

Serverdaten, die in der Seite stecken

Next.js schreibt alles, was deine Server-Props zurückgeben, in das HTML jeder Seite, die es rendert, in einen __NEXT_DATA__-Block. So funktioniert das Framework, und das ist kein Fehler. Der Fehler ist der naheliegende daneben: du holst einen Benutzerdatensatz, um einen Namen anzuzeigen, gibst den ganzen Datensatz zurück, und damit stehen E-Mail-Adresse, interne ID und Rolle im Quelltext der Seite, für jeden, der view-source drückt. Wir suchen nach Feldnamen, die nach Datensätzen statt nach Anzeigedaten klingen, und melden ausschließlich die Namen, nie die Werte. Dieser Punkt kann danebenliegen, deshalb verlangen wir zwei davon, bevor wir überhaupt etwas sagen, und formulieren ihn als etwas zum Nachsehen, nicht als etwas zum Reparieren.

Software jenseits ihres Supportzeitraums

Wenn eine Version lesbar ist, gleichen wir sie mit einer kleinen Tabelle von End-of-Life-Daten ab: PHP, Apache, IIS, OpenSSL, jQuery, Bootstrap, AngularJS und Vue 2. Außerhalb des Supports zu sein heißt nicht, dass in die Seite eingebrochen wurde. Es heißt, dass der Grund, aus dem du dich zu Recht sicher fühlen darfst, nämlich dass Korrekturen weiter eintreffen, an einem bestimmten Datum aufgehört hat zu gelten, und dass alles seither Gefundene weiterhin darin steckt. Wir gleichen das mit den Daten ab, die der Hersteller selbst veröffentlicht, du kannst unsere Rechnung also nachprüfen. Was wir bewusst nicht tun, ist eine CVE zu nennen. Dafür bräuchte es eine Schwachstellendatenbank und einen Abgleich von Versionsbereichen, die wir hier nicht haben, und ein Scanner, der konkrete Lücken rät, ist schlechter als einer, der sagt, dass die Version alt ist, und dann aufhört.

Warum nichts davon in den Wert eingeht

WordPress zu erkennen ist kein Befund. PHP, Vercel oder Stripe zu erkennen auch nicht. Etliche Werkzeuge präsentieren eine Technologieliste, als wäre jeder Eintrag ein Anklagepunkt, und das ist der schnellste Weg, einen Bericht unglaubwürdig zu machen: er markiert Dinge, die man nicht beheben kann und die zu beheben sich nicht lohnen würde. Das Stack-Panel dieser Seite kostet dich nichts. Nur die vier Prüfungen oben können einen Befund erzeugen, und nur dann, wenn sie tatsächlich etwas gefunden haben.

So behebst du es

Drei Korrekturen, in der Reihenfolge, in der sie meist gebraucht werden: hör auf, den Dev-Server auszuliefern, hör auf, die Map-Dateien und das Framework-Banner auszuliefern, und hör auf, Versionen anzukündigen, die du noch nicht aktualisiert hast. Die letzte ist für sich genommen kosmetisch und trotzdem sinnvoll, denn sie ist der Unterschied zwischen gezielt getroffen und bei einem Scan übersehen zu werden.

Liefere den Build aus, nicht den Dev-Server
# The dev server is not a web server. It reads from your project
# directory on every request and serves whatever it finds, which is
# why /@fs/ can walk outside the project root at all.

# Wrong, and the single most common way this happens:
npm run dev            # then a tunnel or a host pointed at :5173

# Right:
npm run build          # emits dist/
npm run preview        # serves dist/ locally, to check it first

# Then deploy dist/ as static files. On a host that runs a command
# for you, the build command is 'npm run build' and the publish
# directory is 'dist' (Vite) or 'build' (Create React App).
Source Maps und Framework-Banner abschalten
// vite.config.js  -- source maps off, which is the default but
// worth stating, because a debugging session that flipped it on
// is how most of them end up in production.
export default {
  build: { sourcemap: false },
};

// next.config.js
module.exports = {
  productionBrowserSourceMaps: false,
  // Also drops the "X-Powered-By: Next.js" banner, which is the
  // header that tells a scanner exactly which framework to target.
  poweredByHeader: false,
};
Keine Versionen mehr ankündigen
# nginx: stop announcing the version. server_tokens off leaves
# "nginx" but removes the number, which is the part that tells
# someone which published bugs to try.
server_tokens off;

# Anything proxied from PHP, Express or Rails announces itself too:
proxy_hide_header X-Powered-By;
proxy_hide_header X-AspNet-Version;

# Caddy, equivalent:
#   header {
#       -Server
#       -X-Powered-By
#   }

# Express, at the top of the app:
#   app.disable("x-powered-by");

# This is cosmetic on its own. It does not patch anything, it just
# stops handing over the version. Upgrade as well.
Was dieses Tool nicht sehen kann

Wir können nur benennen, was die Seite veröffentlicht. Ein Stack, der nichts ankündigt, ist für uns unsichtbar, und ein leeres Ergebnis hier heißt, dass wir nichts erkannt haben, nicht dass es nichts zu erkennen gäbe. Wichtiger ist die Gegenrichtung: eine Technologie, die wir benennen, haben wir gesichtet, nicht geprüft. Server: Apache/2.4.41 zu lesen sagt uns, wie die Versionszeile lautet, nicht ob sie gepatcht ist, und Supabase zu erkennen sagt überhaupt nichts darüber, ob dein Row-Level Security eingeschaltet ist, was die Frage ist, die wirklich entscheidet, ob deine Daten lesbar sind. Dafür braucht es den Code. Wenn du die Prüfungen willst, die vom Stack abhängen, und nicht nur den Stack, führt der vollständige Oberflächen-Scan sie alle zusammen aus und bewertet das Ergebnis.

Auch den Code scannenZum Einstieg kostenlos. Repository verbinden, der erste Scan läuft in rund zwei Minuten.