Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Einreichen
ToolsExploitsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

FeedsKontaktDatenschutz© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
Instatic-Stored-XSS-CVE-2026-103931 — Proof-of-Concept und Analyse eines gespeicherten XSS im isSafeUrl()-URL-Filter von Instatic, bei dem führende C0-Steuerzeichen die Blockierung des javascript:-Schemas umgehen. | Kitploit
Tools/GitHubGitHub/overgrowncarrot1/instatic-stored-xss-cve-2026-103931
Statische AnalyseSchwachstellenanalyseCode-AnalyseExploitationWebanwendungs-ExploitationWebsicherheit
GitHubovergrowncarrot1/instatic-stored-xss-cve-2026-103931

Instatic-Stored-XSS-CVE-2026-103931

Proof-of-Concept und Analyse eines gespeicherten XSS im isSafeUrl()-URL-Filter von Instatic, bei dem führende C0-Steuerzeichen die Blockierung des javascript:-Schemas umgehen.

Repository anzeigen
vor 1 TagNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

URL-Schema-Filter-Bypass in isSafeUrl() über führende C0-Steuerzeichen

Betroffen: Instatic v0.0.13 / Commit 63ad5d6 (und alle früheren Revisionen, die src/core/html-sanitize/index.ts enthalten) Komponente: src/core/html-sanitize/index.ts → isSafeUrl() / safeUrl() Klasse: CWE-79 (Stored XSS) über CWE-184 (Incomplete List of Disallowed Input)

Zusammenfassung

isSafeUrl() ist der einzige Engpass, der javascript:, vbscript: und data: URLs im gesamten Publisher blockiert. Es normalisiert die Eingabe mit .replace(/[\t\n\r]/g, '').trim(), bevor es das Schema-Präfix prüft.

Der WHATWG-URL-Parser entfernt alle führenden C0-Steuerzeichen (U+0000–U+001F) und Leerzeichen, bevor er ein Schema liest. JavaScripts String.prototype.trim() entfernt nur U+0009, U+000A, U+000B, U+000C, U+000D, U+0020 und Unicode-Leerzeichen — es lässt U+0000–U+0008 und U+000E–U+001F unangetastet.

Eine URL mit z. B. U+0001 als Präfix wird vom Guard also als sicher gemeldet, während jeder Browser sie als javascript:-Schema parst und ausführt.

Proof of Concept

Gegen die unveränderte src/core/html-sanitize/index.ts:

payload           : "\x01javascript:alert(document.domain)"
isSafeUrl()       : true          <-- Guard meldet "sicher"
WHATWG URL scheme : javascript:   <-- was der Browser tatsächlich ausführt
safeUrl() output  : "\x01javascript:alert(document.domain)"   (NICHT zu "#" kollabiert)

27 der 32 C0-Steuerzeichen umgehen die Prüfung. U+0000 wird durch HTML-Attribut-Parsing neutralisiert (NUL → U+FFFD), sodass 26 zuverlässig ausnutzbare Präfixe verbleiben (U+0001–U+0008, U+000E–U+001F). Derselbe Bypass umgeht auch die vbscript:- und data:-Filter.

Die bestehende Testsuite (src/__tests__/publisher/utils.test.ts) deckt Case-Folding und eingebettete Tabs (java\tscript:) ab, aber niemals ein führendes Steuerzeichen — deshalb wurde dies nicht erkannt.

End-to-End-Pfad

base.link deklariert href: { type: 'url' } und LinkPropsSchema typisiert es als uneingeschränktes Type.String({ default: '#' }) — es gibt keine URL-Validierung zur Schreibzeit. Daher:

  1. escapeProps() leitet Props vom Typ type: 'url' | 'image' | 'media' an isSafeUrl(value) ? value : '#' weiter — die Payload passiert roh und bewusst un-HTML-escaped.
  2. render() von base.link gibt `<a href="https://github.com/overgrowncarrot1/instatic-stored-xss-cve-2026-103931/blob/main/%24%7BsafeUrl%28props.href%29%7D" …>` aus. safeUrl() prüft erneut mit demselben defekten isSafeUrl(), dann escapeHtml() — was nur & < > " ' escaped und Steuerzeichen nicht anfasst.
  3. Die Payload landet wörtlich im href-Attribut: <a href="https://github.com/overgrowncarrot1/instatic-stored-xss-cve-2026-103931/blob/main/%5Cx01javascript%3Aalert%28document.domain%29" target="_self">Click me</a>

Betroffene Sinks

All diese laufen durch dasselbe isSafeUrl():

SinkDatei
Jede url / image / media-Modul-Prop (Link href, Button href, Image src, Video src/poster, Form action, Form redirectUrl)src/core/publisher/escapeProps.ts:108
Beliebige benutzerdefinierte HTML-Attribute auf jedem Nodesrc/core/htmlAttributes/attributes.ts:66
Markdown-Link/Image href und srcsrc/core/markdown/renderMarkdown.ts:75
Site faviconUrlsrc/core/publisher/render.ts:322
Der safeUrl()-Aufruf jedes Basismodulssrc/modules/base/utils/escape.ts

Auswirkung

Rechteausweitung von einem niedrig privilegierten Editor zur vollständigen Admin-Kompromittierung.

Die eingebaute Client-Rolle besitzt site.content.edit, was ausreicht, um ein Link-href oder ein benutzerdefiniertes HTML-Attribut zu setzen. Die injizierte URL wird dann im Admin-Editor-Canvas gerendert, einem srcdoc-iframe — same-origin mit /admin.

server/securityHeaders.ts:69-72 setzt auf /admin nur frame-ancestors 'none'; base-uri 'self'; object-src 'none', mit einem expliziten Code-Kommentar, dass eine script-src-Policy bewusst noch nicht gesetzt ist. Ohne script-src hindert nichts eine javascript:-URL daran, auf der Admin-Origin ausgeführt zu werden.

Wenn ein Owner oder Admin die betroffene Seite im Editor öffnet und das Element aktiviert, läuft die Payload same-origin mit der Admin-SPA. Das Session-Cookie ist HttpOnly, kann also nicht direkt gelesen werden — aber die Payload kann die Admin-API als Opfer steuern (ein Owner-Konto anlegen, Secrets lesen oder ein Plugin installieren, dessen Server-Einstiegspunkt ein Pfad zur Codeausführung ist).

Auf der veröffentlichten Seite ist die Auswirkung geringer: cspPlan.ts setzt script-src 'none' (oder 'self'), was javascript:-URLs blockiert. Allerdings lockert server/publish/frontendInjections.ts:377 dies zu 'self' 'unsafe-inline' für jede Seite mit einem Inline-Script, und 'unsafe-inline' erlaubt javascript:-URLs wieder — auf diesen Seiten ist also XSS auf der veröffentlichten Seite erreichbar.

Beachten Sie, dass der Docstring von attributes.ts bereits genau dieses Bedrohungsmodell benennt ("on the published site AND, more seriously, inside the admin editor canvas (same-origin as /admin)") — der Guard implementiert es schlicht nicht vollständig.

Verifikation

Im Browser bestätigt (Chromium, srcdoc-iframes, die die CSP jeder Origin reproduzieren, und die byte-genaue Ausgabe des repo-eigenen safeUrl() rendern):

Reproduzierte OriginAngewandte CSPErgebnis
Admin-Editor-Canvas (/admin)keine — wie securityHeaders.ts sie ausgibtjavascript: ausgeführt
Veröffentlichte Seite (Baseline)script-src 'none' — wie cspPlan.ts sie ausgibtblockiert (script-src-elem)
Veröffentlichte Seite mit Inline-Scriptscript-src 'self' 'unsafe-inline' — wie frontendInjections.ts:377 sie ausgibtsiehe Hinweis

Die ersten beiden Zeilen sind beobachtete Ergebnisse. Die Ausführung auf der Admin-Origin meldete document.domain als die ausliefernde Origin, was Same-Origin-Ausführung statt eines opaken Kontexts bestätigt.

Einschränkung zur Genauigkeit: Das Harness wendet jede Policy über <meta http-equiv> an, während Instatic sie als HTTP-Response-Header sendet. Diese sind für die script-src-Durchsetzung äquivalent, aber eine Reproduktion auf einer laufenden bun run dev-Instanz würde die echten Header tragen.

Fix

Den vollständigen C0- + Leerzeichen-Bereich entfernen, statt sich auf trim() zu verlassen. Genau das tut Reacts eigene isJavaScriptProtocol-Regex mit ihrem ^[\u0000-\u001F ]*-Präfix — eine nützliche Gegenprobe, dass dies eine bekannte, reale Bypass-Klasse ist und keine theoretische.

--- a/src/core/html-sanitize/index.ts
+++ b/src/core/html-sanitize/index.ts
@@ -30,7 +30,11 @@ export function escapeHtml(value: unknown): string {
   * normalisation browsers apply during URL parsing.
   */
  export function isSafeUrl(url: string): boolean {
-  const normalized = url.replace(/[\t\n\r]/g, '').trim().toLowerCase()
+  const normalized = String(url ?? '')
+    .replace(/[\t\n\r]/g, '')
+    .replace(/^[\u0000-\u0020]+/, '')
+    .replace(/[\u0000-\u0020]+$/, '')
+    .toLowerCase()
    return (
      !normalized.startsWith('javascript:') &&
      !normalized.startsWith('vbscript:') &&

Gegen die gepatchte Datei verifiziert: Die Payload wird blockiert, safeUrl() kollabiert sie zu #, 0 von 99 gefährlichen URLs mit Steuer-/Leerzeichen-Präfix werden weiterhin akzeptiert, und alle 14 bestehenden isSafeUrl-Testfälle verhalten sich identisch.

Vorgeschlagener Regressionstest

it('blocks javascript: behind a leading C0 control character', () => {
  for (let i = 0x00; i <= 0x1f; i++) {
    expect(isSafeUrl(String.fromCharCode(i) + 'javascript:alert(1)')).toBe(false)
  }
})

Defense in Depth

Tool herunterladen