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 的人,这些技术怎么能不会呢?
本帖由系统于 4年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 20
jaak

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

5年前 评论

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

5年前 评论
jaak

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

5年前 评论

@jaak

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

5年前 评论
jaak

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

5年前 评论

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

5年前 评论

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

5年前 评论
jaak

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

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

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

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

4年前 评论

@hanghang

div 前面增加 video,即可
file

4年前 评论

@GucciLee

file

file

file
不行!直接就报错

4年前 评论
西巴以及 10个月前

@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

4年前 评论

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

4年前 评论

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

4年前 评论
xiatian

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

4年前 评论

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

4年前 评论

@xuzili

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

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

4年前 评论
jaak

@feiffy

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

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

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

<button>按钮</button>

数据库里得到的是

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

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

4年前 评论
kakaxi 2年前

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