more general solution addressing #554, kudos @rugk for the suggestions

This commit is contained in:
El RIDO 2020-01-04 11:34:16 +01:00
parent 8d0ac336d2
commit 4bf7f863dc
No known key found for this signature in database
GPG key ID: 0F5C940A6BD81F92
15 changed files with 95 additions and 143 deletions

View file

@ -440,7 +440,33 @@ jQuery.PrivateBin = (function($, RawDeflate) {
expirationDate = expirationDate.setUTCSeconds(expirationDate.getUTCSeconds() + secondsToExpiration);
return expirationDate;
}
};
/**
* encode all applicable characters to HTML entities
*
* @see {@link https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html}
*
* @name Helper.htmlEntities
* @function
* @param string str
* @return string escaped HTML
*/
me.htmlEntities = function(str) {
// using textarea, since other tags may allow and execute scripts, even when detached from DOM
let holder = document.createElement('textarea');
holder.textContent = str;
// as per OWASP recommendation, also encoding quotes and slash
return holder.innerHTML.replace(
/["'\/]/g,
function(s) {
return {
'"': '"',
"'": ''',
'/': '/'
}[s];
});
};
return me;
})();
@ -592,16 +618,31 @@ jQuery.PrivateBin = (function($, RawDeflate) {
args[0] = translations[messageId];
}
// messageID may contain links, but should be from a trusted source (code or translation JSON files)
let containsNoLinks = args[0].indexOf('<a') === -1;
for (let i = 0; i < args.length; ++i) {
// parameters (i > 0) may never contain HTML as they may come from untrusted parties
if (i > 0 || containsNoLinks) {
args[i] = Helper.htmlEntities(args[i]);
}
}
// format string
let output = Helper.sprintf.apply(this, args);
// if $element is given, apply text to element
if ($element !== null) {
// avoid HTML entity encoding if translation contains link
if (output.indexOf('<a') === -1) {
if (containsNoLinks) {
// avoid HTML entity encoding if translation contains links
$element.text(output);
} else {
$element.html(output);
// only allow tags/attributes we actually use in our translations
$element.html(
DOMPurify.sanitize(output, {
ALLOWED_TAGS: ['a', 'br', 'i', 'span'],
ALLOWED_ATTR: ['href', 'id']
})
);
}
}
@ -2362,7 +2403,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
// escape HTML entities, link URLs, sanitize
const escapedLinkedText = Helper.urls2links(
$('<div />').text(text).html()
Helper.htmlEntities(text)
),
sanitizedLinkedText = DOMPurify.sanitize(escapedLinkedText);
$plainText.html(sanitizedLinkedText);
@ -2796,11 +2837,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$attachmentLink.appendTo($element);
// update text - ensuring no HTML is inserted into the text node
I18n._(
$attachmentLink,
$('<div />').text(label).html(),
$('<div />').text($attachmentLink.attr('download')).html()
);
I18n._($attachmentLink, label, $attachmentLink.attr('download'));
};
/**
@ -3498,7 +3535,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
for (let i = 0; i < $head.length; ++i) {
newDoc.write($head[i].outerHTML);
}
newDoc.write('</head><body><pre>' + DOMPurify.sanitize($('<div />').text(paste).html()) + '</pre></body></html>');
newDoc.write('</head><body><pre>' + DOMPurify.sanitize(Helper.htmlEntities(paste)) + '</pre></body></html>');
newDoc.close();
}