Skip to content

Stored XSS via Table Header ID Attribute Injection #1046

@vc-smoore

Description

@vc-smoore

I noticed that when table header IDs are enabled, the raw header text
gets dropped straight into an id="" attribute with almost no escaping.
In src/subParsers/makehtml/tables.js, the parseHeaders function does
this:

id = ' id="' + header.replace(/ /g, '_').toLowerCase() + '"';

That's it. Spaces become underscores, everything gets lowercased, and
then it's concatenated into the attribute. No HTML encoding at all.
Double quotes, angle brackets, everything passes through.

So if the table header contains a " character, you break out of the
id attribute. And since < and > pass through too, you can inject any
HTML you want after the breakout.

This matters because tablesHeaderId is enabled by default in the
"github" flavor, which is what most people use:

converter.setFlavor('github');  // sets tablesHeaderId: true

PoC — put this markdown through showdown with github flavor:

| "><svg/onload=alert(1)> | Col2 |

Showdown produces:

<th id=""><svg/onload=alert(1)>">...

The browser sees:

  1. — th with empty id
  2. <svg/onload=alert(1)> — SVG executes the JS

The /onload trick works because HTML5 parsers treat / after a tag name
as a transition to the "self-closing start tag" state, which on a
non-void character falls back to "before attribute name" — so
onload=alert(1) becomes a valid attribute.

You don't need spaces at all in the payload, which is convenient since
spaces get replaced with underscores.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions