Skip to content

Security: Escape HTML in testcase event history to prevent stored XSS#5306

Open
Ashutosh0x wants to merge 1 commit into
google:masterfrom
Ashutosh0x:fix/xss-event-history
Open

Security: Escape HTML in testcase event history to prevent stored XSS#5306
Ashutosh0x wants to merge 1 commit into
google:masterfrom
Ashutosh0x:fix/xss-event-history

Conversation

@Ashutosh0x
Copy link
Copy Markdown

Summary

Fix stored XSS vulnerability in the testcase event history component where attacker-controlled event data (e.g., fuzzer name from testcase uploads) is rendered as HTML without escaping.

Problem

The _formatEvent() method in testcase-event-history.html constructs an HTML string from event data and assigns it via Polymer's inner-h-t-m-l binding. While JSON.stringify() is called on values, the resulting string still contains raw < and > characters that are parsed as HTML when assigned through inner-h-t-m-l.

Attack vector: A user with testcase upload permissions injects a payload like <img src=x onerror='...'> in the fuzzer form field. This value flows through:

  1. Upload handler → testcase.fuzzer_name (stored in datastore)
  2. BaseTestcaseEvent.__post_init__()event.fuzzer (copied from testcase)
  3. _format_event_for_history() → event dict (returned to frontend)
  4. _formatEvent() → HTML string (rendered via inner-h-t-m-l)

When any user views the testcase detail page, the stored payload executes in their browser session, enabling cross-user data theft.

Fix

Add an _escapeHtml() helper that replaces &, <, >, ", and ' with their HTML entity equivalents. Both event keys and JSON-stringified values are escaped before interpolation into the HTML string.

+      _escapeHtml(value) {
+        const replacements = {
+          '&': '&amp;',
+          '<': '&lt;',
+          '>': '&gt;',
+          '"': '&quot;',
+          "'": '&#39;',
+        };
+        return String(value).replace(/[&<>"']/g, char => replacements[char]);
+      },
       _formatEvent(event) {
         ...
-          const value = JSON.stringify(eventCopy[key]);
+          const escapedKey = this._escapeHtml(key);
+          const escapedValue = this._escapeHtml(JSON.stringify(eventCopy[key]));
           if (boldKeys.includes(key)) {
-            return `<b>"${key}"</b>: <b>${value}</b>`;
+            return `<b>"${escapedKey}"</b>: <b>${escapedValue}</b>`;
           }
-          return `"${key}": ${value}`;
+          return `"${escapedKey}": ${escapedValue}`;

Impact

A limited ClusterFuzz user who can upload testcases can execute arbitrary JavaScript in the browser session of any user (including admins) who views the uploaded testcase's detail page. The demonstrated impact includes reading data from other testcases that the attacker cannot directly access.

Fixes #5257

The _formatEvent() method in testcase-event-history.html interpolates
event data (including user-controlled fields like fuzzer name) into HTML
strings assigned via inner-h-t-m-l without escaping. An attacker with
testcase upload permissions can inject arbitrary HTML/JavaScript via the
fuzzer name field, which executes when any user views the testcase detail
page.

Add _escapeHtml() helper to escape &, <, >, quote, and apostrophe
characters before interpolating event keys and JSON-stringified values
into the HTML string.

Fixes google#5257
@Ashutosh0x Ashutosh0x requested a review from a team as a code owner June 3, 2026 04:39
@Ashutosh0x
Copy link
Copy Markdown
Author

This PR fixes the stored XSS vulnerability reported in #5257. The fix adds HTML entity escaping to the _formatEvent() method so that attacker-controlled event data (like fuzzer names from testcase uploads) cannot inject executable HTML/JavaScript via the inner-h-t-m-l binding.

The patch is minimal (14 lines added, 3 changed) and follows the exact approach suggested in the issue report. Happy to address any feedback!

@Ashutosh0x
Copy link
Copy Markdown
Author

Hi @jonathanmetzman — this fixes a stored XSS in the testcase event history view (issue #5257).

The _formatEvent() function in testcase-event-history.html interpolates event data — including fuzzer names and other user-influenced fields — into the DOM without HTML escaping. An attacker who uploads a testcase with a crafted fuzzer name can execute JavaScript in the browser of any user viewing that testcase.

The fix adds an _escapeHtml() sanitization function that encodes special characters before DOM insertion. Applied to all dynamic data rendered in the event history template.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security: Stored XSS in ClusterFuzz testcase event history via uploaded fuzzer name

1 participant