本文目录导读:

在PHP项目中处理富文本安全过滤,核心原则是白名单机制(只允许特定标签/属性)而非黑名单,以下是主流方案及代码示例:
专业库方案(推荐)
HTMLPurifier(行业标准)
require_once '/path/to/HTMLPurifier.auto.php';
$config = HTMLPurifier_Config::createDefault();
$config->set('HTML.Allowed', 'p,b,i,u,a[href|target],img[src|alt]'); // 白名单
$config->set('URI.AllowedSchemes', ['http','https','mailto']);
$config->set('AutoFormat.RemoveEmpty', true); // 移除空标签
$purifier = new HTMLPurifier($config);
$clean_html = $purifier->purify($dirty_html);
特点
- 严格遵循W3C标准
- 防御XSS、CSS注入、URL欺骗等
- 可以自定义标签/属性白名单
- 约2MB大小,适合生产环境
轻量级方案(适用于简单场景)
使用strip_tags + 自定义处理
function safeRichText($html) {
// 1. 基础过滤(只保留指定标签)
$allowed = '<p><br><b><i><u><a><img><ul><ol><li><blockquote>';
$html = strip_tags($html, $allowed);
// 2. 移除事件处理器
$html = preg_replace('/\son\w+="[^"]*"/i', '', $html);
// 3. 清理javascript链接
$html = preg_replace('/href="javascript:[^"]*"/i', 'href="#"', $html);
// 4. XSS防御
$html = htmlspecialchars_decode($html); // strip_tags已编码,需要解码后重新处理
// 实际建议使用更全面的过滤
return $html;
}
注意:此方法不够安全,不建议单独用于用户输入。
HTML输入解析过滤法(推荐中级项目)
使用DOMDocument解析后遍历过滤:
function filterRichText($html) {
$allowedTags = ['p','br','strong','em','a','img','ul','ol','li','blockquote'];
$allowedAttrs = ['href','src','alt','title'];
$allowedProtocols = ['http','https','mailto'];
libxml_use_internal_errors(true);
$dom = new DOMDocument();
@$dom->loadHTML('<?xml encoding="UTF-8">' . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
// 递归移除违规元素
$xpath = new DOMXPath($dom);
$nodes = $xpath->query('//*');
foreach ($nodes as $node) {
// 检查标签是否允许
if (!in_array($node->nodeName, $allowedTags)) {
$node->parentNode->removeChild($node);
continue;
}
// 过滤属性
$attrsToRemove = [];
foreach ($node->attributes as $attr) {
if (!in_array($attr->nodeName, $allowedAttrs)) {
$attrsToRemove[] = $attr;
}
}
foreach ($attrsToRemove as $attr) {
$node->removeAttribute($attr->nodeName);
}
// 特殊处理href协议
if ($node->hasAttribute('href')) {
$href = $node->getAttribute('href');
$protocol = parse_url($href, PHP_URL_SCHEME);
if ($protocol && !in_array($protocol, $allowedProtocols)) {
$node->removeAttribute('href');
}
}
}
$result = '';
foreach ($dom->childNodes as $child) {
$result .= $dom->saveHTML($child);
}
return $result;
}
安全配置示例(HTMLPurifier详细配置)
$config = HTMLPurifier_Config::createDefault();
// 白名单设置
$config->set('HTML.Allowed',
'h1,h2,h3,h4,p,br,strong,em,span[style],' .
'ul,ol,li,a[href|title|target],' .
'img[src|alt|width|height],' .
'blockquote,pre,code,hr'
);
// 允许的CSS属性
$config->set('CSS.AllowedProperties', 'color,background-color,font-size,text-align');
// 禁止iframe/object/embed等风险标签
$config->set('HTML.ForbiddenElements', ['iframe','object','embed','applet','script','style']);
// 自动移除无效嵌套
$config->set('AutoFormat.AutoParagraph', true);
$config->set('AutoFormat.RemoveEmpty', true);
// 链接配置
$config->set('URI.AllowedSchemes', ['http','https','mailto','ftp']);
$config->set('URI.DisableExternalResources', false);
$config->set('URI.Host', $_SERVER['HTTP_HOST']); // 限制本域资源
// 编码
$config->set('Core.Encoding', 'UTF-8');
输出时的额外保护
// 存储时保持原始格式 $safe_html = $purifier->purify($input_html); // 输出时(特别是模板渲染时)进行HTML实体编码 echo htmlspecialchars($safe_html, ENT_QUOTES | ENT_HTML5, 'UTF-8');
安全建议
- 绝对不要使用
strip_tags()作为唯一过滤 - 避免黑名单(如过滤
<script>),总有绕过的可能 - 严格控制
style属性的使用(可能有CSS注入) - 不要信任
data:协议(可嵌入恶意内容) - 配置CDN 增加CSP头进行二次防护:
Content-Security-Policy: default-src 'self'; script-src 'none'; style-src 'unsafe-inline'
性能考虑
- HTMLPurifier 处理500字符约需0.1-0.3ms
- 建议对用户输入做缓存(如摘要缓存)>10KB)建议异步处理或使用消息队列
选择方案时请根据项目需求权衡:生产环境强安全建议用HTMLPurifier,简单场景可用DOMDocument+白名单,但切勿自行实现完整HTML解析器。