fix: prevent browsers from rendering unsafe attachments like HTML in a new tab

We do not want to santitize them with DOMPurify, as an attached file should
be returned exactly as it has been attached by the creator. And sharing HTML files
is IMHO a legitimate use case.

However, opening these in a new tab can result in (JS) code execution.
Yet again, opening a "bigger" preview for some file types like images or videos
is also a valid use case, which also should not be broken. That's why this is not done
in alll cases, but some mime type filtering still exists.

I also looked into whether a blocklist (for HTML and SVG) would make sense, but really
you cannot possibly define an exhausive list of "unsafe" mime types: https://security.stackexchange.com/a/167853/91425

That's why I use a quite strict allowlist approach here.
I skimmed https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types/Common_types
for what could be common use cases/mime types, people _may_ actually want to get
rendered and this is the result.

Thus I am quite confident this patch does not introduce such a big "breaking change"
(in UI behaviour), but people best do not notice it.
This commit is contained in:
rugk 2026-06-13 22:01:35 +02:00
parent 597a6f0d12
commit 7f1f40853e
2 changed files with 32 additions and 3 deletions

View file

@ -2994,11 +2994,18 @@ jQuery.PrivateBin = (function($) {
const mimeType = me.getAttachmentMimeType(attachmentData);
// We explicitly do _not_ use the original mime type for the download link
// to always force a download instead of potentially dangerous browser rendering/parsing/interpretation
let safeMimeType = 'application/octet-stream';
if (me.isSafeMimeType(mimeType)) {
safeMimeType = mimeType;
}
// extract data and convert to binary
const rawData = attachmentData.substring(base64Start);
const decodedData = rawData.length > 0 ? atob(rawData) : '';
let blobUrl = getBlobUrl(decodedData, mimeType);
let blobUrl = getBlobUrl(decodedData, safeMimeType);
attachmentLink.attr('href', blobUrl);
if (typeof fileName !== 'undefined') {
@ -3028,6 +3035,28 @@ jQuery.PrivateBin = (function($) {
me.handleBlobAttachmentPreview($attachmentPreview, blobUrl, mimeType);
};
/**
* Evaluates whether this is known a safe mime type.
*
* This means, the media can safely be displayed and e.g. no XSS should be possible.
*
* @name AttachmentViewer.isSafeMimeType
* @function
* @param {string}
* @returns {bool}
*/
me.isSafeMimeType = function(mimeType) {
return (
mimeType.startsWith('image/') &&
!mimeType.includes('svg')
) ||
mimeType.startsWith('video/') ||
mimeType.startsWith('audio/') ||
mimeType.endsWith('/pdf') ||
mimeType === 'text/plain';
}
/**
* displays the attachment
*

View file

@ -122,7 +122,7 @@ class Configuration
'js/kjua-0.10.0.js' => 'sha512-BYj4xggowR7QD150VLSTRlzH62YPfhpIM+b/1EUEr7RQpdWAGKulxWnOvjFx1FUlba4m6ihpNYuQab51H6XlYg==',
'js/legacy.js' => 'sha512-RQEo1hxpNc37i+jz/D9/JiAZhG8GFx3+SNxjYnI7jUgirDIqrCSj6QPAAZeaidditcWzsJ3jxfEj5lVm7ZwTRQ==',
'js/prettify.js' => 'sha512-puO0Ogy++IoA2Pb9IjSxV1n4+kQkKXYAEUtVzfZpQepyDPyXk8hokiYDS7ybMogYlyyEIwMLpZqVhCkARQWLMg==',
'js/privatebin.js' => 'sha512-U52jFm7xsPJsJ7M/sFF6GoEBLTQ4pcvNDN5z3JcH0P2xHokiV3YrhfSi6XgTnBANdnjAXmg9GfJf3Bs2yKAiug==',
'js/privatebin.js' => 'sha512-LgKu4erWrsmN7bYLwo1I2cptFa+RUY33oufmu0Psdp2vmGA4SAqA7sml0kIFCP/44Yd4VILvr/4fCRVSQ48GNQ==',
'js/purify-3.4.1.js' => 'sha512-280a/Vb6fVFsYaeRrkuDp4EDmdYlt2XS+dlDEO/U9qljPrAraA2bIzHTNmP+9dpwPDDwTML+RS+h5iaagPwTzA==',
'js/showdown-2.1.0.js' => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==',
'js/zlib-1.3.2.js' => 'sha512-RAhJgxg9siMIA8ky4c10Rc2zUgnK80olHB8Tt1IOYWY4Eh1WmrviQkDn+sgBlb38ZHq3tzufGC41kP360gmosQ==',