PHP项目富文本如何安全过滤标签

wen PHP项目 26

本文目录导读:

PHP项目富文本如何安全过滤标签

  1. 专业库方案(推荐)
  2. 轻量级方案(适用于简单场景)
  3. HTML输入解析过滤法(推荐中级项目)
  4. 安全配置示例(HTMLPurifier详细配置)
  5. 输出时的额外保护
  6. 安全建议
  7. 性能考虑

在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');

安全建议

  1. 绝对不要使用 strip_tags() 作为唯一过滤
  2. 避免黑名单(如过滤<script>),总有绕过的可能
  3. 严格控制 style 属性的使用(可能有CSS注入)
  4. 不要信任 data: 协议(可嵌入恶意内容)
  5. 配置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解析器

抱歉,评论功能暂时关闭!