mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-07-15 21:31:36 +03:00
Content Filtering: Limited file protocol to just anchor hrefs
Updated allow list/purifier system to only allow file protocol use on anchor hrefs to avoid potential security concerns with, after export, content being auto loaded via interactive elements like embeds/objects/videos etc... Updated tests to cover. Thanks to Gurmandeep Deol at Seneca Polytechnic for reporting.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace BookStack\Util;
|
||||
|
||||
use BookStack\Util\HtmlPurifier\ConfiguredHtmlPurifier;
|
||||
use DOMAttr;
|
||||
use DOMElement;
|
||||
use DOMNodeList;
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Util;
|
||||
namespace BookStack\Util\HtmlPurifier;
|
||||
|
||||
use BookStack\App\AppVersion;
|
||||
use BookStack\Util\HtmlPurifier\Filters\UriLimitFileProtocolToAnchors;
|
||||
use HTMLPurifier;
|
||||
use HTMLPurifier_Config;
|
||||
use HTMLPurifier_DefinitionCache_Serializer;
|
||||
use HTMLPurifier_HTML5Config;
|
||||
use HTMLPurifier_HTMLDefinition;
|
||||
use HTMLPurifier_URIDefinition;
|
||||
|
||||
/**
|
||||
* Provides a configured HTML Purifier instance.
|
||||
@@ -33,7 +35,13 @@ class ConfiguredHtmlPurifier
|
||||
|
||||
$htmlDef = $config->getDefinition('HTML', true, true);
|
||||
if ($htmlDef instanceof HTMLPurifier_HTMLDefinition) {
|
||||
$this->configureDefinition($htmlDef);
|
||||
$this->configureHtmlDefinition($htmlDef);
|
||||
}
|
||||
|
||||
/** @var \HTMLPurifier_URIDefinition $uriDef */
|
||||
$uriDef = $config->getDefinition('URI', true, true);
|
||||
if ($uriDef instanceof HTMLPurifier_URIDefinition) {
|
||||
$this->configureUriDefinition($uriDef);
|
||||
}
|
||||
|
||||
$this->purifier = new HTMLPurifier($config);
|
||||
@@ -91,7 +99,7 @@ class ConfiguredHtmlPurifier
|
||||
// $config->set('Cache.DefinitionImpl', null); // Disable cache during testing
|
||||
}
|
||||
|
||||
public function configureDefinition(HTMLPurifier_HTMLDefinition $definition): void
|
||||
protected function configureHtmlDefinition(HTMLPurifier_HTMLDefinition $definition): void
|
||||
{
|
||||
// Allow the object element
|
||||
$definition->addElement(
|
||||
@@ -151,6 +159,11 @@ class ConfiguredHtmlPurifier
|
||||
$definition->addAttribute('a', 'data-mention-user-id', 'Number');
|
||||
}
|
||||
|
||||
protected function configureUriDefinition(HTMLPurifier_URIDefinition $definition): void
|
||||
{
|
||||
$definition->registerFilter(new UriLimitFileProtocolToAnchors());
|
||||
}
|
||||
|
||||
public function purify(string $html): string
|
||||
{
|
||||
return $this->purifier->purify($html);
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Util\HtmlPurifier\Filters;
|
||||
|
||||
use HTMLPurifier_Config;
|
||||
use HTMLPurifier_Context;
|
||||
use HTMLPurifier_URI;
|
||||
use HTMLPurifier_URIFilter;
|
||||
|
||||
/**
|
||||
* Limits file:// URIs to only be used on anchor tags href attributes.
|
||||
* This prevents use on iframes/embeds/images where they can be used to load external
|
||||
* content on the network, triggering calls which may include NTLM auth hashes when in
|
||||
* certain windows based environments.
|
||||
*/
|
||||
class UriLimitFileProtocolToAnchors extends HTMLPurifier_URIFilter
|
||||
{
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
public $name = 'LimitFileProtocolToAnchors';
|
||||
|
||||
/**
|
||||
* @type bool
|
||||
*/
|
||||
public $always_load = true;
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_URI $uri
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool
|
||||
*/
|
||||
public function filter(&$uri, $config, $context)
|
||||
{
|
||||
// Ensure we're only filtering file:// URIs'
|
||||
if ($uri->scheme !== 'file') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$token = $context->get('CurrentToken', true);
|
||||
$attr = $context->get('CurrentAttr', true);
|
||||
|
||||
// Only allow if used on hrefs on anchor tags
|
||||
$isAnchor = $token && $token->name === 'a';
|
||||
$isHref = $attr === 'href';
|
||||
if ($isAnchor && $isHref) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
@@ -464,6 +464,11 @@ HTML;
|
||||
'<div style="background:#FF0000;left:0;color:#00FFEE;">Hello!</div>' => '<div style="background:#FF0000;color:#00FFEE;">Hello!</div>',
|
||||
'<div style="color:#00FFEE;">Hello!<style>testinghello!</style></div>' => '<div style="color:#00FFEE;">Hello!</div>',
|
||||
'<div drawio-diagram="5332" another-attr="cat">Hello!</div>' => '<div drawio-diagram="5332">Hello!</div>',
|
||||
'<iframe src="file://link/to/file" id="bkmrk-file-iframe"></iframe>' => '<iframe id="bkmrk-file-iframe"></iframe>',
|
||||
'<embed src="file://link/to/file" id="bkmrk-file-embed"></embed>' => '<embed id="bkmrk-file-embed">',
|
||||
'<object data="file://link/to/file" id="bkmrk-file-object"></object>' => '<object id="bkmrk-file-object">',
|
||||
'<div id="bkmrk-file-img"><img src="file://link/to/file" alt="My local image"></div>' => '<div id="bkmrk-file-img"></div>',
|
||||
'<div id="bkmrk-file-img"><img srcset="file://link/to/file" alt="My local image"></div>' => '<div id="bkmrk-file-img"></div>',
|
||||
];
|
||||
|
||||
config()->set('app.content_filtering', 'a');
|
||||
@@ -476,6 +481,7 @@ HTML;
|
||||
$resp = $this->get($page->getUrl());
|
||||
|
||||
$resp->assertSee($expected, false);
|
||||
$resp->assertDontSee($input, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,6 +490,7 @@ HTML;
|
||||
$testCasesExpectedByInput = [
|
||||
'<p><a href="https://example.com" target="_blank">New tab linkydoodle</a></p>',
|
||||
'<p><a href="https://example.com/user/1" data-mention-user-id="5">@mentionusertext</a></p>',
|
||||
'<p><a href="file://link/to/file">Link to file</a></p>',
|
||||
'<details><summary>Hello</summary><p>Mydetailshere</p></details>',
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user