在PHP后台接收表单提交的富文本内容并写入数据库时,开发者往往面临两难:如果原样保存,前端渲染就会执行其中的恶意脚本;如果全部转义,用户精心排版的标题、列表、图片又会变成纯文本。真正可行的方案是入库前做富文本净化,输出时再做上下文转义,两者配合才能兼顾体验与安全。

为什么普通转义不适合富文本
很多初学者习惯在入库时用htmlspecialchars把所有的尖括号转成实体,这样确实能挡住XSS,但代价是富文本不再是富文本。比如用户用编辑器生成的<strong>重点</strong>,入库后变成<strong>重点</strong>,前端展示时就只是一串字符,完全失去了加粗效果。
另一个常见误区是只用addslashes。这个函数仅仅在单引号、双引号、反斜杠前加斜杠,用来防止SQL注入尚可,但对HTML上下文中的XSS毫无作用。攻击者写入<img src=x onerror=alert(1)>,经过addslashes之后标签结构完好,输出到页面依旧会触发弹窗。因此富文本场景必须引入专门的净化逻辑。
使用HTML Purifier净化富文本
HTML Purifier是一个用PHP编写的HTML过滤库,它会根据一份严格的白名单来解析并重建HTML,丢掉所有不在允许范围内的标签和属性。相比正则替换,它基于HTML语义树处理,不容易出现绕过漏洞。下面演示如何通过Composer引入并在新增数据时调用。
先安装依赖:
composer require ezyang/htmlpurifier
接着在接收表单后做净化处理,再写入数据库:
<?php
require_once 'vendor/autoload.php';
use HTMLPurifier;
// 接收富文本
$rawHtml = $_POST['content'] ?? '';
// 配置允许的标签和属性
$config = HTMLPurifier_Config::createDefault();
$config->set('HTML.Allowed', 'p,br,strong,em,ul,ol,li,img[src|alt|width|height],a[href|title]');
$config->set('URI.AllowedSchemes', array('http' => true, 'https' => true, 'mailto' => true));
$config->set('Attr.AllowedFrameTargets', array('_blank' => true));
$purifier = new HTMLPurifier($config);
$cleanHtml = $purifier->purify($rawHtml);
// 此时 $cleanHtml 已剔除 onerror、javascript: 等危险内容
// 使用预处理语句入库,防止SQL注入
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test', 'user', 'pass');
$stmt = $pdo->prepare('INSERT INTO articles (content) VALUES (?)');
$stmt->execute([$cleanHtml]);
?>
上面的配置只允许段落、换行、加粗、斜体、列表、图片和链接,并且图片和链接只接受http、https、mailto协议。像<img src=x onerror=alert(1)>里的onerror会被直接丢弃,<a href="javascript:alert(2)">的javascript协议也不在白名单内,从而从根源上消除了存储型XSS。
需要注意的是,净化后的内容虽然安全,但依然包含HTML标签。如果后续在某些非HTML上下文(如HTML属性值、JS变量)中输出,仍要做对应转义。例如把内容塞进JS字符串时,应使用json_encode而非直接拼接。
输出层的二次防护
即便数据库里存的是净化过的HTML,在模板中直接echo也不一定绝对安全。推荐在视图层统一用htmlspecialchars包裹纯文本字段,而富文本字段因为已经净化,可直接输出,但必须确保它不会被嵌入到别的危险上下文。
下面用一张表对比几种处理方式的差异:
| 处理方式 | 能否保留排版 | 防XSS效果 | 适用场景 |
|---|---|---|---|
| htmlspecialchars入库 | 否 | 强 | 纯文本留言 |
| addslashes入库 | 是 | 无 | 仅防SQL注入 |
| HTML Purifier净化 | 是 | 强 | 富文本文章 |
| 净化+输出转义 | 是 | 最强 | 高安全后台 |
从表中可以看出,只有净化配合合理的输出策略,才能既让富文本正常显示,又堵住各类脚本注入通道。实际项目中建议封装一个统一的过滤函数,在所有新增和编辑接口中强制调用,避免人为遗漏。
常见绕过与注意事项
有些攻击者会利用大小写、嵌套、空字节或非法实体来绕过简陋的正则过滤,例如写<ImG oNeRrOr=alert(1)>。HTML Purifier在解析阶段会把标签名统一规范化,大小写混淆无效。此外,若业务需要允许用户上传图片,应单独校验图片二进制头,不能只信src里的扩展名,防止上传恶意SVG触发脚本。
还有一个容易被忽视的点:富文本中可能包含style属性,而CSS里也能写expression或url(javascript:...)这类老式攻击。因此白名单中最好不要开放style,或者采用更严格的CSS过滤器。保持最小权限原则,只放行业务真正用到的标签和属性,是长久安全的基石。