Preventing XSS and CSRF

Jeremy Stashewsky, GoInstant




Security ∈ Trust

Vulnerabilties ∉ Trust

Prevention  >Repair

This talk is an introduction to two common web vulnerabilities

XSS (Cross Site Scripting)

CSRF (Cross Site Request Forgery)

And how to prevent (or fix) them.


Cross Site Scripting

XSS is an injection attack, driven by user-controlled inputs

[bad data flow]

Potentially, a user can place arbitrary




on to your page!

An example

  <h1>Hello <%- %>, welcome to <%- %></h1>


Where <%- %> is an Interpolation operator for a Template Slot.

What happens if someone updates my profile and changes my name from




  <h1>Hello <%- %>, welcome to <%- %></h1>

... is rendered as ...

  <h1>Hello </h1>
  welcome to My Awesome Site</h1>


A Three-Part Approach to Preventing XSS

  1. Validate Input
  2. Sanitize Output
  3. Enable Content-Security-Policy

[anti-xss data flow]


Step 1: Validation

Best case: Compare against an Allow List of known-good values


  var HANDEDNESS = ['Lefty','Righty','Ambidexterous','Other'];

The Validation Conundrum

Not everything can be Validated against an Allow List

Human names don't fit into a convenient list :(

Instead, you might say "anything but <>" to at least exclude HTML tags.


Step 2: Sanitization

(a.k.a. filtering, normalizing, or escaping)

Goal: Prevent user-controlled data from breaking out of its context.

Means: Convert unsafe markup to safe markup.

HTML Entity-encoding takes markup characters and turns them into display characters.

Minimal list of HTML Entity Encodings

"&quot; or &#34;
&&amp; or &#38;

Exhaustive List of HTML Entity Encodings

(Insert all 65536 JavaScript UTF-16 code-points here)

Basically, entity-encode characters not in this RegExp set:

[\t\n\v\f\r ,\.0-9A-Z_a-z\-\u00A0-\uFFFF]

source: secure-filters

Sanitizing the example (EJS)

Change ...

  <h1>Hello <%- %>, welcome to <%- %></h1>


... to ...

  <h1>Hello <%= %>, welcome to <%- %></h1>


Where <%= %> is an Escaping operator for a Template Slot.

This changes the bad output from...

  <h1>Hello </h1>
  welcome to My Awesome Site</h1>


... to the safe (entity-encoded) ...

  <h1>Hello &lt;/h1&gt;
  welcome to My Awesome Site</h1>

So... I just have to worry about escaping HTML?


There's more to it than HTML entity-encoding!

[secure filters contextual filtering]

JavaScript Variable Attack

    var foo = <%- someJSON %>;


  { someJSON: JSON.stringify("</script><script>alert('boom');//") }


    var foo = "</script><script>alert('boom');//";

Sanitizing JavaScript Literals

In strings, things like < become \x3C, etc.

    var foo = "</script><script>alert('boom');//";

... becomes ...

    var foo = "\x3C/script\x3E\x3Cscript\x3Ealert('boom');//";

JavaScript sanitization doesn't save you from innerHTML

    var userName = "Jeremy\x3Cscript\x3Ealert('boom')\x3C/script\x3E";
    element.innerHTML = "<span>"+userName+"</span>";

Query Param Attack

  <a href="/show?user=<%= userId %>">...</a>;


  { userId: "42&user=666" }


  <a href="/show?user=42&amp;user=666">...</a>;

The server sees, so maybe shows user 666 now?

Sanitizing via URI-escaping

Convert unsafe characters to %XX UTF-8 octets.

E.g. & to %26

  <a href="/show?user=42%26user=666">...</a>;

Luckily, parseInt("42&user=666") evaluates to 42.

Are there any tools to help me with Sanitization?


JavaScript: secure-filters

Works in node.js and browsers, includes EJS support

    var config = <%-: config |jsObj%>;
    var userId = parseInt('<%-: userId |js%>',10);
  <a href="/welcome/<%-: userId |uri%>">Welcome <%-: userName |html%></a>
  <a href="javascript:activate('<%-: userId |jsAttr%>')">
    Click here to activate</a>

Can use these as regular functions too

PHP: Phalcon\Escaper

Good selection of output filters

  <title><?php echo $e->escapeHtml($maliciousTitle) ?></title>

  <style type="text/css">
  .<?php echo $e->escapeCss($className) ?> {
      font-family  : "<?php echo $e->escapeCss($fontName) ?>";

  <div class='<?php echo $e->escapeHtmlAttr($className) ?>'>hello</div>

  <script>var some = '<?php echo $e->escapeJs($javascriptText) ?>'</script>


Strict Contextual Escaping$sce

The {{ }} operator and ng- attributes are context-aware!

React & JSX

DOM manipulation macros are available without JSX:

  var link = React.DOM.a({href: ''}, 'React');

Or, conveniently in JSX:

  var link = <a href="">React</a>;

Java: OWASP Enterprise Security API


Has APIs for escaping output, as well as input-validation helpers, anti-CSRF and more.

Go html/template

Based on EcmaScript Harmony "Quasis" (a.k.a. Tagged Template Strings)

  <a href="/search?q={{.}}">{{.}}</a>

... is compiled to mean ...

  <a href="/search?q={{. | urlquery}}">{{. | html}}</a>
Should I sanitize inputs?


Why not to Sanitize Input

Sanitizing input permanently modifies the data.

Sanitization is fairly cheap and highly cacheable!


Step 3: Content-Security-Policy

Validation can't cover everything...

... and Sanitization can't catch all the cases...

(but you should still do them!)

... we needed something more!

How to CSP

Pages define an Allow-List of what features (and their Origins) are permissible.

Serve as a HTTP header (or use a <meta> HTML tag)

      default-src 'none';
      frame-src 'self';

Remember this?

  <h1>Hello </h1>
  welcome to My Awesome Site</h1>


It could have been prevented with restricting scripts sourced from the same Origin:

  Content-Security-Policy: script-src 'self'

With script-src 'self', all unknown script sources are also blocked:

  <!-- allowed by CSP: -->
  <script src="/main.js"></script>
  <!-- blocked by CSP: -->
  <script src=""></script>

Consequently, to allow inline script blocks, instead of ...

  Content-Security-Policy: script-src 'self'

... we'd need to say ...

  Content-Security-Policy: script-src 'self', 'unsafe-inline'
Are there any tools to help me with CSP?


Connect middleware that does CSP and more!

    var helmet = require('helmet');
    var app = express(); // or connect

Neat tool using Report-Only mode to dynamically help you form a valid CSP header.

Just be aware it does send a list of all included scripts/fonts/etc to do that analysis

XSS Prevention In Summary

  1. Validate your inputs
  2. Sanitize your outputs
  3. Enable CSP on your web-server


Cross-Site Request Forgery

CSRF exploits the fact that you are logged-in to some other site.

For example,

  • You're logged into
  • You accidentally click a link to

Say has the following HTML:

  <title>Welcome to</title>
  <script src=""></script>

Even though you're visiting,

you're still authenticated with!

How do we fix this?

In Human terms:

Assert that the user intended to do this action.


The user was on my website ...

... then, they clicked submit on a form ...

... therefore, this isn't a Cross-Site Forgery

In Technical terms:

"The user was on my website ..."


Put into any Forms a unique, secret Anti-CSRF token that's tied to their login-cookie.

"... then they clicked submit on a form ..."


Actions that change application state should be POST/PUT/PATCH/DELETE

(consistent with the REST Architectural Style)

Note: that POST/etc. on its own is not enough to stop CSRF based on XHR!

"... therefore, this isn't a Cross-Site Forgery"


Validate the Anti-CSRF token, which since it was a secret, the attacker can't know.

Note: HTTP isn't very good at keeping secrets, so consider the importance of HTTPS.


Assume it's running a simple Express 3.x node.js server with EJS templates.

Express Routes

  app.get('/api/inviteAdmin', handlerFn);

... change this to ...'/api/inviteAdmin', handlerFn);

Connect Anti-CSRF middleware


... then to access the token ...

  var token = req.csrfToken();
  res.render('template', { _csrf: token });

Change the EJS template

  <form method="GET" action="/api/inviteAdmin">
    <input type="email" name="email">

... change to use POST and consume anti-CSRF token ...

  <form method="POST" action="/api/inviteAdmin">
    <input type="hidden" name="_csrf" value="<%- _csrf %>">
    <input type="email" name="email">
Are there ways to verify intent without a CSRF token?

Some intent verification ideas

Idea: ask the user for confirmation (just make sure the confirmation isn't CSRF-attackable and is server-controlled)

Idea: For really sensitive operations, re-prompting for a password is good, especially for long-lived sessions

CSRF summary

You too can prevent forest CSRF fires!

  1. Verify intent: did the user do this action?
  2. Be a good REST citizen: Use POST/PUT/etc. instead of GET.
  3. Use Anti-CSRF tokens: ties together presence on the site and intent.

In Conclusion



  1. Validate Inputs (or be Radical)
  2. Sanitize Outputs
  3. Use Content-Security-Policy


  1. Verify user intent
  2. Be a good REST citizen
  3. Use Anti-CSRF tokens


  • Slides are at
  • Thanks to my employer GoInstant for sponsoring this talk. We make Real-time, Backend-as-a-Service web APIs, and are very serious about Security.