0
0
mirror of https://github.com/ezyang/htmlpurifier.git synced 2024-12-22 08:21:52 +00:00

[3.1.1] Implement SafeObject.

git-svn-id: http://htmlpurifier.org/svnroot/htmlpurifier/trunk@1780 48356398-32a2-884e-a903-53898d9a118a
This commit is contained in:
Edward Z. Yang 2008-06-10 00:13:44 +00:00
parent 32025a12e1
commit 13eb016e06
13 changed files with 346 additions and 20 deletions

1
NEWS
View File

@ -19,6 +19,7 @@ NEWS ( CHANGELOG and HISTORY ) HTMLPurifier
a URIFilter as such.
! Allow modules to define injectors via $info_injector. Injectors are
automatically disabled if injector's needed elements are not found.
! Support for "safe" objects added [info on how to enable needed]
- Disable percent height/width attributes for img
- AttrValidator operations are now atomic; updates to attributes are not
manifest in token until end of operations. This prevents naughty internal

3
TODO
View File

@ -24,11 +24,10 @@ FUTURE VERSIONS
3.2 release [It's All About Trust] (floating)
# Implement untrusted, dangerous elements/attributes
- Objects and Forms are especially wanted
- Forms are especially wanted
# Implement IDREF support (harder than it seems, since you cannot have
IDREFs to non-existent IDs)
# Frameset XHTML 1.0 and HTML 4.01 doctypes
- Research and implement a "safe" version of the Object module
3.3 release [Error'ed]
# Error logging for filtering/cleanup procedures

View File

@ -68,19 +68,19 @@
</directive>
<directive id="Core.Encoding">
<file name="HTMLPurifier/Encoder.php">
<line>288</line>
<line>315</line>
<line>267</line>
<line>294</line>
</file>
</directive>
<directive id="Test.ForceNoIconv">
<file name="HTMLPurifier/Encoder.php">
<line>293</line>
<line>323</line>
<line>272</line>
<line>302</line>
</file>
</directive>
<directive id="Core.EscapeNonASCIICharacters">
<file name="HTMLPurifier/Encoder.php">
<line>319</line>
<line>298</line>
</file>
</directive>
<directive id="Core.MaintainLineNumbers">
@ -111,37 +111,37 @@
</directive>
<directive id="HTML.BlockWrapper">
<file name="HTMLPurifier/HTMLDefinition.php">
<line>213</line>
<line>222</line>
</file>
</directive>
<directive id="HTML.Parent">
<file name="HTMLPurifier/HTMLDefinition.php">
<line>221</line>
<line>230</line>
</file>
</directive>
<directive id="HTML.AllowedElements">
<file name="HTMLPurifier/HTMLDefinition.php">
<line>238</line>
<line>247</line>
</file>
</directive>
<directive id="HTML.AllowedAttributes">
<file name="HTMLPurifier/HTMLDefinition.php">
<line>239</line>
<line>248</line>
</file>
</directive>
<directive id="HTML.Allowed">
<file name="HTMLPurifier/HTMLDefinition.php">
<line>242</line>
<line>251</line>
</file>
</directive>
<directive id="HTML.ForbiddenElements">
<file name="HTMLPurifier/HTMLDefinition.php">
<line>328</line>
<line>337</line>
</file>
</directive>
<directive id="HTML.ForbiddenAttributes">
<file name="HTMLPurifier/HTMLDefinition.php">
<line>329</line>
<line>338</line>
</file>
</directive>
<directive id="HTML.Trusted">
@ -195,7 +195,7 @@
</directive>
<directive id="URI.Host">
<file name="HTMLPurifier/URIDefinition.php">
<line>63</line>
<line>64</line>
</file>
<file name="HTMLPurifier/URIFilter/DisableExternal.php">
<line>8</line>
@ -203,12 +203,12 @@
</directive>
<directive id="URI.Base">
<file name="HTMLPurifier/URIDefinition.php">
<line>64</line>
<line>65</line>
</file>
</directive>
<directive id="URI.DefaultScheme">
<file name="HTMLPurifier/URIDefinition.php">
<line>71</line>
<line>72</line>
</file>
</directive>
<directive id="URI.AllowedSchemes">
@ -223,12 +223,12 @@
</directive>
<directive id="URI.Disable">
<file name="HTMLPurifier/AttrDef/URI.php">
<line>23</line>
<line>28</line>
</file>
</directive>
<directive id="URI.Munge">
<file name="HTMLPurifier/AttrDef/URI.php">
<line>72</line>
<line>77</line>
</file>
</directive>
<directive id="Core.ColorKeywords">
@ -317,6 +317,9 @@
<file name="HTMLPurifier/HTMLModule/Image.php">
<line>14</line>
</file>
<file name="HTMLPurifier/HTMLModule/SafeObject.php">
<line>19</line>
</file>
</directive>
<directive id="HTML.TidyLevel">
<file name="HTMLPurifier/HTMLModule/Tidy.php">

View File

@ -118,6 +118,8 @@ require 'HTMLPurifier/AttrTransform/ImgSpace.php';
require 'HTMLPurifier/AttrTransform/Lang.php';
require 'HTMLPurifier/AttrTransform/Length.php';
require 'HTMLPurifier/AttrTransform/Name.php';
require 'HTMLPurifier/AttrTransform/SafeObject.php';
require 'HTMLPurifier/AttrTransform/SafeParam.php';
require 'HTMLPurifier/AttrTransform/ScriptRequired.php';
require 'HTMLPurifier/ChildDef/Chameleon.php';
require 'HTMLPurifier/ChildDef/Custom.php';
@ -143,6 +145,7 @@ require 'HTMLPurifier/HTMLModule/Object.php';
require 'HTMLPurifier/HTMLModule/Presentation.php';
require 'HTMLPurifier/HTMLModule/Proprietary.php';
require 'HTMLPurifier/HTMLModule/Ruby.php';
require 'HTMLPurifier/HTMLModule/SafeObject.php';
require 'HTMLPurifier/HTMLModule/Scripting.php';
require 'HTMLPurifier/HTMLModule/StyleAttribute.php';
require 'HTMLPurifier/HTMLModule/Tables.php';
@ -158,6 +161,7 @@ require 'HTMLPurifier/HTMLModule/Tidy/XHTML.php';
require 'HTMLPurifier/Injector/AutoParagraph.php';
require 'HTMLPurifier/Injector/Linkify.php';
require 'HTMLPurifier/Injector/PurifierLinkify.php';
require 'HTMLPurifier/Injector/SafeObject.php';
require 'HTMLPurifier/Lexer/DOMLex.php';
require 'HTMLPurifier/Lexer/DirectLex.php';
require 'HTMLPurifier/Strategy/Composite.php';

View File

@ -112,6 +112,8 @@ require_once $__dir . '/HTMLPurifier/AttrTransform/ImgSpace.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Lang.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Length.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Name.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeObject.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeParam.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/ScriptRequired.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Chameleon.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Custom.php';
@ -137,6 +139,7 @@ require_once $__dir . '/HTMLPurifier/HTMLModule/Object.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Presentation.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Proprietary.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Ruby.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/SafeObject.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Scripting.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/StyleAttribute.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tables.php';
@ -152,6 +155,7 @@ require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/XHTML.php';
require_once $__dir . '/HTMLPurifier/Injector/AutoParagraph.php';
require_once $__dir . '/HTMLPurifier/Injector/Linkify.php';
require_once $__dir . '/HTMLPurifier/Injector/PurifierLinkify.php';
require_once $__dir . '/HTMLPurifier/Injector/SafeObject.php';
require_once $__dir . '/HTMLPurifier/Lexer/DOMLex.php';
require_once $__dir . '/HTMLPurifier/Lexer/DirectLex.php';
require_once $__dir . '/HTMLPurifier/Strategy/Composite.php';

View File

@ -0,0 +1,14 @@
<?php
/**
* Writes default type for all objects. Currently only supports flash.
*/
class HTMLPurifier_AttrTransform_SafeObject extends HTMLPurifier_AttrTransform
{
var $name = "SafeObject";
function transform($attr, $config, $context) {
if (!isset($attr['type'])) $attr['type'] = 'application/x-shockwave-flash';
return $attr;
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* Validates name/value pairs in param tags to be used in safe objects. This
* will only allow name values it recognizes, and pre-fill certain attributes
* with required values.
*
* @note
* This class only supports Flash. In the future, Quicktime support
* may be added.
*
* @warning
* This class expects an injector to add the necessary parameters tags.
*/
class HTMLPurifier_AttrTransform_SafeParam extends HTMLPurifier_AttrTransform
{
public $name = "SafeParam";
private $uri;
function __construct() {
$this->uri = new HTMLPurifier_AttrDef_URI(true); // embedded
}
function transform($attr, $config, $context) {
// If we add support for other objects, we'll need to alter the
// transforms.
switch ($attr['name']) {
// application/x-shockwave-flash
// Keep this synchronized with Injector/SafeObject.php
case 'allowScriptAccess':
$attr['value'] = 'never';
break;
case 'allowNetworking':
$attr['value'] = 'internal';
break;
case 'wmode':
$attr['value'] = 'window';
break;
case 'movie':
$attr['value'] = $this->uri->validate($attr['value'], $config, $context);
break;
// add other cases to support other param name/value pairs
default:
$attr['name'] = $attr['value'] = null;
}
return $attr;
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* A "safe" object module. In theory, objects permitted by this module will
* be safe, and untrusted users can be allowed to embed arbitrary flash objects
* (maybe other types too, but only Flash is supported as of right now).
* Highly experimental.
*/
class HTMLPurifier_HTMLModule_SafeObject extends HTMLPurifier_HTMLModule
{
public $name = 'SafeObject';
public function setup($config) {
// These definitions are not intrinsically safe: the attribute transforms
// are a vital part of ensuring safety.
$max = $config->get('HTML', 'MaxImgLength');
$object = $this->addElement(
'object',
'Inline',
'Optional: param | Flow | #PCDATA',
'Common',
array(
// While technically not required by the spec, we're forcing
// it to this value.
'type' => 'Enum#application/x-shockwave-flash',
'width' => 'Pixels#' . $max,
'height' => 'Pixels#' . $max,
'data' => 'Text'
)
);
$object->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeObject();
$param = $this->addElement('param', false, 'Empty', false,
array(
'id' => 'ID',
'name*' => 'Text',
'value' => 'Text'
)
);
$param->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeParam();
$this->info_injector[] = 'SafeObject';
}
}

View File

@ -0,0 +1,77 @@
<?php
/**
* Adds important param elements to inside of object in order to make
* things safe.
*/
class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector
{
public $name = 'SafeObject';
public $needed = array('object', 'param');
protected $objectStack = array();
protected $paramStack = array();
// Keep this synchronized with AttrTransform/SafeParam.php
protected $addParam = array(
'allowScriptAccess' => 'never',
'allowNetworking' => 'internal',
);
protected $allowedParam = array(
'wmode' => true,
'movie' => true,
);
public function prepare($config, $context) {
parent::prepare($config, $context);
}
public function handleElement(&$token) {
if ($token->name == 'object') {
$this->objectStack[] = $token;
$this->paramStack[] = array();
$new = array($token);
foreach ($this->addParam as $name => $value) {
$new[] = new HTMLPurifier_Token_Empty('param', array('name' => $name, 'value' => $value));
}
$token = $new;
} elseif ($token->name == 'param') {
$nest = count($this->currentNesting) - 1;
if ($nest >= 0 && $this->currentNesting[$nest]->name === 'object') {
$i = count($this->objectStack) - 1;
if (!isset($token->attr['name'])) {
$token = false;
return;
}
$n = $token->attr['name'];
// Check if the parameter is the correct value but has not
// already been added
if (
!isset($this->paramStack[$i][$n]) &&
isset($this->addParam[$n]) &&
$token->attr['name'] === $this->addParam[$n]
) {
// keep token, and add to param stack
$this->paramStack[$i][$n] = true;
} elseif (isset($this->allowedParam[$n])) {
// keep token, don't do anything to it
// (could possibly check for duplicates here)
} else {
$token = false;
}
} else {
// not directly inside an object, DENY!
$token = false;
}
}
}
public function notifyEnd($token) {
if ($token->name == 'object') {
array_pop($this->objectStack);
array_pop($this->paramStack);
}
}
}

View File

@ -4,7 +4,6 @@
* Abstract base token class that all others inherit from.
*/
class HTMLPurifier_Token {
public $type; /**< Type of node to bypass <tt>is_a()</tt>. */
public $line; /**< Line number node was on in source document. Null if unknown. */
/**

View File

@ -0,0 +1,42 @@
<?php
class HTMLPurifier_HTMLModule_SafeObjectTest extends HTMLPurifier_HTMLModuleHarness
{
function setUp() {
parent::setUp();
$this->config->set('HTML', 'DefinitionID', 'HTMLPurifier_HTMLModule_SafeObjectTest');
$def = $this->config->getHTMLDefinition(true);
$def->manager->addModule('SafeObject');
}
function testMinimal() {
$this->assertResult(
'<object></object>',
'<object type="application/x-shockwave-flash"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object>'
);
}
function testYouTube() {
// embed is purposely removed
$this->assertResult(
'<object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/RVtEQxH7PWA&hl=en"></param><embed src="http://www.youtube.com/v/RVtEQxH7PWA&hl=en" type="application/x-shockwave-flash" width="425" height="344"></embed></object>',
'<object width="425" height="344" type="application/x-shockwave-flash"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="movie" value="http://www.youtube.com/v/RVtEQxH7PWA&amp;hl=en" /></object>'
);
}
function testMalicious() {
$this->assertResult(
'<object width="9999999" height="9999999"><param name="allowScriptAccess" value="always" /><param name="movie" value="http://example.com/attack.swf" /></object>',
'<object width="1200" height="1200" type="application/x-shockwave-flash"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="movie" value="http://example.com/attack.swf" /></object>'
);
}
function testFull() {
$this->assertResult(
'<b><object width="425" height="344" type="application/x-shockwave-flash" data="Foobar"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="movie" value="http://www.youtube.com/v/RVtEQxH7PWA&amp;hl=en" /><param name="wmode" value="window" /></object></b>'
);
}
}

View File

@ -0,0 +1,86 @@
<?php
/**
* This test is kinda weird, because it doesn't test the full safe object
* functionality, just a small section of it. Or maybe it's actually the right
* way.
*/
class HTMLPurifier_Injector_SafeObjectTest extends HTMLPurifier_InjectorHarness
{
function setup() {
parent::setup();
$this->config->set('AutoFormat', 'Custom', array(new HTMLPurifier_Injector_SafeObject()));
$this->config->set('HTML', 'Trusted', true);
}
function testPreserve() {
$this->assertResult(
'<b>asdf</b>'
);
}
function testRemoveStrayParam() {
$this->assertResult(
'<param />',
''
);
}
function testEditObjectParam() {
$this->assertResult(
'<object></object>',
'<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object>'
);
}
function testIgnoreStrayParam() {
$this->assertResult(
'<object><param /></object>',
'<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object>'
);
}
function testIgnoreDuplicates() {
$this->assertResult(
'<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object>'
);
}
function testIgnoreBogusData() {
$this->assertResult(
'<object><param name="allowScriptAccess" value="always" /><param name="allowNetworking" value="always" /></object>',
'<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object>'
);
}
function testIgnoreInvalidData() {
$this->assertResult(
'<object><param name="foo" value="bar" /></object>',
'<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object>'
);
}
function testKeepValidData() {
$this->assertResult(
'<object><param name="movie" value="bar" /></object>',
'<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="movie" value="bar" /></object>'
);
}
function testNested() {
$this->assertResult(
'<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><object></object></object>',
'<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object></object>'
);
}
function testNotActuallyNested() {
$this->assertResult(
'<object><p><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></p></object>',
'<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><p></p></object>'
);
}
}

View File

@ -26,6 +26,7 @@ define('HTMLPURIFIER_SCHEMA_STRICT', true); // validate schemas
chdir(dirname(__FILE__));
$php = 'php'; // for safety
ini_set('memory_limit', '64M');
require 'common.php';