Restrictive Content-Security-Policy for media #1045

This adds a CSP header for all media delivered through our fetch.php
dispatcher. This should revent any scripts etc. to be executed when
scriptable media, like SVG is used.

Suggestions on finetuning the policy are welcome.

The policy is added to the MEDIA_SENDFILE event, so plugins can easily
influence it. The way it is passed as an array should make it easier to
modify from plugins as well.

I put the mechanism to send the header into it's own class in the HTTP
namespace. Additional methods from inc/httputils could be moved here
later. The method might also be interesting for #2198 and #1676.
This commit is contained in:
Andreas Gohr 2020-10-14 15:10:47 +02:00
parent a7e2efd2e2
commit 6cda96e3cf
3 changed files with 68 additions and 8 deletions

39
inc/HTTP/Headers.php Normal file
View File

@ -0,0 +1,39 @@
<?php
namespace dokuwiki\HTTP;
/**
* Utilities to send HTTP Headers
*/
class Headers
{
/**
* Send a Content-Security-Polica Header
*
* Expects an associative array with individual policies and their values
*
* @param array $policy
*/
static public function contentSecurityPolicy($policy)
{
foreach ($policy as $key => $values) {
// if the value is not an array, we also accept newline terminated strings
if (!is_array($values)) $values = explode("\n", $values);
$values = array_map('trim', $values);
$values = array_unique($values);
$values = array_filter($values);
$policy[$key] = $values;
}
$cspheader = 'Content-Security-Policy:';
foreach ($policy as $key => $values) {
if ($values) {
$cspheader .= " $key " . join(' ', $values) . ';';
} else {
$cspheader .= " $key;";
}
}
header($cspheader);
}
}

View File

@ -13,22 +13,26 @@
* This function will abort the current script when a 304 is sent or file sending is handled
* through x-sendfile
*
* @param string $file local file to send
* @param string $mime mime type of the file
* @param bool $dl set to true to force a browser download
* @param int $cache remaining cache time in seconds (-1 for $conf['cache'], 0 for no-cache)
* @param bool $public is this a public ressource or a private one?
* @param string $orig original file to send - the file name will be used for the Content-Disposition
* @param array $csp The ContentSecurityPolicy to send
* @author Andreas Gohr <andi@splitbrain.org>
* @author Ben Coburn <btcoburn@silicodon.net>
* @author Gerry Weissbach <dokuwiki@gammaproduction.de>
*
* @param string $file local file to send
* @param string $mime mime type of the file
* @param bool $dl set to true to force a browser download
* @param int $cache remaining cache time in seconds (-1 for $conf['cache'], 0 for no-cache)
* @param bool $public is this a public ressource or a private one?
* @param string $orig original file to send - the file name will be used for the Content-Disposition
*/
function sendFile($file, $mime, $dl, $cache, $public = false, $orig = null) {
function sendFile($file, $mime, $dl, $cache, $public = false, $orig = null, $csp=[]) {
global $conf;
// send mime headers
header("Content-Type: $mime");
// send security policy if given
if ($csp) dokuwiki\HTTP\Headers::contentSecurityPolicy($csp);
// calculate cache times
if($cache == -1) {
$maxage = max($conf['cachetime'], 3600); // cachetime or one hour

View File

@ -55,6 +55,15 @@ if (defined('SIMPLE_TEST')) {
'status' => $STATUS,
'statusmessage' => $STATUSMESSAGE,
'ispublic' => media_ispublic($MEDIA),
'csp' => [
'sandbox' => '',
'default-src' => "'none'",
'script-src' => "'none'",
'style-src' => "'unsafe-inline'",
'media-src' => "'self'",
'object-src' => "'self'",
'form-action' => "'none'",
],
);
// handle the file status
@ -96,7 +105,15 @@ if (defined('SIMPLE_TEST')) {
// finally send the file to the client
$evt = new Event('MEDIA_SENDFILE', $data);
if($evt->advise_before()) {
sendFile($data['file'], $data['mime'], $data['download'], $data['cache'], $data['ispublic'], $data['orig']);
sendFile(
$data['file'],
$data['mime'],
$data['download'],
$data['cache'],
$data['ispublic'],
$data['orig'],
$data['csp']
);
}
// Do something after the download finished.
$evt->advise_after(); // will not be emitted on 304 or x-sendfile