{"id":329,"date":"2015-05-27T18:30:46","date_gmt":"2015-05-28T00:30:46","guid":{"rendered":"http:\/\/www.managerjs.com\/blog\/?p=329"},"modified":"2015-05-27T17:31:34","modified_gmt":"2015-05-27T23:31:34","slug":"will-ejs-escape-save-me-from-xss-sorta","status":"publish","type":"post","link":"https:\/\/www.managerjs.com\/blog\/2015\/05\/will-ejs-escape-save-me-from-xss-sorta\/","title":{"rendered":"Will EJS Escape Save Me From XSS? Sorta"},"content":{"rendered":"<p>If you&#8217;ve never had your website reported for cross-site scripting (XSS) vulnerabilities then you&#8217;re missing out. Of course, it&#8217;s great to get it right the first time. But it&#8217;s hard to beat that sense that you&#8217;re wide open for attack, it&#8217;s your fault, and everyone knows it thanks to some white-hat hacker.<\/p>\n<p>This raises the question how to generally protect against XSS. Of course, there are a lot of ways to screw up. Here&#8217;s one of them.<\/p>\n<h2>Here&#8217;s Your Broken Code<\/h2>\n<p>You have a value on the server (like <code>locale<\/code>) that you want accessible on the client. You realize that you&#8217;re building the whole page in EJS anyway so why not plop a <code>script<\/code> tag on the page and pop a <code>var<\/code> into it? So, we render it right into some JavaScript like this:<\/p>\n<pre>var lang = \"&lt;%- locale %&gt;\";\n<\/pre>\n<p>Here&#8217;s the problem: What if <code>locale<\/code>&#8216;s value is <strong><code>en\"; doEvil(); \"throw away string literal<\/code><\/strong>? Now we render into a JavaScript execution context the following code<\/p>\n<pre>var lang = \"en\"; doEvil(); \"throw away string literal\";<\/pre>\n<p>Which is valid AND EVIL code.<\/p>\n<h2>Does &lt;%= Do the Necessary Escaping? Erm&#8230;<\/h2>\n<p>What if we use the escaping capability of EJS? Are we safe? Sorta.<\/p>\n<p>Let&#8217;s bust out the REPL.<\/p>\n<pre>$ node\n> var ejs = require('ejs')\nundefined\n> var locale = 'en\"; doEvil(); \"throw away string literal'\nundefined\n> ejs.render('var lang = \"&lt;%- locale %&gt;\";')\n'var lang = \"en\"; doEvil(); \"throw away string literal\";'\n> ejs.render('var lang = \"&lt;%= locale %&gt;\";')\n'var lang = \"en&amp;#34;; doEvil(); &amp;#34;throw away string literal\";'<\/pre>\n<p>You see that using the back fat arrow (<code>&lt;%=<\/code>) does prevent the evil from running in this case. <strong>But it isn&#8217;t really a safe technique in general.<\/strong><\/p>\n<p>What if you had a number instead of a string? What if you wanted to do the same thing to it? Continuing in the REPL the sample attack would look like this:<\/p>\n<pre>&gt; var onServer = '6; doEvil();'\nundefined\n> ejs.render('var count = &lt;%= onServer %&gt;')\n'var count = 6; doEvil();'<\/pre>\n<p>Notice that the escaping doesn&#8217;t help because there are no quotes in the attack string. In order for escaping to really work it would have to escape semicolons, too.<\/p>\n<p>So, you&#8217;re kinda safe as long as you are using strings OR at least match the untrusted string with a RegEx like <code>\/[^;'\"]*\/<\/code> and use the matched text instead of the full text.<\/p>\n<h2>My Tools Have Betrayed Me!?<\/h2>\n<p>Why is EJS so broken? Why doesn&#8217;t escaping help you escape?<\/p>\n<p>It isn&#8217;t broken. The problem is that back fat arrow\u00a0is an HTML escape and you are rendering text into a JavaScript execution context.<\/p>\n<blockquote><p><strong>For escaping to be reliable you have to match data context with escaping algorithm.<\/strong><\/p><\/blockquote>\n<p>In this case the context is <em>JavaScript<\/em> and the algorithm is <em>HTML<\/em>. Close. But missed it by <em>that much<\/em>.<\/p>\n<h2>What&#8217;s the Right Way?<\/h2>\n<p>The Right Way\u2122 to do this is to render it into a meta tag like this:<\/p>\n<pre>&lt;meta name=\"lang\" content=\"&lt;%= lang %&gt;\"&gt;<\/pre>\n<p>Notice that here the escaping algorithm (HTML) matches the data context (HTML).<\/p>\n<p>Then you get the value using code like this:<\/p>\n<pre>var metas = document.getElementsByTagName('meta');\nvar i, l = metas.length, lang;\n\nfor (i=0; i &lt; l; ++i) {\n  if (metas[i].getAttribute('name') == 'lang') {\n    lang = metas[i].getAttribute('content');\n  }\n}<\/pre>\n<p>Looking at the Right Way\u2122 it&#8217;s no wonder that we take shortcuts.<\/p>\n<p>But seriously, the Right Way\u2122 is much less XSS error prone.<\/p>\n<p>For other ideas on how to get meta data from the DOM using JavaScript you can always <a href=\"http:\/\/stackoverflow.com\/questions\/7524585\/how-do-i-get-the-information-from-a-meta-tag-with-javascript\">Stack Overflow<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you&#8217;ve never had your website reported for cross-site scripting (XSS) vulnerabilities then you&#8217;re missing out. Of course, it&#8217;s great to get it right the first time. But it&#8217;s hard to beat that sense that you&#8217;re wide open for attack, it&#8217;s your fault, and everyone knows it thanks to some white-hat hacker. This raises the&hellip; <a class=\"more-link\" href=\"https:\/\/www.managerjs.com\/blog\/2015\/05\/will-ejs-escape-save-me-from-xss-sorta\/\">Continue reading <span class=\"screen-reader-text\">Will EJS Escape Save Me From XSS? Sorta<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[28],"tags":[115,116,114],"class_list":["post-329","post","type-post","status-publish","format-standard","hentry","category-developing","tag-ejs","tag-escaping","tag-xss","wow fadeInUp","entry"],"_links":{"self":[{"href":"https:\/\/www.managerjs.com\/blog\/wp-json\/wp\/v2\/posts\/329","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.managerjs.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.managerjs.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.managerjs.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.managerjs.com\/blog\/wp-json\/wp\/v2\/comments?post=329"}],"version-history":[{"count":1,"href":"https:\/\/www.managerjs.com\/blog\/wp-json\/wp\/v2\/posts\/329\/revisions"}],"predecessor-version":[{"id":330,"href":"https:\/\/www.managerjs.com\/blog\/wp-json\/wp\/v2\/posts\/329\/revisions\/330"}],"wp:attachment":[{"href":"https:\/\/www.managerjs.com\/blog\/wp-json\/wp\/v2\/media?parent=329"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.managerjs.com\/blog\/wp-json\/wp\/v2\/categories?post=329"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.managerjs.com\/blog\/wp-json\/wp\/v2\/tags?post=329"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}