Skip to content

Commit 2de9592

Browse files
committed
Fix possible XSS if loading BBCode that someone else has written
else has written.
1 parent a37e40a commit 2de9592

8 files changed

+320
-41
lines changed

CHANGELOG.txt

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ Version 1.4.5:
2626
- Thanks to @gan068 for translating.
2727
Updated Polish translation.
2828
- Thanks to @gnysek for updating.
29+
Fix possible XSS if editing loading BBCode that someone else has written.
30+
- Thanks to for Sergiu reporting.
2931

3032
Version 1.4.4:
3133
Fixed height auto expanding when resized.

minified/jquery.sceditor.bbcode.min.js

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

minified/jquery.sceditor.min.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

minified/jquery.sceditor.xhtml.min.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

minified/plugins/bbcode.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/jquery.sceditor.js

+53-4
Original file line numberDiff line numberDiff line change
@@ -2446,13 +2446,14 @@
24462446
* @memberOf jQuery.sceditor.prototype
24472447
*/
24482448
base._ = function() {
2449-
var args = arguments;
2449+
var undef,
2450+
args = arguments;
24502451

24512452
if(locale && locale[args[0]])
24522453
args[0] = locale[args[0]];
24532454

24542455
return args[0].replace(/\{(\d+)\}/g, function(str, p1) {
2455-
return typeof args[p1-0+1] !== 'undefined' ?
2456+
return args[p1-0+1] !== undef ?
24562457
args[p1-0+1] :
24572458
'{' + p1 + '}';
24582459
});
@@ -3389,22 +3390,70 @@
33893390
/**
33903391
* Escapes all HTML entities in a string
33913392
*
3393+
* If quote is set to true, single and double quotes will also be escaped
3394+
*
33923395
* @param {String} str
3396+
* @param {Boolean} [quote=false]
33933397
* @return {String}
33943398
* @name escapeEntities
33953399
* @memberOf jQuery.sceditor
33963400
* @since 1.4.1
33973401
*/
3398-
$.sceditor.escapeEntities = function(str) {
3402+
$.sceditor.escapeEntities = function(str, quote) {
33993403
if(!str)
34003404
return str;
34013405

3402-
return str.replace(/&/g, '&')
3406+
str = str.replace(/&/g, '&')
34033407
.replace(/</g, '&lt;')
34043408
.replace(/>/g, '&gt;')
34053409
.replace(/ {2}/g, ' &nbsp;')
34063410
.replace(/\r\n|\r/g, '\n')
34073411
.replace(/\n/g, '<br />');
3412+
3413+
if (quote)
3414+
str = str.replace(/"/g, '&#34;').replace(/'/g, '&#39;');
3415+
3416+
return str;
3417+
};
3418+
3419+
/**
3420+
* Escape URI scheme.
3421+
*
3422+
* Appends the current URL to a url if it has a scheme that is not:
3423+
*
3424+
* http
3425+
* https
3426+
* sftp
3427+
* ftp
3428+
* mailto
3429+
* spotify
3430+
* skype
3431+
* ssh
3432+
* teamspeak
3433+
* tel
3434+
* //
3435+
*
3436+
* @param {String} url
3437+
* @return {String}
3438+
* @name escapeUriScheme
3439+
* @memberOf jQuery.sceditor
3440+
* @since 1.4.5
3441+
*/
3442+
$.sceditor.escapeUriScheme = function(url) {
3443+
var path,
3444+
validSchemes = /^(?:https?|s?ftp|mailto|spotify|skype|ssh|teamspeak|tel):|(?:\/\/)/i,
3445+
// If there is a : before a / then it has a scheme
3446+
hasScheme = /^[^\/]*:/i,
3447+
location = window.location;
3448+
3449+
// Has no scheme or a valid scheme
3450+
if ((!url || !hasScheme.test(url)) || validSchemes.test(url))
3451+
return url;
3452+
3453+
path = location.pathname.split('/');
3454+
path.pop();
3455+
3456+
return location.protocol + '://' + location.host + path.join('/') + '/' + url;
34083457
};
34093458

34103459
/**

src/plugins/bbcode.js

+76-27
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
(function($, window, document) {
2424
'use strict';
2525

26+
var escapeEntities = $.sceditor.escapeEntities;
27+
var escapeUriScheme = $.sceditor.escapeUriScheme;
28+
2629
/**
2730
* SCEditor BBCode parser class
2831
*
@@ -874,10 +877,13 @@
874877
content += '<br />';
875878
}
876879

877-
if($.isFunction(bbcode.html))
878-
html = bbcode.html.call(base, token, token.attrs, content);
880+
if(!$.isFunction(bbcode.html))
881+
{
882+
token.attrs['0'] = content;
883+
html = $.sceditor.plugins.bbcode.formatBBCodeString(bbcode.html, token.attrs);
884+
}
879885
else
880-
html = $.sceditor.plugins.bbcode.formatString(bbcode.html, content);
886+
html = bbcode.html.call(base, token, token.attrs, content);
881887
}
882888
else
883889
html = token.val + content + (token.closing ? token.closing.val : '');
@@ -926,7 +932,7 @@
926932
else // content
927933
{
928934
needsBlockWrap = isRoot;
929-
html = $.sceditor.escapeEntities(token.val);
935+
html = escapeEntities(token.val, true);
930936
}
931937

932938
if(needsBlockWrap && !blockWrapOpen)
@@ -1760,14 +1766,51 @@
17601766
* @since v1.4.0
17611767
*/
17621768
$.sceditor.plugins.bbcode.formatString = function() {
1763-
var args = arguments;
1769+
var undef,
1770+
args = arguments;
1771+
17641772
return args[0].replace(/\{(\d+)\}/g, function(str, p1) {
1765-
return typeof args[p1-0+1] !== 'undefined' ?
1773+
return args[p1-0+1] !== undef ?
17661774
args[p1-0+1] :
17671775
'{' + p1 + '}';
17681776
});
17691777
};
17701778

1779+
/**
1780+
* Formats a string replacing {name} with the values of
1781+
* obj.name properties.
1782+
*
1783+
* If there is no property for the specified {name} then
1784+
* it will be left intact.
1785+
*
1786+
* @param {String} str
1787+
* @param {Object} obj
1788+
* @return {String}
1789+
* @since 1.4.5
1790+
*/
1791+
$.sceditor.plugins.bbcode.formatBBCodeString = function(str, obj) {
1792+
return str.replace(/\{(!?[^}]+)\}/g, function(match, group) {
1793+
var undef,
1794+
escape = true;
1795+
1796+
if (group[0] === '!')
1797+
{
1798+
escape = false;
1799+
group = group.substring(1);
1800+
}
1801+
1802+
if (group[0] === '0')
1803+
escape = false;
1804+
1805+
if (obj[group] === undef)
1806+
return match;
1807+
1808+
return escape ?
1809+
escapeEntities(obj[group], true) :
1810+
obj[group];
1811+
});
1812+
};
1813+
17711814
/**
17721815
* Converts CSS RGB and hex shorthand into hex
17731816
*
@@ -1898,9 +1941,7 @@
18981941

18991942
return '[font=' + this.stripQuotes(font) + ']' + content + '[/font]';
19001943
},
1901-
html: function(token, attrs, content) {
1902-
return '<font face="' + attrs.defaultattr + '">' + content + '</font>';
1903-
}
1944+
html: '<font face="{defaultattr}">{0}</font>'
19041945
},
19051946
// END_COMMAND
19061947

@@ -1944,9 +1985,7 @@
19441985

19451986
return '[size=' + size + ']' + content + '[/size]';
19461987
},
1947-
html: function(token, attrs, content) {
1948-
return '<font size="' + attrs.defaultattr + '">' + content + '</font>';
1949-
}
1988+
html: '<font size="{defaultattr}">{!0}</font>'
19501989
},
19511990
// END_COMMAND
19521991

@@ -1971,7 +2010,9 @@
19712010
return '[color=' + normaliseColour(color) + ']' + content + '[/color]';
19722011
},
19732012
html: function(token, attrs, content) {
1974-
return '<font color="' + normaliseColour(attrs.defaultattr) + '">' + content + '</font>';
2013+
return '<font color="' +
2014+
escapeEntities(normaliseColour(attrs.defaultattr), true) +
2015+
'">' + content + '</font>';
19752016
}
19762017
},
19772018
// END_COMMAND
@@ -2068,8 +2109,8 @@
20682109
'data-sceditor-emoticon': null
20692110
}
20702111
},
2071-
format: function(element, content) {
2072-
return element.data('sceditor-emoticon') + content;
2112+
format: function($elm, content) {
2113+
return $elm.data('sceditor-emoticon') + content;
20732114
},
20742115
html: '{0}'
20752116
},
@@ -2096,6 +2137,7 @@
20962137
src: null
20972138
}
20982139
},
2140+
allowedChildren: ['#'],
20992141
quoteType: $.sceditor.BBCodeParser.QuoteType.never,
21002142
format: function($element, content) {
21012143
var w, h,
@@ -2119,24 +2161,29 @@
21192161
return '[img' + attribs + ']' + $element.attr('src') + '[/img]';
21202162
},
21212163
html: function(token, attrs, content) {
2122-
var parts,
2164+
var undef, w, h, parts,
21232165
attribs = '';
21242166

21252167
// handle [img width=340 height=240]url[/img]
2126-
if(typeof attrs.width !== 'undefined')
2127-
attribs += ' width="' + attrs.width + '"';
2128-
if(typeof attrs.height !== 'undefined')
2129-
attribs += ' height="' + attrs.height + '"';
2168+
if(attrs.width !== undef)
2169+
w = attrs.width;
2170+
if(attrs.height !== undef)
2171+
h = attrs.height;
21302172

21312173
// handle [img=340x240]url[/img]
21322174
if(attrs.defaultattr) {
21332175
parts = attrs.defaultattr.split(/x/i);
21342176

2135-
attribs = ' width="' + parts[0] + '"' +
2136-
' height="' + (parts.length === 2 ? parts[1] : parts[0]) + '"';
2177+
w = parts[0];
2178+
h = (parts.length === 2 ? parts[1] : parts[0]);
21372179
}
21382180

2139-
return '<img' + attribs + ' src="' + content + '" />';
2181+
if (w !== undef)
2182+
attribs += ' width="' + escapeEntities(w, true) + '"';
2183+
if (h !== undef)
2184+
attribs += ' height="' + escapeEntities(h, true) + '"';
2185+
2186+
return '<img' + attribs + ' src="' + escapeUriScheme(content) + '" />';
21402187
}
21412188
},
21422189
// END_COMMAND
@@ -2157,10 +2204,12 @@
21572204
if(url.substr(0, 7) === 'mailto:')
21582205
return '[email="' + url.substr(7) + '"]' + content + '[/email]';
21592206

2160-
return '[url=' + decodeURI(url) + ']' + content + '[/url]';
2207+
return '[url=' + url + ']' + content + '[/url]';
21612208
},
21622209
html: function(token, attrs, content) {
2163-
return '<a href="' + encodeURI(attrs.defaultattr || content) + '">' + content + '</a>';
2210+
attrs.defaultattr = escapeEntities(attrs.defaultattr, true) || content;
2211+
2212+
return '<a href="' + escapeUriScheme(attrs.defaultattr) + '">' + content + '</a>';
21642213
}
21652214
},
21662215
// END_COMMAND
@@ -2169,7 +2218,7 @@
21692218
email: {
21702219
quoteType: $.sceditor.BBCodeParser.QuoteType.never,
21712220
html: function(token, attrs, content) {
2172-
return '<a href="mailto:' + (attrs.defaultattr || content) + '">' + content + '</a>';
2221+
return '<a href="mailto:' + (escapeEntities(attrs.defaultattr, true) || content) + '">' + content + '</a>';
21732222
}
21742223
},
21752224
// END_COMMAND
@@ -2203,7 +2252,7 @@
22032252
},
22042253
html: function(token, attrs, content) {
22052254
if(attrs.defaultattr)
2206-
content = '<cite>' + attrs.defaultattr + '</cite>' + content;
2255+
content = '<cite>' + escapeEntities(attrs.defaultattr) + '</cite>' + content;
22072256

22082257
return '<blockquote>' + content + '</blockquote>';
22092258
}

0 commit comments

Comments
 (0)