-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Component: /app/app/javascript/components/TiptapExtensions/MediaEmbed.tsx
Vulnerability: Potential DOM-based Cross-Site Scripting (XSS) via unsanitized HTML from iframe.ly.
Source: User-controlled URL input provided to the media embed dialog (EmbedMediaForm component, triggered from the Tiptap editor menu).
Data Flow:
- User Input: A user clicks "Insert video" or "Insert post" in the Tiptap editor and enters a URL into the
EmbedMediaFormmodal. - External API Call: The frontend takes the user's URL, encodes it, and sends it to the external
iframe.lyservice API:https://iframe.ly/api/oembed?iframe=1&api_key=...&url=<encoded_user_url>(SeeMediaEmbed.tsx:146). - HTML Received: The application receives a JSON response from
iframe.ly. This response is expected to contain anhtmlproperty (data.html) which holds the embeddable HTML code generated byiframe.lybased on the user's URL (SeeMediaEmbed.tsx:148-149). - Internal Processing: The
insertMediaEmbedhelper function (MediaEmbed.tsx:185) receives thisdataobject containingdata.html.- For certain supported providers (
MEDIA_EMBED_SUPPORTING_PROVIDERS), it attempts to parsedata.htmland extract only theouterHTMLof an<iframe>tag (MediaEmbed.tsx:187-189). - Fallback: If no
<iframe>is found withindata.htmlfor a supported provider, it falls back to using the originaldata.html. - Unsupported Provider: If the provider is not in the supported list, it directly uses the raw
data.htmlwhen calling thesetRawcommand (MediaEmbed.tsx:195).
- For certain supported providers (
- Tiptap Command: The processed (or raw)
htmlstring is passed as an attribute to either theinsertMediaEmbedTiptap command (MediaEmbed.tsx:190) or thesetRawcommand (MediaEmbed.tsx:195). - Node Attribute Update: These commands update the Tiptap editor's state, creating/updating a
mediaEmbedorrawnode and storing the received HTML string in the node's attributes (node.attrs.htmlorHTMLAttributes.html). - Sink 1 (
dangerouslySetInnerHTML): TheExternalMediaFileEmbedReact component renders themediaEmbednode. It usesdangerouslySetInnerHTMLto render the preview, directly injecting the stored HTML:<div className="preview" dangerouslySetInnerHTML={{ __html: cast(node.attrs.html) }}></div>(MediaEmbed.tsx:246). - Sink 2 (
innerHTML): TheRawnode renderer (used ifsetRawwas called) also uses aninnerHTMLsink:doc.innerHTML = cast(HTMLAttributes.html);(MediaEmbed.tsx:67).
Risk:
The application relies on the external iframe.ly service to provide safe, embeddable HTML. If an attacker can provide a crafted URL that causes iframe.ly to return HTML containing malicious code (e.g., <script>, onerror attributes, etc.), and this HTML bypasses the simple iframe extraction logic (or uses the setRaw path), it will be stored and subsequently rendered directly into the DOM via dangerouslySetInnerHTML or innerHTML. This executes the malicious script in the context of the user's session.
Hypothetical Reproduction Steps:
(Note: Successful exploitation depends on finding a way to make iframe.ly return malicious HTML for a given URL. This might require exploiting iframe.ly itself or finding specific edge cases it handles insecurely.)
- Navigate to a page using the rich text editor (e.g., creating/editing a product description or post).
- Click the "Insert video" or "Insert post" button in the editor toolbar.
- In the URL input field, enter a URL crafted to exploit
iframe.ly. Examples (purely illustrative, likely blocked byiframe.ly):- A
data:URL:data:text/html,<svg onload=alert(document.domain)> - A URL pointing to a page with malicious OpenGraph/oEmbed tags that
iframe.lymight misinterpret. - Any URL for which
iframe.lyis known (or discovered) to return unsanitized script content.
- A
- Click "Insert".
- If the crafted URL successfully tricks
iframe.lyinto returning malicious HTML, and that HTML reaches thedangerouslySetInnerHTMLorinnerHTMLsink, the script should execute.
Recommendation:
- Avoid Direct Rendering: Do not directly render HTML received from external services using
dangerouslySetInnerHTMLorinnerHTML, especially when the input driving the external service is user-controlled. - Sanitization: If rendering external HTML is unavoidable, rigorously sanitize it after receiving it from
iframe.lyand before passing it to the sink. Use a well-vetted sanitization library (like DOMPurify) configured appropriately. - Sandboxing: Consider rendering the embed within a sandboxed
<iframe>(<iframe sandbox>) to isolate it from the main application context, significantly reducing the impact of potential XSS within the embed.