首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP HTML 屬性過濾類:XSS 防護、白名單機制、標籤例外處理

     編輯:PHP 相關知識

PHP HTML 屬性過濾類 HtmlAttributeFilter 實作

使用者輸入 HTML 內容時,需過濾危險屬性(onclick、style 等)防止 XSS。HtmlAttributeFilter 提供白名單機制、標籤級例外、忽略標籤等靈活設定。

核心功能

  • 全域白名單:允許保留的屬性(如 id、class、title)
  • 標籤例外:特定標籤額外允許屬性(如 a 標籤允許 href)
  • 忽略標籤:完全不處理特定標籤(如 img 保留所有屬性)
  • 大小寫不敏感:自動轉小寫比對

HtmlAttributeFilter 類別程式碼

<?php
/** HTML 屬性過濾器
 * Date: 2013-09-22
 * Author: fdipzone
 * Ver: 1.0
 */
class HtmlAttributeFilter {
    private $_str = '';
    private $_allow = array();      // 全域允許屬性
    private $_exception = array();  // 標籤例外
    private $_ignore = array();     // 忽略標籤

    /** 主處理入口 */
    public function strip($str) {
        $this->_str = $str;
        if (is_string($this->_str) && strlen($this->_str) > 0) {
            $this->_str = strtolower($this->_str);
            $res = $this->findElements();
            if (is_string($res)) return $res;
            $nodes = $this->findAttributes($res);
            $this->removeAttributes($nodes);
        }
        return $this->_str;
    }

    /** 設定全域允許屬性 */
    public function setAllow($param = array()) { $this->_allow = $param; }

    /** 設定標籤例外 */
    public function setException($param = array()) { $this->_exception = $param; }

    /** 設定忽略標籤 */
    public function setIgnore($param = array()) { $this->_ignore = $param; }

    /** 尋找所有標籤元素 */
    private function findElements() {
        $nodes = array();
        preg_match_all("/<([^ !\/\>\n]+)([^>]*)>/i", $this->_str, $elements);
        foreach ($elements[1] as $key => $tag) {
            if ($elements[2][$key]) {
                $literal = $elements[0][$key];
                $name = $elements[1][$key];
                $attrs = $elements[2][$key];
                if (is_array($this->_ignore) && !in_array($name, $this->_ignore)) {
                    $nodes[] = array('literal'=>$literal, 'name'=>$name, 'attributes'=>$attrs);
                }
            }
        }
        return $nodes ? $nodes : $this->_str;
    }

    /** 解析屬性 */
    private function findAttributes($nodes) {
        foreach ($nodes as &$node) {
            preg_match_all("/([^ =]+)\s*=\s*[\"|']{0,1}([^\"']*)[\"|']{0,1}/i", $node['attributes'], $attrs);
            $atts = array();
            if ($attrs[1]) {
                foreach ($attrs[1] as $k => $aname) {
                    $atts[] = array(
                        'literal' => $attrs[0][$k],
                        'name' => $attrs[1][$k],
                        'value' => $attrs[2][$k]
                    );
                }
            }
            $node['attributes'] = $atts;
        }
        return $nodes;
    }

    /** 移除不允許屬性並重建標籤 */
    private function removeAttributes($nodes) {
        foreach ($nodes as $node) {
            $new_attrs = '';
            if (is_array($node['attributes'])) {
                foreach ($node['attributes'] as $attr) {
                    $allowed = in_array($attr['name'], $this->_allow);
                    $excepted = $this->isException($node['name'], $attr['name']);
                    if ($allowed || $excepted) {
                        $new_attrs = $this->createAttributes($new_attrs, $attr['name'], $attr['value']);
                    }
                }
            }
            $replacement = $new_attrs ? "<{$node['name']} $new_attrs>" : "<{$node['name']}>";
            $this->_str = preg_replace('/' . $this->protect($node['literal']) . '/', $replacement, $this->_str);
        }
    }

    /** 判斷標籤例外 */
    private function isException($tag, $attr) {
        return isset($this->_exception[$tag]) && in_array($attr, $this->_exception[$tag]);
    }

    /** 建立屬性字串 */
    private function createAttributes($str, $name, $value) {
        return $str . ($str ? ' ' : '') . "$name=\"$value\"";
    }

    /** 正則特殊字元跳脫 */
    private function protect($str) {
        $conv = array(
            '^'=>'\^', '['=>'\[', '.'=>'\.', '$'=>'\$', '{'=>'\{', '*'=>'\*',
            '('=>'\(', '\\'=>'\\\\', '/'=>'\/', '+'=>'\+', ')'=>'\)', '|'=>'\|',
            '?'=>'\?', '<'=>'\<', '>'=>'\>'
        );
        return strtr($str, $conv);
    }
}
?>

使用範例

<?php
require('HtmlAttributeFilter.class.php');

$str = '<div class="bd" id="main"><ul class="list"><li><img src="pic.jpg" width="100"><a href="https://example.com" onclick="alert(1)">連結</a></li></ul></div>';

$obj = new HtmlAttributeFilter();
// 全域允許 id
$obj->setAllow(array('id'));
// 標籤例外:a 允許 href,ul 允許 class
$obj->setException(array(
    'a' => array('href'),
    'ul' => array('class')
));
// 忽略 img 標籤(保留所有屬性)
$obj->setIgnore(array('img'));

echo '原始:<br>';
echo htmlspecialchars($str) . '<br><br>';
echo '過濾後:<br>';
echo htmlspecialchars($obj->strip($str));
?>

輸出結果

原始:
<div class="bd" id="main"><ul class="list"><li><img src="pic.jpg" width="100"><a href="https://example.com" onclick="alert(1)">連結</a></li></ul></div>

過濾後:
<div id="main"><ul class="list"><li><img src="pic.jpg" width="100"><a href="https://example.com">連結</a></li></ul></div>
  • div:移除 class,保留 id(全域白名單)
  • ul:保留 class(標籤例外)
  • img:完全保留(忽略標籤)
  • a:保留 href(標籤例外),移除 onclick(危險屬性)

安全性補充

  • 此類為層級過濾,建議搭配 HTMLPurifier 做完整 XSS 防護
  • 輸出時仍需視情境做 htmlspecialchars 或 CSP 標頭
  • 不處理 CSS expression()、url(javascript:) 等樣式型攻擊

現代替代方案

新專案強烈建議使用 HTMLPurifier:

require_once 'HTMLPurifier.auto.php';
$config = HTMLPurifier_Config::createDefault();
$config->set('HTML.Allowed', 'div[id],ul[class],li,img[src|width|height],a[href]');
$purifier = new HTMLPurifier($config);
$clean = $purifier->purify($dirty_html);
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved