mirror of
https://github.com/PrivateBin/PrivateBin.git
synced 2025-05-02 14:36:08 -04:00
more general solution addressing #554, kudos @rugk for the suggestions
This commit is contained in:
parent
8d0ac336d2
commit
4bf7f863dc
15 changed files with 95 additions and 143 deletions
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue