【问题标题】:remove script tag from HTML content从 HTML 内容中删除脚本标签
【发布时间】:2011-10-31 03:30:30
【问题描述】:

我正在使用 HTML Purifier (http://htmlpurifier.org/)

我只想删除<script> 标签。 我不想删除内联格式或任何其他内容。

我怎样才能做到这一点?

还有一件事,还有其他方法可以从 HTML 中删除脚本标签

【问题讨论】:

  • 请记住,脚本标签并不是 HTML 唯一易受攻击的部分。
  • 是的,我也知道其他易受攻击的部分,但我只需要删除脚本标签
  • 阅读this。它会帮助你
  • @Jose 地狱没有。阅读此stackoverflow.com/questions/1732348/… 没有用于解析 html 的正则表达式
  • @Rikudo 好吧...如果他需要使用正则表达式来删除 html 标签...应该是有原因的。感谢您的链接!

标签: php regex htmlpurifier


【解决方案1】:

因为这个问题被标记为,所以在这种情况下我将用穷人的解决方案来回答:

$html = preg_replace('#<script(.*?)>(.*?)</script>#is', '', $html);

然而,正则表达式并不用于解析 HTML/XML,即使你编写了 完美 表达式,它最终也会崩溃,这是不值得的,尽管在某些情况下快速修复一些问题很有用标记,以及快速修复,忘记安全。仅对您信任的内容/标记使用正则表达式。

请记住,用户输入的任何内容都应被视为不安全

更好的解决方案是使用为此设计的DOMDocument。 这是一个 sn-p,它展示了这样做是多么容易、干净(与正则表达式相比)、(几乎)可靠和(几乎)安全:

<?php

$html = <<<HTML
...
HTML;

$dom = new DOMDocument();

$dom->loadHTML($html);

$script = $dom->getElementsByTagName('script');

$remove = [];
foreach($script as $item)
{
  $remove[] = $item;
}

foreach ($remove as $item)
{
  $item->parentNode->removeChild($item); 
}

$html = $dom->saveHTML();

我故意删除了 HTML,因为即使这样也会bork

【讨论】:

  • -1 用于 RegExp 解决方案。见this discussion
  • 我很久以前就看到了那个讨论,你应该阅读它,而不是仅仅看到它。
  • 虽然我很欣赏你冷漠的回应,但我不赞成你的回答的理由是合理的。请参阅this gist 以获取绕过您的正则表达式的精心制作的脚本标签。公平地说,这可能更多是您的特定正则表达式的缺点,而不是完全放弃正则表达式的理由。但是,对我来说仍然很有趣。
  • 如果您想采用正则表达式路线,请确保多次运行prey_replace,直到输出不再变化(从@ParijatKalia 获取示例输入)。
  • @Arth 因为你不会得到正确的结果(迭代器的行为不像预期的那样),请参阅this 评论。
【解决方案2】:

使用 PHP DOMDocument 解析器。

$doc = new DOMDocument();

// load the HTML string we want to strip
$doc->loadHTML($html);

// get all the script tags
$script_tags = $doc->getElementsByTagName('script');

$length = $script_tags->length;

// for each tag, remove it from the DOM
for ($i = 0; $i < $length; $i++) {
  $script_tags->item($i)->parentNode->removeChild($script_tags->item($i));
}

// get the HTML string back
$no_script_html_string = $doc->saveHTML();

这让我使用了以下 HTML 文档:

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <title>
            hey
        </title>
        <script>
            alert("hello");
        </script>
    </head>
    <body>
        hey
    </body>
</html>

请记住,DOMDocument 解析器需要 PHP 5 或更高版本。

【讨论】:

  • +0 我讨厌听到关于正则表达式和 HTML 的讨论。在一些非常特殊的场合使用正则表达式应该没问题。就我而言,我收到此错误:Warning: DOMDocument::loadHTML() [domdocument.loadhtml]: Tag myCustomTag invalid in Entity。什么都试过了。我要做的就是删除应用程序一小部分的脚本标签(无需花费更多时间)。我将使用 preg_replace 就是这样。我不想再听到这件事了。 :)
  • 查看我对所选最佳答案的评论。我希望看到编码人员涵盖一般情况,因为恶意用户可以变得非常聪明。但是,您是对的:例如,在开发内部应用程序时,可以忽略此类漏洞并使用正则表达式。
  • @Xeoncross 谢谢!下次我有机会研究这个问题时,我会尝试一下。目前我正忙于其他代码,不想挖掘那些东西:)。
  • DOMDocument 和 SimpleXML 可用于加载文档根目录之外的文件。使用 libxml_disable_entity_loader(true) 禁用 libxml 的此功能。 php.net/manual/en/function.libxml-disable-entity-loader.php
  • 一旦你有一个空标签,这个代码就会给出'Fatal error: Call to a member function removeChild() on null',比如&lt;script src="..."&gt;&lt;/script&gt;
【解决方案3】:
$html = <<<HTML
...
HTML;
$dom = new DOMDocument();
$dom->loadHTML($html);
$tags_to_remove = array('script','style','iframe','link');
foreach($tags_to_remove as $tag){
    $element = $dom->getElementsByTagName($tag);
    foreach($element  as $item){
        $item->parentNode->removeChild($item);
    }
}
$html = $dom->saveHTML();

【讨论】:

  • 我赞成这个回复,因为一方面它干净简单,而且它也提醒我 iframe 也可能给我带来麻烦。
  • 另外,我刚刚意识到,这添加了 doctype、html 和 body 标签,这对当前问题来说还可以,但对我来说不行,但我只需要更改一行(作为顶部评论在 saveHTML php.net 页面上说):$dom-&gt;loadHTML($html,LIBXML_HTML_NOIMPLIED|LIBXML_HTML_NODEFDTD);
【解决方案4】:

操作字符串的简单方法。

function stripStr($str, $ini, $fin)
{
    while (($pos = mb_stripos($str, $ini)) !== false) {
        $aux = mb_substr($str, $pos + mb_strlen($ini));
        $str = mb_substr($str, 0, $pos);
        
        if (($pos2 = mb_stripos($aux, $fin)) !== false) {
            $str .= mb_substr($aux, $pos2 + mb_strlen($fin));
        }
    }

    return $str;
}

【讨论】:

  • @Someone_who_likes_SE 是的,当然。你可以使用 stripos 和 substr 代替 mb_stripos 和 mb_substr,但我更喜欢使用 MB 函数,它们更可靠。
  • 这一切都很好,但这里有一个严重的缺陷。请注意,您不知道您有哪个输入。如果 $fin 不在 $str (或 $aux) 中,那么这里有一个完美的循环。调试愉快!有几个选项可以调整此代码以应对该缺陷。我会留给你修复它。
  • @kklepper 我已经修改了它,现在如果没有找到 $fin ,它会从 $ini 切到字符串的末尾。问候!
【解决方案5】:

更短:

$html = preg_replace("/&lt;script.*?\/script&gt;/s", "", $html);

当做正则表达式时,事情可能会出错,所以这样做更安全:

$html = preg_replace("/&lt;script.*?\/script&gt;/s", "", $html) ? : $html;

这样当“意外”发生时,我们得到的是原始的 $html 而不是空字符串。

【讨论】:

    【解决方案6】:
    • 这是 ClandestineCoderBinh WPO 的合并。

    脚本标签箭头的问题是它们可以有多个变体

    例如。 (&lt; = &amp;amp;lt;) & ( > = &amp;amp;gt; = &amp;amp;gt;)

    所以与其创建一个包含无数变体的模式数组, 恕我直言,更好的解决方案是

    return preg_replace('/script.*?\/script/ius', '', $text)
           ? preg_replace('/script.*?\/script/ius', '', $text)
           : $text;
    

    这将删除任何看起来像 script.../script 的东西,无论箭头代码/变体如何,您都可以在这里进行测试 https://regex101.com/r/lK6vS8/1

    【讨论】:

      【解决方案7】:

      修改 ctf0 答案的示例。这应该只执行一次 preg_replace 并且还检查错误并阻止正斜杠的字符代码。

      $str = '<script> var a - 1; <&#47;script>'; 
      
      $pattern = '/(script.*?(?:\/|&#47;|&#x0002F;)script)/ius';
      $replace = preg_replace($pattern, '', $str); 
      return ($replace !== null)? $replace : $str;  
      

      如果您使用的是 php 7,您可以使用 null coalesce 运算符来进一步简化它。

      $pattern = '/(script.*?(?:\/|&#47;|&#x0002F;)script)/ius'; 
      return (preg_replace($pattern, '', $str) ?? $str); 
      

      【讨论】:

      • 这确实有一个失败,那就是如果有人使用 html 中脚本文件夹中的文件,例如:.. 。这将创建一个捕获,将删除它们之间的所有内容。
      【解决方案8】:

      如果有的话,我会使用 BeautifulSoup。让这类事情变得非常简单。

      不要尝试使用正则表达式。那就是疯狂。

      【讨论】:

      • 为什么不用正则表达式来做这个简单的操作呢?
      • @webarto 见this discussion
      • @Alex,我知道,但为什么不在这里使用呢?
      • 因为我链接到的答案。这不安全或任何形式的保证。 HTML/XML 是更好的解决方案。
      【解决方案9】:

      我一直在努力解决这个问题。我发现你真的只需要一个功能。爆炸('>', $html);任何标签的唯一共同点是 。然后通常是引号(")。一旦找到共同点,您就可以轻松提取信息。这就是我想出的:

      $html = file_get_contents('http://some_page.html');
      
      $h = explode('>', $html);
      
      foreach($h as $k => $v){
      
          $v = trim($v);//clean it up a bit
      
          if(preg_match('/^(<script[.*]*)/ius', $v)){//my regex here might be questionable
      
              $counter = $k;//match opening tag and start counter for backtrace
      
              }elseif(preg_match('/([.*]*<\/script$)/ius', $v)){//but it gets the job done
      
                  $script_length = $k - $counter;
      
                  $counter = 0;
      
                  for($i = $script_length; $i >= 0; $i--){
                      $h[$k-$i] = '';//backtrace and clear everything in between
                      }
                  }           
              }
      for($i = 0; $i <= count($h); $i++){
          if($h[$i] != ''){
          $ht[$i] = $h[$i];//clean out the blanks so when we implode it works right.
              }
          }
      $html = implode('>', $ht);//all scripts stripped.
      
      
      echo $html;
      

      我认为这真的只适用于脚本标签,因为你永远不会有嵌套的脚本标签。当然,您可以轻松添加更多执行相同检查和收集嵌套标签的代码。

      我称之为手风琴编码。内爆();爆炸();如果你有一个共同点,这是让你的逻辑流畅的最简单的方法。

      【讨论】:

      • 您不应使用正则表达式在 HTML 代码中查找脚本标签。使用 DOMDocument 解析整个文档,找到要移除的脚本标签
      【解决方案10】:

      这是 Dejan Marjanovic 答案的简化变体:

      function removeTags($html, $tag) {
          $dom = new DOMDocument();
          $dom->loadHTML($html);
          foreach (iterator_to_array($dom->getElementsByTagName($tag)) as $item) {
              $item->parentNode->removeChild($item);
          }
          return $dom->saveHTML();
      }
      

      可用于删除任何类型的标签,包括&lt;script&gt;

      $scriptlessHtml = removeTags($html, 'script');
      

      【讨论】:

        【解决方案11】:

        使用 str_replace 函数将它们替换为空格或其他东西

        $query = '<script>console.log("I should be banned")</script>';
        
        $badChar = array('<script>','</script>');
        $query = str_replace($badChar, '', $query);
        
        echo $query; 
        //this echoes console.log("I should be banned")
        

        ?>

        【讨论】:

        • 我不知道为什么人们一直争论 DOMDocument 和某种正则表达式是“解决方案”还是“不是解决方案”。我喜欢这个人的回答——简单地使用 php 的 str_replace (但由于不区分大小写,我会使用 str_ireplace)。除非您有大量要删除的内容,否则这似乎是最简单和最有效的解决方案。我告诉我的用户不能粘贴或输入那种东西。如果他们这样做,那么运气不好——它将被删除。
        • 此解决方案将 javascript 代码保留在 html 字符串中。这是一个笑话,不是一个好的解决方案!但是,您可以从“
        • i将“
        【解决方案12】:
        function remove_script_tags($html){
            $dom = new DOMDocument();
            $dom->loadHTML($html);
            $script = $dom->getElementsByTagName('script');
        
            $remove = [];
            foreach($script as $item){
                $remove[] = $item;
            }
        
            foreach ($remove as $item){
                $item->parentNode->removeChild($item);
            }
        
            $html = $dom->saveHTML();
            $html = preg_replace('/<!DOCTYPE.*?<html>.*?<body><p>/ims', '', $html);
            $html = str_replace('</p></body></html>', '', $html);
            return $html;
        }
        

        Dejan 的回答很好,但是 saveHTML() 添加了不必要的 doctype 和 body 标签,这应该去掉它。见https://3v4l.org/82FNP

        【讨论】:

        【解决方案13】:

        试试这个完整灵活的解决方案。它运行良好,部分基于 some 以前的答案,但包含额外的验证检查,并从 loadHTML(...) 函数中删除了额外的 implied HTML。它分为两个独立的函数(一个具有先前的依赖项,因此不要重新排序/重新排列),因此您可以将它与多个要同时删除的 HTML 标签一起使用(即不仅仅是'script' 标签)。例如removeAllInstancesOfTag(...) 函数接受标签名称的array,或者可选地只接受一个string。所以,废话不多说,代码如下:

        
        /* Remove all instances of a particular HTML tag (e.g. <script>...</script>) from a variable containing raw HTML data. [BEGIN] */
        
        /* Usage Example: $scriptless_html = removeAllInstancesOfTag($html, 'script'); */
        
        if (!function_exists('removeAllInstancesOfTag'))
            {
                function removeAllInstancesOfTag($html, $tag_nm)
                    {
                        if (!empty($html))
                            {
                                $html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'); /* For UTF-8 Compatibility. */
                                $doc = new DOMDocument();
                                $doc->loadHTML($html,LIBXML_HTML_NOIMPLIED|LIBXML_HTML_NODEFDTD|LIBXML_NOWARNING);
        
                                if (!empty($tag_nm))
                                    {
                                        if (is_array($tag_nm))
                                            {
                                                $tag_nms = $tag_nm;
                                                unset($tag_nm);
        
                                                foreach ($tag_nms as $tag_nm)
                                                    {
                                                        $rmvbl_itms = $doc->getElementsByTagName(strval($tag_nm));
                                                        $rmvbl_itms_arr = [];
        
                                                        foreach ($rmvbl_itms as $itm)
                                                            {
                                                                $rmvbl_itms_arr[] = $itm;
                                                            };
        
                                                        foreach ($rmvbl_itms_arr as $itm)
                                                            {
                                                                $itm->parentNode->removeChild($itm);
                                                            };
                                                    };
                                            }
                                        else if (is_string($tag_nm))
                                            {
                                                $rmvbl_itms = $doc->getElementsByTagName($tag_nm);
                                                $rmvbl_itms_arr = [];
        
                                                foreach ($rmvbl_itms as $itm)
                                                    {
                                                        $rmvbl_itms_arr[] = $itm;
                                                    };
        
                                                foreach ($rmvbl_itms_arr as $itm)
                                                    {
                                                        $itm->parentNode->removeChild($itm); 
                                                    };
                                            };
                                    };
        
                                return $doc->saveHTML();
                            }
                        else
                            {
                                return '';
                            };
                    };
            };
        
        /* Remove all instances of a particular HTML tag (e.g. <script>...</script>) from a variable containing raw HTML data. [END] */
        
        /* Remove all instances of dangerous and pesky <script> tags from a variable containing raw user-input HTML data. [BEGIN] */
        
        /* Prerequisites: 'removeAllInstancesOfTag(...)' */
        
        if (!function_exists('removeAllScriptTags'))
            {
                function removeAllScriptTags($html)
                    {
                        return removeAllInstancesOfTag($html, 'script');
                    };
            };
        
        /* Remove all instances of dangerous and pesky <script> tags from a variable containing raw user-input HTML data. [END] */
        
        
        

        这是一个测试的用法示例:

        
        $html = 'This is a JavaScript retention test.<br><br><span id="chk_frst_scrpt">Congratulations! The first \'script\' tag was successfully removed!</span><br><br><span id="chk_secd_scrpt">Congratulations! The second \'script\' tag was successfully removed!</span><script>document.getElementById("chk_frst_scrpt").innerHTML = "Oops! The first \'script\' tag was NOT removed!";</script><script>document.getElementById("chk_secd_scrpt").innerHTML = "Oops! The second \'script\' tag was NOT removed!";</script>';
        echo removeAllScriptTags($html);
        
        

        我希望我的回答真的对某人有所帮助。尽情享受吧!

        【讨论】:

          猜你喜欢
          • 2014-03-14
          • 1970-01-01
          • 2017-02-27
          • 1970-01-01
          • 2014-09-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多