Merge branch 'hardening'
This commit is contained in:
commit
1dd53a93f4
11 changed files with 122 additions and 46 deletions
|
@ -9,11 +9,15 @@
|
|||
* ADDED: Google Cloud Storage backend support (#795)
|
||||
* ADDED: Oracle database support (#868)
|
||||
* ADDED: Configuration option to limit paste creation and commenting to certain IPs (#883)
|
||||
* ADDED: Set CSP also as meta tag, to deal with misconfigured webservers mangling the HTTP header
|
||||
* ADDED: Sanitize SVG preview, preventing script execution in instance context
|
||||
* CHANGED: Language selection cookie only transmitted over HTTPS (#472)
|
||||
* CHANGED: Upgrading libraries to: base-x 4.0.0, bootstrap 3.4.1 (JS), DOMpurify 2.3.6, ip-lib 1.18.0, jQuery 3.6.0, random_compat 2.0.21 & Showdown 2.0.0
|
||||
* CHANGED: Upgrading libraries to: base-x 4.0.0, bootstrap 3.4.1 (JS), DOMpurify 2.3.6, ip-lib 1.18.0, jQuery 3.6.0, random_compat 2.0.21 & Showdown 2.0.3
|
||||
* CHANGED: Removed automatic `.ini` configuration file migration (#808)
|
||||
* CHANGED: Removed configurable `dir` for `traffic` & `purge` limiters (#419)
|
||||
* CHANGED: Server salt, traffic and purge limiter now stored in the storage backend (#419)
|
||||
* CHANGED: Drop support for attachment download in IE
|
||||
* FIXED: Error when attachments are disabled, but paste with attachment gets displayed
|
||||
* **1.3.5 (2021-04-05)**
|
||||
* ADDED: Translations for Hebrew, Lithuanian, Indonesian and Catalan
|
||||
* ADDED: Make the project info configurable (#681)
|
||||
|
|
|
@ -87,7 +87,7 @@ languageselection = false
|
|||
; async functions and display an error if not and for Chrome to enable
|
||||
; webassembly support (used for zlib compression). You can remove it if Chrome
|
||||
; doesn't need to be supported and old browsers don't need to be warned.
|
||||
; cspheader = "default-src 'none'; base-uri 'self'; form-action 'none'; manifest-src 'self'; connect-src * blob:; script-src 'self' 'unsafe-eval' resource:; style-src 'self'; font-src 'self'; frame-ancestors 'none'; img-src 'self' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads"
|
||||
; cspheader = "default-src 'none'; base-uri 'self'; form-action 'none'; manifest-src 'self'; connect-src * blob:; script-src 'self' 'unsafe-eval'; style-src 'self'; font-src 'self'; frame-ancestors 'none'; img-src 'self' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads"
|
||||
|
||||
; stay compatible with PrivateBin Alpha 0.19, less secure
|
||||
; if enabled will use base64.js version 1.7 instead of 2.1.9 and sha1 instead of
|
||||
|
|
|
@ -16,7 +16,7 @@ global.zlib = require('./zlib-1.2.11').zlib;
|
|||
require('./prettify');
|
||||
global.prettyPrint = window.PR.prettyPrint;
|
||||
global.prettyPrintOne = window.PR.prettyPrintOne;
|
||||
global.showdown = require('./showdown-2.0.0');
|
||||
global.showdown = require('./showdown-2.0.3');
|
||||
global.DOMPurify = require('./purify-2.3.6');
|
||||
global.baseX = require('./base-x-4.0.0').baseX;
|
||||
global.Legacy = require('./legacy').Legacy;
|
||||
|
|
130
js/privatebin.js
130
js/privatebin.js
|
@ -52,6 +52,31 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
*/
|
||||
let z;
|
||||
|
||||
/**
|
||||
* DOMpurify settings for HTML content
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
const purifyHtmlConfig = {
|
||||
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|magnet):)/i,
|
||||
SAFE_FOR_JQUERY: true,
|
||||
USE_PROFILES: {
|
||||
html: true
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DOMpurify settings for SVG content
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
const purifySvgConfig = {
|
||||
USE_PROFILES: {
|
||||
svg: true,
|
||||
svgFilters: true
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* CryptoData class
|
||||
*
|
||||
|
@ -409,7 +434,8 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
element.html().replace(
|
||||
/(((https?|ftp):\/\/[\w?!=&.\/-;#@~%+*-]+(?![\w\s?!&.\/;#~%"=-]>))|((magnet):[\w?=&.\/-;#@~%+*-]+))/ig,
|
||||
'<a href="$1" rel="nofollow noopener noreferrer">$1</a>'
|
||||
)
|
||||
),
|
||||
purifyHtmlConfig
|
||||
)
|
||||
);
|
||||
};
|
||||
|
@ -2536,7 +2562,8 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
// let showdown convert the HTML and sanitize HTML *afterwards*!
|
||||
$plainText.html(
|
||||
DOMPurify.sanitize(
|
||||
converter.makeHtml(text)
|
||||
converter.makeHtml(text),
|
||||
purifyHtmlConfig
|
||||
)
|
||||
);
|
||||
// add table classes from bootstrap css
|
||||
|
@ -2752,6 +2779,34 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
$dropzone;
|
||||
|
||||
/**
|
||||
* get blob URL from string data and mime type
|
||||
*
|
||||
* @name AttachmentViewer.getBlobUrl
|
||||
* @private
|
||||
* @function
|
||||
* @param {string} data - raw data of attachment
|
||||
* @param {string} data - mime type of attachment
|
||||
* @return {string} objectURL
|
||||
*/
|
||||
function getBlobUrl(data, mimeType)
|
||||
{
|
||||
// Transform into a Blob
|
||||
const buf = new Uint8Array(data.length);
|
||||
for (let i = 0; i < data.length; ++i) {
|
||||
buf[i] = data.charCodeAt(i);
|
||||
}
|
||||
const blob = new window.Blob(
|
||||
[buf],
|
||||
{
|
||||
type: mimeType
|
||||
}
|
||||
);
|
||||
|
||||
// Get blob URL
|
||||
return window.URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the attachment but does not yet show it
|
||||
*
|
||||
* @name AttachmentViewer.setAttachment
|
||||
|
@ -2761,44 +2816,42 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
*/
|
||||
me.setAttachment = function(attachmentData, fileName)
|
||||
{
|
||||
// data URI format: data:[<mediaType>][;base64],<data>
|
||||
// skip, if attachments got disabled
|
||||
if (!$attachmentLink || !$attachmentPreview) return;
|
||||
|
||||
// data URI format: data:[<mimeType>][;base64],<data>
|
||||
|
||||
// position in data URI string of where data begins
|
||||
const base64Start = attachmentData.indexOf(',') + 1;
|
||||
// position in data URI string of where mediaType ends
|
||||
const mediaTypeEnd = attachmentData.indexOf(';');
|
||||
// position in data URI string of where mimeType ends
|
||||
const mimeTypeEnd = attachmentData.indexOf(';');
|
||||
|
||||
// extract mediaType
|
||||
const mediaType = attachmentData.substring(5, mediaTypeEnd);
|
||||
// extract mimeType
|
||||
const mimeType = attachmentData.substring(5, mimeTypeEnd);
|
||||
// extract data and convert to binary
|
||||
const rawData = attachmentData.substring(base64Start);
|
||||
const decodedData = rawData.length > 0 ? atob(rawData) : '';
|
||||
|
||||
// Transform into a Blob
|
||||
const buf = new Uint8Array(decodedData.length);
|
||||
for (let i = 0; i < decodedData.length; ++i) {
|
||||
buf[i] = decodedData.charCodeAt(i);
|
||||
}
|
||||
const blob = new window.Blob([ buf ], { type: mediaType });
|
||||
|
||||
// Get Blob URL
|
||||
const blobUrl = window.URL.createObjectURL(blob);
|
||||
|
||||
// IE does not support setting a data URI on an a element
|
||||
// Using msSaveBlob to download
|
||||
if (window.Blob && navigator.msSaveBlob) {
|
||||
$attachmentLink.off('click').on('click', function () {
|
||||
navigator.msSaveBlob(blob, fileName);
|
||||
});
|
||||
} else {
|
||||
$attachmentLink.attr('href', blobUrl);
|
||||
}
|
||||
let blobUrl = getBlobUrl(decodedData, mimeType);
|
||||
$attachmentLink.attr('href', blobUrl);
|
||||
|
||||
if (typeof fileName !== 'undefined') {
|
||||
$attachmentLink.attr('download', fileName);
|
||||
}
|
||||
|
||||
me.handleBlobAttachmentPreview($attachmentPreview, blobUrl, mediaType);
|
||||
// sanitize SVG preview
|
||||
// prevents executing embedded scripts when CSP is not set and user
|
||||
// right-clicks/long-taps and opens the SVG in a new tab - prevented
|
||||
// in the preview by use of an img tag, which disables scripts, too
|
||||
if (mimeType.match(/^image\/.*svg/i)) {
|
||||
const sanitizedData = DOMPurify.sanitize(
|
||||
decodedData,
|
||||
purifySvgConfig
|
||||
);
|
||||
blobUrl = getBlobUrl(sanitizedData, mimeType);
|
||||
}
|
||||
|
||||
me.handleBlobAttachmentPreview($attachmentPreview, blobUrl, mimeType);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -2809,6 +2862,9 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
*/
|
||||
me.showAttachment = function()
|
||||
{
|
||||
// skip, if attachments got disabled
|
||||
if (!$attachment || !$attachmentPreview) return;
|
||||
|
||||
$attachment.removeClass('hidden');
|
||||
|
||||
if (attachmentHasPreview) {
|
||||
|
@ -3016,13 +3072,13 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
me.handleBlobAttachmentPreview = function ($targetElement, blobUrl, mimeType) {
|
||||
if (blobUrl) {
|
||||
attachmentHasPreview = true;
|
||||
if (mimeType.match(/image\//i)) {
|
||||
if (mimeType.match(/^image\//i)) {
|
||||
$targetElement.html(
|
||||
$(document.createElement('img'))
|
||||
.attr('src', blobUrl)
|
||||
.attr('class', 'img-thumbnail')
|
||||
);
|
||||
} else if (mimeType.match(/video\//i)) {
|
||||
} else if (mimeType.match(/^video\//i)) {
|
||||
$targetElement.html(
|
||||
$(document.createElement('video'))
|
||||
.attr('controls', 'true')
|
||||
|
@ -3033,7 +3089,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
.attr('type', mimeType)
|
||||
.attr('src', blobUrl))
|
||||
);
|
||||
} else if (mimeType.match(/audio\//i)) {
|
||||
} else if (mimeType.match(/^audio\//i)) {
|
||||
$targetElement.html(
|
||||
$(document.createElement('audio'))
|
||||
.attr('controls', 'true')
|
||||
|
@ -3665,7 +3721,14 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
for (let i = 0; i < $head.length; ++i) {
|
||||
newDoc.write($head[i].outerHTML);
|
||||
}
|
||||
newDoc.write('</head><body><pre>' + DOMPurify.sanitize(Helper.htmlEntities(paste)) + '</pre></body></html>');
|
||||
newDoc.write(
|
||||
'</head><body><pre>' +
|
||||
DOMPurify.sanitize(
|
||||
Helper.htmlEntities(paste),
|
||||
purifyHtmlConfig
|
||||
) +
|
||||
'</pre></body></html>'
|
||||
);
|
||||
newDoc.close();
|
||||
}
|
||||
|
||||
|
@ -5394,11 +5457,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
// first load translations
|
||||
I18n.loadTranslations();
|
||||
|
||||
DOMPurify.setConfig({
|
||||
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|magnet):)/i,
|
||||
SAFE_FOR_JQUERY: true
|
||||
});
|
||||
|
||||
// Add a hook to make all links open a new window
|
||||
DOMPurify.addHook('afterSanitizeAttributes', function(node) {
|
||||
// set all elements owning target to target=_blank
|
||||
|
|
File diff suppressed because one or more lines are too long
2
js/showdown-2.0.3.js
Normal file
2
js/showdown-2.0.3.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -54,7 +54,7 @@ class Configuration
|
|||
'urlshortener' => '',
|
||||
'qrcode' => true,
|
||||
'icon' => 'identicon',
|
||||
'cspheader' => 'default-src \'none\'; base-uri \'self\'; form-action \'none\'; manifest-src \'self\'; connect-src * blob:; script-src \'self\' \'unsafe-eval\' resource:; style-src \'self\'; font-src \'self\'; frame-ancestors \'none\'; img-src \'self\' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads',
|
||||
'cspheader' => 'default-src \'none\'; base-uri \'self\'; form-action \'none\'; manifest-src \'self\'; connect-src * blob:; script-src \'self\' \'unsafe-eval\'; style-src \'self\'; font-src \'self\'; frame-ancestors \'none\'; img-src \'self\' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads',
|
||||
'zerobincompatibility' => false,
|
||||
'httpwarning' => true,
|
||||
'compression' => 'zlib',
|
||||
|
|
|
@ -364,6 +364,16 @@ class Controller
|
|||
setcookie('lang', $languageselection, 0, '', '', true);
|
||||
}
|
||||
|
||||
// strip policies that are unsupported in meta tag
|
||||
$metacspheader = str_replace(
|
||||
array(
|
||||
'frame-ancestors \'none\'; ',
|
||||
'; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads',
|
||||
),
|
||||
'',
|
||||
$this->_conf->getKey('cspheader')
|
||||
);
|
||||
|
||||
$page = new View;
|
||||
$page->assign('NAME', $this->_conf->getKey('name'));
|
||||
$page->assign('BASEPATH', I18n::_($this->_conf->getKey('basepath')));
|
||||
|
@ -392,6 +402,7 @@ class Controller
|
|||
$page->assign('HTTPWARNING', $this->_conf->getKey('httpwarning'));
|
||||
$page->assign('HTTPSLINK', 'https://' . $this->_request->getHost() . $this->_request->getRequestUri());
|
||||
$page->assign('COMPRESSION', $this->_conf->getKey('compression'));
|
||||
$page->assign('CSPHEADER', $metacspheader);
|
||||
$page->draw($this->_conf->getKey('template'));
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ $isPage = substr($template, -5) === '-page';
|
|||
<html lang="<?php echo I18n::_('en'); ?>">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Security-Policy" content="<?php echo I18n::encode($CSPHEADER); ?>">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex" />
|
||||
|
@ -66,13 +67,13 @@ if ($SYNTAXHIGHLIGHTING) :
|
|||
endif;
|
||||
if ($MARKDOWN) :
|
||||
?>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/showdown-2.0.0.js" integrity="sha512-UB9jpMTOJLSnVzePuqlSGT34G70wEGqtIWabMeAh+Drnj4/uQ8rFkFn1zkN9vkWp/7nA51U2LmP23H5MJvBXsw==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/showdown-2.0.3.js" integrity="sha512-vcfjvW3UKHD/4vlQx804cqWK88jFmjsWRsZ8/u5YEcyHB1IituxrXDU7TvdqsFVsMnxpE/UIEo25/SYW+puWHw==" crossorigin="anonymous"></script>
|
||||
<?php
|
||||
endif;
|
||||
?>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/purify-2.3.6.js" integrity="sha512-N1GGPjbqLbwK821ZN7C925WuTwU4aDxz2CEEOXQ6/s6m6MBwVj8fh5fugiE2hzsm0xud3q7jpjZQ4ILnpMREYQ==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-EdUms2nI12Cmtv014stIEBlyPjeKMHlkg7NiBJup1b7jJF5amKhev2RwTaldINXK4UaWbZtQ6hGuMPNvvNQZFA==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-kFUoM3Qg6VzRTJGau3wWP2IRZQF2WToL7Dgvazxqp0jgYIKA5cCK9xFy9Zes208XX0nYP64scW2nJIYNk9pL6Q==" crossorigin="anonymous"></script>
|
||||
<!-- icon -->
|
||||
<link rel="apple-touch-icon" href="<?php echo I18n::encode($BASEPATH); ?>img/apple-touch-icon.png" sizes="180x180" />
|
||||
<link rel="icon" type="image/png" href="img/favicon-32x32.png" sizes="32x32" />
|
||||
|
|
|
@ -4,6 +4,7 @@ use PrivateBin\I18n;
|
|||
<html lang="<?php echo I18n::_('en'); ?>">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Security-Policy" content="<?php echo I18n::encode($CSPHEADER); ?>">
|
||||
<meta name="robots" content="noindex" />
|
||||
<meta name="google" content="notranslate">
|
||||
<title><?php echo I18n::_($NAME); ?></title>
|
||||
|
@ -44,13 +45,13 @@ if ($SYNTAXHIGHLIGHTING):
|
|||
endif;
|
||||
if ($MARKDOWN):
|
||||
?>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/showdown-2.0.0.js" integrity="sha512-UB9jpMTOJLSnVzePuqlSGT34G70wEGqtIWabMeAh+Drnj4/uQ8rFkFn1zkN9vkWp/7nA51U2LmP23H5MJvBXsw==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/showdown-2.0.3.js" integrity="sha512-vcfjvW3UKHD/4vlQx804cqWK88jFmjsWRsZ8/u5YEcyHB1IituxrXDU7TvdqsFVsMnxpE/UIEo25/SYW+puWHw==" crossorigin="anonymous"></script>
|
||||
<?php
|
||||
endif;
|
||||
?>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/purify-2.3.6.js" integrity="sha512-N1GGPjbqLbwK821ZN7C925WuTwU4aDxz2CEEOXQ6/s6m6MBwVj8fh5fugiE2hzsm0xud3q7jpjZQ4ILnpMREYQ==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-EdUms2nI12Cmtv014stIEBlyPjeKMHlkg7NiBJup1b7jJF5amKhev2RwTaldINXK4UaWbZtQ6hGuMPNvvNQZFA==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-kFUoM3Qg6VzRTJGau3wWP2IRZQF2WToL7Dgvazxqp0jgYIKA5cCK9xFy9Zes208XX0nYP64scW2nJIYNk9pL6Q==" crossorigin="anonymous"></script>
|
||||
<!-- icon -->
|
||||
<link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" />
|
||||
<link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" />
|
||||
|
|
|
@ -60,6 +60,7 @@ class ViewTest extends PHPUnit_Framework_TestCase
|
|||
$page->assign('HTTPWARNING', true);
|
||||
$page->assign('HTTPSLINK', 'https://example.com/');
|
||||
$page->assign('COMPRESSION', 'zlib');
|
||||
$page->assign('CSPHEADER', 'default-src \'none\'');
|
||||
|
||||
$dir = dir(PATH . 'tpl');
|
||||
while (false !== ($file = $dir->read())) {
|
||||
|
|
Loading…
Reference in a new issue