【问题标题】:HTML sanitizer using regex & security issues使用正则表达式和安全问题的 HTML sanitizer
【发布时间】:2010-12-22 14:10:21
【问题描述】:

我知道用正则表达式解析 HTML 很糟糕,而且它不能适用于所有情况(在 Stack Overflow 上有很多关于此的主题)。 但我仍然想尝试使用基于白名单方法的正则表达式来清理 HTML。

我想向您展示我的代码(用 PHP 5.2 编写)。 它似乎工作正常,但我仍然想知道是否存在安全问题。

那么,我是不是搞错了?

基本原理是使用Html_Sanitizer::sanitize()

  1. 该函数首先用标记替换没有属性的允许标记。然后解析带有属性的标签并用令牌替换它们。
  2. 然后解析 HTML 标记以检测允许的属性(使用 cleanTag 函数)。因此,HTML 标记以(让希望)安全的方式重新构建。
  3. htmlspecialchars 用于确保剩余代码是干净的
  4. 令牌被替换为安全标签。

代码:

class Html_Sanitizer
{
    const VALIDATOR_CSS_UNIT = '(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0';
    const VALIDATOR_URL = 'http://\\S+';
    const VALIDATOR_CSS_PROPERTY = '[a-z\-]+';
    const VALIDATOR_STYLE = '[^"]*';

    protected static $_tags = 'a|b|blockquote|br|cite|d[ldt]|h[1-6]|i|img|li|ol|p|span|strong|u|ul';

    protected static $_attributes = array(
        'img' => array(
            'width' => '[0-9]+',
            'height' => '[0-9]+',
            'src' => self::VALIDATOR_URL,
            'style' => self::VALIDATOR_STYLE
            ),
        'span' => array(
            'style' => self::VALIDATOR_STYLE
            ),
        'p' => array(
            'style' => self::VALIDATOR_STYLE
            ),
        'a' =>  array(
            'href' => self::VALIDATOR_URL
            )
    );

    protected static $_styleValidators = array(
        'color' => '(\#[a-fA-F0-9]+)|([a-z ]+)',
        'background-color' => '\#[a-zA-Z0-9]+',
        'font-style' => '(normal|italic|oblique)',
        'font-size' => '[\-a-z]+',
        'margin-left' => self::VALIDATOR_CSS_UNIT,
        'margin-right' => self::VALIDATOR_CSS_UNIT,
        'text-align' => '(left|right|center|justify)',
        'text-indent' => self::VALIDATOR_CSS_UNIT,
        'text-decoration' => '(none|overline|underline|blink|line-through)',
        'width' => self::VALIDATOR_CSS_UNIT,
        'height' => self::VALIDATOR_CSS_UNIT
    );

    public static function sanitize($str)
    {
        $tokens = array();

        //tokenize opening tags with no attributes
        $pattern = '#<(/)?('. self::$_tags .')>#';
        $replace = '__SAFE_TAG_$1$2__';
        $str = preg_replace($pattern, $replace, $str);

        // tokenize tags with attributes
        $pattern = '#<('. self::$_tags .')(?:\s+(?:[a-z]+)="(?:[^"\\\]*(?:\\\"[^"\\\]*)*)")*\s*(/)?>#';
        preg_match_all($pattern, $str, $matches, PREG_SET_ORDER);
        foreach($matches as $i => $match) {
            $tokens[$i] = self::cleanTag($match[1], $match[0]);
            $str = str_replace($match[0], '__SAFE_TOKEN_'.$i.'__', $str);
        }

        $str = htmlspecialchars($str);

        foreach ($tokens as $i => $cleanTag) {
            $str = str_replace('__SAFE_TOKEN_'.$i.'__', $cleanTag, $str);
        }

        $pattern = '#__SAFE_TAG_(/?(?:'. self::$_tags .'))__#';
        $replace = '<$1>';
        $str = preg_replace($pattern, $replace, $str);

        return $str;
    }

    public static function cleanTag($tag, $str)
    {
        $cleanTag = '<' . $tag;

        if ($tag === 'a') {
            $cleanTag .= ' rel="nofolow" target="_blank"';
        }

        if (isset(self::$_attributes[$tag])) {
            foreach(self::$_attributes[$tag] as $attr => $attrPattern) {
                $pattern = '#'.$attr.'="('. $attrPattern .')"#';
                preg_match($pattern, $str, $match);
                if (isset($match[1])) {
                    if ($attr == 'style') {
                        $cleanTag .= ' style="' . self::cleanStyle($match[1]) . '"';
                    } else {
                        $cleanTag .= ' ' . $attr . '="' . $match[1] . '"';
                    }
                }
            }
        }

        if ($tag === 'img') {
            $cleanTag .= ' /';
        }

        $cleanTag .= '>';
        return $cleanTag;
    }

    public static function cleanStyle($style)
    {
        $cleanStyle = '';

        foreach(self::$_styleValidators as $stl => $stlPattern) {
            $pattern = '#[; ]?' . $stl . '\s*:\s*(' . $stlPattern . ')\s*;#i';
            preg_match($pattern, $style, $match);
            if (isset($match[1])) {
                $cleanStyle .= ($cleanStyle ? ' ' : '') . $stl . ':' . $match[1] . ';';
            }
        }

        return $cleanStyle;
    }
}

【问题讨论】:

  • ,不要去那里。请转至此处:htmlpurifier.org.
  • 白名单是一种非常有效的方法。看起来不错,但是用__SAFE_TAG_ 标记来回重写是不合适的。而且我同意,当 HTML_Purifier 已经存在时,它不是明智的使用时间,它涵盖了所有边缘情况。
  • 感谢您的评论,但编码不仅仅是时间问题。有时它也很有趣。而且我喜欢编码,它可以帮助我更好地了解其他人做了什么,以及真正的问题在哪里。此外,我现在已经开发了三年,我使用了很多框架,最后我总是不得不自己做,因为没有计划修复一个错误,并且我花了更多的时间来修补现有的框架而不是开发我自己的完全符合我需要的东西。但是是的,你是对的,我不应该这样做:)

标签: php html regex sanitization whitelist


【解决方案1】:

我可以使用与样式相关的属性来破坏您的网站。在某些浏览器中,通过设置巨大的宽度和高度组合来使浏览器崩溃。

我可以使用样式来做更多的破坏。

我可以使用图像 SRC 属性来跟踪您的用户移动。监视他们并赚钱。

如果您按照预期完美编码,这些都是我可以对您的课程做的事情。哪些可能性是您没有做到的,并且可能还有其他漏洞可以访问更狡猾的东西。

【讨论】:

  • 好吧thanx的宽度和高度。我应该添加一些额外的验证器。我知道风格有点敏感,我应该在这方面更加谨慎。但是对不起(我是法国人),我不明白你的最后一句话。 “我没有按预期完美编码”是什么意思。你的意思是我解释的和我实际编码的有区别吗?
  • 没有。我没有看你的代码。我只是假设这是你说的。我的意思是,您正在尝试解决一个移动目标且从根本上无法解决的问题。
猜你喜欢
  • 1970-01-01
  • 2015-11-25
  • 1970-01-01
  • 2014-07-22
  • 2011-12-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-18
相关资源
最近更新 更多