Skip to content

Cross-Site Scripting #865

@aydinnyunus

Description

@aydinnyunus

Executive Summary

A Cross-Site Scripting (XSS) vulnerability has been identified in the html2pdf.js library. The vulnerability exists due to unsanitized user input being directly assigned to the innerHTML property. This allows attackers to execute arbitrary JavaScript code in the context of the application, potentially leading to session hijacking, data theft, and unauthorized actions.


Vulnerability Details

Affected Component

File: src/worker.js
Line: 71
Link: https://github.com/eKoopmans/html2pdf.js/blob/main/src/worker.js#L71
Function: Worker.prototype.from()

Vulnerable Code

case 'string':  return this.set({ src: createElement('div', {innerHTML: src}) });

Root Cause

When html2pdf() is called with a string parameter, the library directly assigns the user-controlled input to the innerHTML property of a newly created div element without any sanitization. This occurs in the from() method of the Worker class.

Attack Vector

  1. Attacker provides malicious HTML string containing JavaScript payload
  2. html2pdf.js assigns this string to innerHTML (line 71, worker.js)
  3. Element is appended to the DOM (line 125, worker.js)
  4. Browser executes JavaScript when the element is added to the DOM
  5. XSS payload runs in the victim's browser context

Partial Mitigation

The library includes a partial mitigation in src/utils.js (lines 20-23) that removes <script> tags:

if (opt.innerHTML) {
    el.innerHTML = opt.innerHTML;
    var scripts = el.getElementsByTagName('script');
    for (var i = scripts.length; i-- > 0; null) {
        scripts[i].parentNode.removeChild(scripts[i]);
    }
}

However, this mitigation is insufficient as it only removes <script> tags but does not protect against:

  • Event handlers (onerror, onclick, onload, etc.)
  • SVG with embedded scripts
  • <iframe> with JavaScript URLs
  • Other HTML injection vectors

Proof of Concept

PoC 1: Basic XSS (Alert)

Payload:

<img src=x onerror="alert(document.cookie)">

Exploitation Code:

const maliciousHTML = '<img src=x onerror="alert(document.cookie)">';

html2pdf()
    .from(maliciousHTML)
    .set({
        margin: 1,
        filename: 'xss-test.pdf',
        image: { type: 'jpeg', quality: 0.98 },
        html2canvas: { scale: 2 },
        jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' }
    })
    .save();

Result: An alert dialog appears when the PDF generation process begins, confirming XSS execution.

Technical Analysis

Code Flow

  1. Input Reception (src/index.js:20)

    return worker.from(src).save();
  2. String Processing (src/worker.js:71)

    case 'string':  return this.set({ src: createElement('div', {innerHTML: src}) });
  3. DOM Injection (src/worker.js:125)

    document.body.appendChild(this.prop.overlay);
  4. XSS Execution

    • When innerHTML is set, browser parses HTML
    • Event handlers (e.g., onerror) are registered
    • When element is appended to DOM, events fire
    • JavaScript executes in page context

Why Current Mitigation Fails

The script tag removal in src/utils.js is insufficient because:

  1. Event Handlers Bypass: Event handlers like onerror, onclick, onload are not script tags
  2. SVG Vectors: SVG elements can contain embedded JavaScript
  3. Iframe Vectors: Iframes with JavaScript URLs can execute code
  4. Timing: Script removal happens after innerHTML assignment, but event handlers are already registered

Browser Behavior

Modern browsers execute JavaScript when:

  • innerHTML is set with HTML containing event handlers
  • Elements with event handlers are appended to the DOM
  • This occurs before html2canvas processes the element

Remediation

Always Sanitize User Input:

const DOMPurify = require('dompurify');
const sanitizedHTML = DOMPurify.sanitize(userInput);
html2pdf().from(sanitizedHTML).save();

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions