Skip to content

Commit

Permalink
Ported the html attribute escaping vulnerability fix
Browse files Browse the repository at this point in the history
  • Loading branch information
gamefreak committed Dec 12, 2014
1 parent b8cf23d commit e0c2dd4
Show file tree
Hide file tree
Showing 2 changed files with 201 additions and 87 deletions.
186 changes: 100 additions & 86 deletions snuownd.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// up to date with commit 8eb57f6a2259480160f0ba569344ccfa3371eaef
// up to date with commit 62bfa4ad673c4f19683ed91c5ebb093bbe9f581d

/**
@module SnuOwnd
Expand Down Expand Up @@ -1375,91 +1375,105 @@
// rndr_html_tag(struct buf *ob, const struct buf *text, void *opaque, char* tagname, char** whitelist, int tagtype)
//NOT A CALLBACK!
function rndr_html_tag(out, text, options, tagname, whitelist, tagtype) {
var x, z, in_str = 0, seen_equals = 0, done, reset;
var attr = new Buffer()
var c;

out.s += '<';

var i = 1 + tagname.length;

if(tagtype == HTML_TAG_CLOSE) {
out.s += '/';
i += 1;
}

out.s += tagname;

if(tagtype != HTML_TAG_CLOSE) {
for(;i < text.s.length; i++) {
c = text.s[i];
done = 0;
reset = 0;

switch(c) {
case '>':
if(seen_equals && !in_str) {
done = 1;
reset = 1;
} else {
reset = 1;
}
break;
case '\'':
case '"':
if(!in_str)
in_str = c;
else if(in_str == c)
in_str = !in_str;
break;
default:
if(!in_str) {
switch(c) {
case ' ':
if(seen_equals) {
done = 1;
reset = 1;
} else
reset = 1;
break;
case '=':
if(seen_equals) {
reset = 1;
} else {
for(z=0; z < whitelist.length; z++) {
if(whitelist[z].length != attr.s.length)
continue;
for(x=0;x < attr.s.length; x++) {
if(whitelist[z][x].toLowerCase() != attr.s[x].toLowerCase())
break;
}
if(x == attr.s.length)
seen_equals = 1;
}
if(!seen_equals)
reset = 1;
}
break;
}
}
}

if(done) {
out.s += ' ' + attr.s;
}

if(reset) {
seen_equals = 0;
in_str = 0;
attr.s = '';
} else {
attr.s += c;
}
}
}

// bufrelease(attr);
out.s += '>';
var i, x, z, in_str = 0, seen_equals = 0, done = 0, done_attr = 0, reset = 0;
var attr, value;
var c;

out.s += '<';

if(tagtype == HTML_TAG_CLOSE) {
out.s += "/" + tagname + ">";
return;
}

out.s += tagname;
var i = 1 + tagname.length;

attr = new Buffer;
value = new Buffer;

for(;i < text.s.length && !done; i++) {
c = text.s[i];
done = 0;
reset = 0;
done_attr = 0;

switch(c) {
case '>':
done = 1;
break;
case '\'':
case '"':
if (!seen_equals) {
reset = 1;
} else if(!in_str) {
in_str = c;
} else if(in_str == c) {
in_str = 0;
done_attr = 1;
} else {
value.s += c;
}
break;
case ' ':
if (in_str) {
value.s += ' ';
} else {
reset = 1;
}
break;
case '=':
if (seen_equals) {
reset = 1;
break;
}
seen_equals = 1;
break;
default:
if (seen_equals && in_str || !seen_equals) {
if (seen_equals) {
value.s += c;
} else {
attr.s += c;
}
}
break;
}

if (done_attr) {
var valid = 0;
for (z = 0; whitelist[z]; z++) {
if (whitelist[z].length != attr.s.length) {
continue;
}
for (x = 0; x < attr.s.length; x++) {
if (whitelist[z][x].toLowerCase() != attr.s[x].toLowerCase()) {
break;
}
}
if (x == attr.s.length) {
valid = 1;
break;
}
}
if (valid && value.s.length && attr.s.length) {
// console.log("VALD", value.s.length, attr.s.length);
out.s += ' ';
escape_html(out, attr.s, false);
out.s += '="';
escape_html(out, value.s, false)
out.s += '"';
}
reset = 1;
}
if (reset) {
seen_equals = 0;
in_str = 0;
attr = new Buffer;
value = new Buffer;
}
}
out.s += '>';
}

// int (*raw_html_tag)(struct buf *ob, const struct buf *tag, void *opaque);
Expand Down
102 changes: 101 additions & 1 deletion test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

var snuownd = require('./snuownd');
var md = snuownd.getParser();

var md_wiki = (function() {
var redditCallbacks = snuownd.getRedditCallbacks();
var rendererConfig = snuownd.defaultRenderState();
rendererConfig.flags = snuownd.DEFAULT_WIKI_FLAGS;
rendererConfig.html_element_whitelist = snuownd.DEFAULT_HTML_ELEMENT_WHITELIST;
rendererConfig.html_attr_whitelist = snuownd.DEFAULT_HTML_ATTR_WHITELIST;

return snuownd.getParser({
callbacks: redditCallbacks,
context: rendererConfig
});
})();

var fs = require('fs');

colors = {
Expand Down Expand Up @@ -142,6 +156,71 @@ cases = {
'<p><a href="/r/t:heatdeathoftheuniverse">/r/t:heatdeathoftheuniverse</a></p>\n',
}

wiki_cases = {
'<table scope="foo"bar>':
'<p><table scope="foo"></p>\n',

'<table scope="foo"bar colspan="2">':
'<p><table scope="foo" colspan="2"></p>\n',

'<table scope="foo" colspan="2"bar>':
'<p><table scope="foo" colspan="2"></p>\n',

'<table scope="foo">':
'<p><table scope="foo"></p>\n',

'<table scop="foo">':
'<p><table></p>\n',

'<table ff= scope="foo">':
'<p><table scope="foo"></p>\n',

'<table colspan= scope="foo">':
'<p><table scope="foo"></p>\n',

'<table scope=ff"foo">':
'<p><table scope="foo"></p>\n',

'<table scope="foo" test="test">':
'<p><table scope="foo"></p>\n',

'<table scope="foo" longervalue="testing test" scope="test">':
'<p><table scope="foo" scope="test"></p>\n',

'<table scope=`"foo">':
'<p><table scope="foo"></p>\n',

'<table scope="foo bar">':
'<p><table scope="foo bar"></p>\n',

'<table scope=\'foo colspan="foo">':
'<p><table></p>\n',

'<table scope=\'foo\' colspan="foo">':
'<p><table scope="foo" colspan="foo"></p>\n',

'<table scope=>':
'<p><table></p>\n',

'<table scope= colspan="test" scope=>':
'<p><table colspan="test"></p>\n',

'<table colspan="\'test">':
'<p><table colspan="&#39;test"></p>\n',

'<table scope="foo" colspan="2">':
'<p><table scope="foo" colspan="2"></p>\n',

'<table scope="foo" colspan="2" ff="test">':
'<p><table scope="foo" colspan="2"></p>\n',

'<table ff="test" scope="foo" colspan="2" colspan=>':
'<p><table scope="foo" colspan="2"></p>\n',

' <table colspan=\'\'\' a="" \' scope="foo">':
'<p><table scope="foo"></p>\n',
}

console.log("Running Snudown test cases.");
var showSuccesses = false
for (var text in cases) {
Expand All @@ -162,14 +241,35 @@ for (var text in cases) {
}
}


console.log("Running Snudown test cases for the wiki format.");
for (var text in wiki_cases) {

var result = md_wiki.render(text);
if (wiki_cases[text] == result) {
if (showSuccesses) {
console.log(text);
console.log(wiki_cases[text]);
console.log(result);
console.log();
}
} else {
console.log(colors.RED, text, colors.RESET);
// console.log(wiki_cases[text]);
console.log(colors.GREEN, wiki_cases[text], colors.RESET);
console.log(result);
console.log();
}
}


try {
var htmlutil = require('html-util');
} catch (e) {
console.error('Could not load module "html-util", module must be installed to run tests.');
return 1;
}


console.log('Retrieving test data from /r/all');
var http = require('http');
http.request({
Expand Down

0 comments on commit e0c2dd4

Please sign in to comment.