Fixes #322 - Autoescaping doesen't work in <script> tag context

This commit is contained in:
Patrick Steele-Idem 2016-07-01 10:54:11 -06:00
parent a0af1495e3
commit 4f79a8042e
10 changed files with 77 additions and 5 deletions

View File

@ -185,7 +185,7 @@ class Generator {
var beforeAfterEvent;
if (node.listenerCount('beforeGenerateCode') || node.listenerCount('beforeGenerateCode')) {
if (node.listenerCount('beforeGenerateCode') || node.listenerCount('afterGenerateCode')) {
beforeAfterEvent = new GeneratorEvent(node, this);
}

View File

@ -80,7 +80,7 @@ class CompileContext {
}
setFlag(name) {
this._flags[name] = true;
this.pushFlag(name);
}
clearFlag(name) {
@ -91,6 +91,24 @@ class CompileContext {
return this._flags.hasOwnProperty(name);
}
pushFlag(name) {
if (this._flags.hasOwnProperty(name)) {
this._flags[name]++;
} else {
this._flags[name] = 1;
}
}
popFlag(name) {
if (!this._flags.hasOwnProperty(name)) {
throw new Error('popFlag() called for "' + name + '" when flag was not set');
}
if (--this._flags[name] === 0) {
delete this._flags[name];
}
}
addError(errorInfo) {
if (errorInfo instanceof Node) {
let node = arguments[0];

View File

@ -66,6 +66,18 @@ class EndTag extends Node {
}
}
function beforeGenerateCode(event) {
if (event.node.tagName === 'script') {
event.context.pushFlag('SCRIPT_BODY');
}
}
function afterGenerateCode(event) {
if (event.node.tagName === 'script') {
event.context.popFlag('SCRIPT_BODY');
}
}
class HtmlElement extends Node {
constructor(def) {
super('HtmlElement');
@ -84,6 +96,9 @@ class HtmlElement extends Node {
this.selfClosed = def.selfClosed;
this.dynamicAttributes = undefined;
this.bodyOnlyIf = undefined;
this.on('beforeGenerateCode', beforeGenerateCode);
this.on('afterGenerateCode', afterGenerateCode);
}
generateHtmlCode(codegen) {

View File

@ -43,10 +43,16 @@ class Text extends Node {
let builder = codegen.builder;
if (escape) {
let escapeFuncVar = 'escapeXml';
if (codegen.context.isFlagSet('SCRIPT_BODY')) {
escapeFuncVar = codegen.addStaticVar('escapeScript', '__helpers.xs');
}
// TODO Only escape the parts that need to be escaped if it is a compound expression with static
// text parts
argument = builder.functionCall(
'escapeXml',
escapeFuncVar,
[argument]);
} else {
argument = builder.functionCall(builder.identifier('str'), [ argument ]);

View File

@ -22,6 +22,7 @@ var attr = require('raptor-util/attr');
var isArray = Array.isArray;
var STYLE_ATTR = 'style';
var CLASS_ATTR = 'class';
var escapeEndingScriptTagRegExp = /<\//g;
function notEmpty(o) {
if (o == null) {
@ -197,6 +198,26 @@ module.exports = {
* @private
*/
xa: escapeXmlAttr,
/**
* Escapes the '</' sequence in the body of a <script> body to avoid the `<script>` being
* ended prematurely.
*
* For example:
* var evil = {
* name: '</script><script>alert(1)</script>'
* };
*
* <script>var foo = ${JSON.stringify(evil)}</script>
*
* Without escaping the ending '</script>' sequence the opening <script> tag would be
* prematurely ended and a new script tag could then be started that could then execute
* arbitrary code.
*/
xs: function(val) {
return (typeof val === 'string') ? val.replace(escapeEndingScriptTagRegExp, '\\u003C/') : val;
},
/**
* Internal method to render a single HTML attribute
* @private

View File

@ -0,0 +1,3 @@
<script>
var foo = {"name":"Evil \u003C/script>"};
</script><pre>{"name":"Evil &lt;/script>"}</pre>

View File

@ -0,0 +1,4 @@
<script>
var foo = ${JSON.stringify(data.foo)};
</script>
<pre>${JSON.stringify(data.foo)}</pre>

View File

@ -0,0 +1,5 @@
exports.templateData = {
foo: {
name: 'Evil </script>'
}
};

View File

@ -1 +1 @@
<script>document.write('<div>Hello &lt;script>evil&lt;script></div>');</script>
<script>document.write('<div>Hello <script>evil\u003C/script></div>');</script>

View File

@ -1,3 +1,3 @@
exports.templateData = {
"name": "<script>evil<script>"
"name": "<script>evil</script>"
};