XSS 安全漏洞 - HTMLPurifier

作为一个合格的 Web 开发工程师,必须遵循一个安全原则:

永远不要信任用户提交的数据。

有两种方法可以避免 XSS 攻击:

  • 第一种,对用户提交的数据进行过滤;
  • 第二种,Web 网页显示时对数据进行特殊处理,一般使用 htmlspecialchars() 输出。

Laravel 的 Blade 语法 {{ }} 会自动调用 PHP htmlspecialchars 函数来避免 XSS 攻击。但是因为我们支持 WYSIWYG 编辑器,我们使用的是 {!! !!} 来打印用户提交的话题内容:

<div class="topic-body">
    {!! $topic->body !!}
</div>

Blade 的{!! !!} 语法是直接数据,不会对数据做任何处理。在我们这种场景下,因为业务逻辑的特殊性,第二种方法不适用,我们将使用第一种方法,对用户提交的数据进行过滤来避免 XSS 攻击。

接下来我们一起解决此问题。

HTMLPurifier

PHP 一个比较合理的解决方案是 HTMLPurifier 。HTMLPurifier 本身就是一个独立的项目,运用『白名单机制』对 HTML 文本信息进行 XSS 过滤。这种过滤机制可以有效地防止各种 XSS 变种攻击。只通过我们认为安全的标签和属性,对于未知的全部过滤。

『白名单机制』指的是使用配置信息来定义『HTML 标签』、『标签属性』和『CSS 属性』数组,在执行 clean() 方法时,只允许配置信息『白名单』里出现的元素通过,其他都进行过滤。

如配置信息:

'HTML.Allowed' => 'div,em,a[href|title|style],ul,ol,li,p[style],br',
'CSS.AllowedProperties'    => 'font,font-size,font-weight,font-style,font-family',

当用户提交时:

<a someproperty="somevalue" href="http://example.com" style="color:#ccc;font-size:16px">
    文章内容<script>alert('Alerted')</script>
</a>

会被解析为:

<a href="http://example.com" style="font-size:16px">
    文章内容
</a>

以下内容因为未指定会被过滤:

  • someproperty 未指定的 HTML 属性
  • color 未指定的 CSS 属性
  • script 未指定的 HTML 标签

HTMLPurifier for Laravel 5

HTMLPurifier for Laravel 是对 HTMLPurifier 针对 Laravel 框架的一个封装。本章节中,我们将使用此扩展包来对用户内容进行过滤。

1. 安装 HTMLPurifier for Laravel 5

使用 Composer 安装:

$ composer require "mews/purifier:~2.0"

2. 配置 HTMLPurifier for Laravel 5

命令行下运行

$ php artisan vendor:publish --provider="Mews\Purifier\PurifierServiceProvider"

请将配置信息替换为以下:

config/purifier.php

<?php
return [
    'encoding'      => 'UTF-8',
    'finalize'      => true,
    'cachePath'     => storage_path('app/purifier'),
    'cacheFileMode' => 0755,
    'settings'      => [
        'user_topic_body' => [
            'HTML.Doctype'             => 'XHTML 1.0 Transitional',
            'HTML.Allowed'             => 'div,b,strong,i,em,a[href|title],ul,ol,ol[start],li,p[style],br,span[style],img[width|height|alt|src],*[style|class],pre,hr,code,h2,h3,h4,h5,h6,blockquote,del,table,thead,tbody,tr,th,td',
            'CSS.AllowedProperties'    => 'font,font-size,font-weight,font-style,margin,width,height,font-family,text-decoration,padding-left,color,background-color,text-align',
            'AutoFormat.AutoParagraph' => true,
            'AutoFormat.RemoveEmpty'   => true,
        ],
    ],
];

配置里的 user_topic_body 是我们为话题内容定制的,配合 clean() 方法使用:

$topic->body = clean($topic->body, 'user_topic_body');

开始过滤

一切准备就绪,现在我们只需要在数据入库前进行过滤即可:

app/Observers/TopicObserver.php

<?php

namespace App\Observers;

use App\Models\Topic;

// creating, created, updating, updated, saving,
// saved,  deleting, deleted, restoring, restored

class TopicObserver
{
    public function saving(Topic $topic)
    {
        $topic->body = clean($topic->body, 'user_topic_body');

        $topic->excerpt = make_excerpt($topic->body);
    }
}

浏览器访问话题创建页面,并填入测试内容:

file

提交表单后的结果,只剩下正常内容,并且没有弹框:
file

数据库里可以看到我们提交的 XSS 注入内容已被过滤:
file

HTMLPurifier 把白名单里设定的内容通过,script 标签未设定,自动过滤掉。

本作品采用《CC 协议》,转载必须注明作者和本文链接
老哥以后是要做 CTO 的人,这些技术怎么能不会呢?
本帖由系统于 5年前 自动加精
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
讨论数量: 20
jaak

用这个包 过滤后 怎么多了 p标签 。。。

6年前 评论

Yii 2.0 好像就是使用的该扩展包。

6年前 评论
jaak

用这个包 过滤后 怎么多了 p标签 。。。

6年前 评论

@jaak

标签是编辑器加的, 这个包是过滤掉,html dom 结构之中你不希望展示的标签,例如用 户输入的<script></script>标签等, 以达到你所希望的结果

6年前 评论
jaak

@liguanjie8 已解决 配置一下就行了

6年前 评论

@jaak 老铁, 可否贴出你是如何配置的 ?

6年前 评论

此文章有个错误的思路就是, 不应该直接转 markdown文本, 而是应该 将 markdown转为 html后对 html执行 xss过滤

6年前 评论
jaak

@GucciLee 我用了个clean函数 ['AutoFormat.AutoParagraph' => false] 如下:

$str = clean($request->str, ['AutoFormat.AutoParagraph' => false]);
6年前 评论

@GucciLee 请问我如何添加video的白名单呢?
我现在过滤富文本编辑器,然后把视频的video标签一起给我删除了,但是我公司要求必须在富文本编辑器里面上传视频;

file
我在这里面添加video标签,报错?
请指教!谢谢

5年前 评论

@hanghang

div 前面增加 video,即可
file

5年前 评论

@GucciLee

file

file

file
不行!直接就报错

5年前 评论
西巴以及 1年前

@hanghang
我没有遇到你说的报错, 测试代码如下:
控制器:

.
.
.
        dump('未过滤文本 -------------------------------------');
        $body = '
            <script type="text/javascript">alert("1")</script>
            <h2>我是H1</h2>
            <video class="video" width="480">
                <source src="http://www.runoob.com/try/demo_source/mov_bbb.mp4" type="video/mp4">
                <source src="http://www.runoob.com/try/demo_source/mov_bbb.ogg" type="video/ogg">
                你的浏览器不支持 HTML5 video.
            </video>            
            ';
        dump($body);

        dump('执行过滤文本 -------------------------------------');
        $rtn = clean($body, 'learnku');
        dd($rtn);
.
.
.

config/purifier.php

.
.
.
'settings'=> [
    'learnku'=> [
            'HTML.Doctype'             => 'HTML 4.01 Transitional',
            'HTML.Allowed'             => 'video,source[src|type],div,b,strong,i,em,a[href|title],ul,ol,ol[start],li,p[style],br,span[style],img[width|height|alt|src],*[style|class],pre,hr,code,h2,h3,h4,h5,h6,blockquote,del,table,thead,tbody,tr,th,td',
            'CSS.AllowedProperties'    => 'font,font-size,font-weight,font-style,margin,width,height,font-family,text-decoration,padding-left,color,background-color,text-align',
            'AutoFormat.AutoParagraph' => false,
            'AutoFormat.RemoveEmpty'   => false,
    ]
]

讲解:
HTML.Allowed 字段 增加了 video,source[src|type],

结果:

Laravel

5年前 评论

@hanghang
如果还是不清楚可以加我好友 QQ 869904300

5年前 评论

@jaak 请问你是如何解决这个问题的呢?

5年前 评论
xiatian

建议所有字段都开启过滤,非富文本的不用自动添加段落p标签。
配置基本都要自定义,自带的不够用。

5年前 评论

大佬问下,富文本里面的图片路径怎么指定域名列表,不在这个域名列表里面的图片过滤掉

5年前 评论

@xuzili

本人能力有限,无法为你解答这个问题:
推荐你看一下配置文档

如果解决了请贴下答案在下方,用于帮助更多的后来者,谢谢。

5年前 评论
jaak

@feiffy

我用了个 clean 函数 ['AutoFormat.AutoParagraph' => false] 如下:

 $str = clean($request->str, ['AutoFormat.AutoParagraph' => false]);
5年前 评论

请教一下各位老大,可以过滤<这样的转义字符吗?
我用的富文本编辑器,比如输入了

<button>按钮</button>

数据库里得到的是

&lt;button&gt;按钮&lt;/button&gt;

这样Purifier就没法过滤了,在页面显示的时候就是个按钮。现在有些纠结啊。

5年前 评论
kakaxi 3年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!