HeyForm: Stored XSS in Formularfeldern über getarnte `javascript:`-URL

Ein registrierter Nutzer oder Projektmitglied hinterlegt über eine getarnte javascript:-URL in einem Formularfeld ein Skript, das im Kontext des Workspace-Eigentümers läuft und dessen Sitzung übernimmt.

Advisory-ID: TP-2026-074
Produkt: HeyForm (Open-Source-Formularbaukasten)
Schwachstellentyp: Stored Cross-Site-Scripting (CWE-79)
CVE: nicht beantragt
CVSS 3.1: 8.7 (Hoch) · CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N
Betroffene Versionen: <= 3.0.0-rc.8
Status: Vom Hersteller veröffentlicht; keine Fix-Version benannt
Hersteller-Advisory: GHSA-8q89-27mx-fvg6
Gemeldet: 11. Juni 2026

Zusammenfassung

HeyForm ist ein Open-Source-Formularbaukasten. Der Formular-Renderer serialisiert title und description eines Feldes zu rohem HTML und gibt es über dangerouslySetInnerHTML sowohl an anonyme Formular-Ausfüllende als auch an angemeldete Bearbeitende aus. Der Allowlist-Sanitizer für <a href> weist javascript: nur ab, wenn das Schema am Stück am Anfang steht; ein Tabulator zwischen java und script passiert Server- wie Client-Sanitizer unverändert, und der Browser stellt beim Klick die lebende javascript:-URL wieder her. Ein registrierter Nutzer oder ein Projektmitglied hinterlegt so ein Skript, das im authentifizierten, gleichseitigen Kontext des Workspace-Eigentümers läuft und über das Sitzungscookie zur vollständigen Workspace-Übernahme führt. turingpoint hat den Ablauf verifiziert und verantwortungsvoll gemeldet.

Ursache

Der Formular-Renderer serialisiert das gespeicherte Rich-Text-Schema eines Feldes zu einem HTML-String (packages/form-renderer/src/utils/form.ts:119 für title, :127 für description) und injiziert es per dangerouslySetInnerHTML (packages/form-renderer/src/blocks/Block.tsx:364 und :370). Denselben FormRenderer rendern die anonyme öffentliche Formularseite (PublicFormResolver.publicForm, packages/server/src/resolver/form/form-detail.resolver.ts:43-48, ohne @Auth) und die authentifizierte Builder-Vorschau (packages/webapp/src/pages/form/Builder/PreviewModal/index.tsx:62). Server- wie Client-Sanitizer prüfen <a href> mit UNSAFE_URL_PROTOCOL_REGEX = /^\s*(?:javascript|vbscript|data):/i (packages/server/src/utils/form-schema.ts:29 und :53), das nur ein zusammenhängendes Schema trifft. Ein Tabulator zwischen java und script passiert beide Prüfungen, escapeAttribute lässt den Tabulator stehen, und dangerouslySetInnerHTML umgeht Reacts JSX-href-Sanitizer. Da die anonyme Selbstregistrierung im Standard offen ist und keine Antwort eine CSP trägt, genügt jeder registrierte Nutzer oder ein gering privilegiertes Projektmitglied als Angreifer.

Proof of Concept

Schematisch, als title-Schema eines Feldes über den anonymen publishForm:

[["a",["CLICK_ME"],{"href":"java<TAB>script:<payload>"}]]

-> die öffentliche Formularseite /form/<id> liefert den Tabulator roh aus;
   beim Klick normalisiert der Browser a.href zurück zu javascript:<payload>
   und führt es im Sitzungskontext des Betrachters aus.

Der Tabulator bricht das vom Sanitizer erwartete zusammenhängende Schema auf, wird aber vom Browser beim Auflösen des Links wieder entfernt, sodass eine lebende javascript:-URL entsteht.

Auswirkung

  • Skriptausführung im authentifizierten, gleichseitigen Kontext des Workspace-Eigentümers beim Öffnen der Formular-Vorschau.
  • Übernahme der Sitzung über das HttpOnly-Sitzungscookie und Zugriff auf die GraphQL-API bis zur vollständigen Workspace-Übernahme.
  • Ausführung auch im Browser anonymer Ausfüllender der öffentlichen Formularseite.

Referenzen

Steckt so etwas in Ihrer Software?

Diese Schwachstelle hat unser Team im Rahmen seiner Arbeit gefunden. Lassen Sie Ihre Anwendungen von denselben Spezialisten prüfen, mit einem Penetrationstest von turingpoint.