如何从用户的markdown输入中删除脚本标签?

时间:2015-08-09 09:16:55

标签: php laravel xss markdown

在我的php应用程序中,我有这个textarea框,它接收来自用户的markdown(如stackoverflow' s),然后它显示在网站上。我正在使用Laravel框架并使用parsedown-laravel包 我能做到:

{!! Markdown::parse('__Hello__ Markdown!'); !!}

有效。

{!! Markdown::parse('<h1>Hello</h1> Markdown!'); !!}

它仍然有效。我对此很满意。

现在,如果我这样做:

{!! Markdown::parse('<script>alert("XSS Attack!!!")</script> Markdown!'); !!}

它仍然有效!!!

如何使用Laravel和此软件包阻止我的应用中的脚本标记?

3 个答案:

答案 0 :(得分:5)

如果您查看Markdown规范(original syntax by Jon GruberCommonMark),您会发现Markdown不应该替换HTML。它的唯一目标是让您更容易阅读您编写的文本。由于Markdown仅涵盖HTML标签的一小部分,因此您仍然可以使用内联HTML代码来创建您想要的内容。事实上,John Gruber说:

  

对于Markdown语法未涵盖的任何标记,您只需使用HTML本身。没有必要为它添加前缀或分隔它以表明您正在从Markdown切换到HTML;你只需使用标签。

基本上,这就是Markdown的工作方式。显然,如果您正在解析用户的输入,则不应该这样。由于Markdown解析器输出HTML代码,因此您无法使用htmlentities函数或类似的解决方案。

解决问题的最简单方法是使用像HTML Purifier这样的HTML过滤库。这将从Markdown输出中删除恶意代码,并尝试阻止XSS攻击。基本上,您应首先调用Markdown解析器,并使用该输出调用HTML Purifier库。

答案 1 :(得分:3)

原始的 Parsedown 库有一个转义html的选项:

echo Parsedown::instance()
    ->setMarkupEscaped(true) # escapes markup (HTML)
    ->text("<div><strong>*Some text*</strong></div>");

# Output:
# <p>&lt;div&gt;&lt;strong&gt;<em>Some text</em>&lt;/strong&gt;&lt;/div&gt;</p>

来自Parsedown Tutorial: Get Started

据推测,由于 parsedown-laravel 只是一个包装器,您应该能够访问该选项。

显然这会禁用所有标签而不是特定标签。

Parsedown bugtracker 上的moldcraft上的GitHub用户issue 229 - Disable parsing of specific elements提供了following code,可以为解决方案铺平道路:

  

moldcraft评论于2月24日•2015-02-24 18:41:31 +0100

     

对某些人可能有用:我也使用Parsedown进行用户评论,我想用h4替换所有h1,h2,h3以防止SEO警告(例如,页面上只有一个h1),这是我的Symfony2服务

<?php

namespace App\MainBundle\Service;

use Parsedown;
use HTMLPurifier;
use Emojione\Emojione;
use Symfony\Component\DependencyInjection\ContainerInterface;

class Markdown extends Parsedown
{
    /**
     * @var HTMLPurifier
     */
    private $purifier;

    public function __construct(ContainerInterface $container)
    {
        $this->setMarkupEscaped(true);

        {
            $purifierConfig = array(
                'HTML.ForbiddenElements' => array('h1', 'h2', 'h3'),
                'HTML.ForbiddenAttributes' => array('style', 'onclick',),
                'HTML.TargetBlank' => true,
            );

            $this->purifier = new HTMLPurifier($purifierConfig);
        }

        {
            Emojione::$imageType = 'svg';
            Emojione::$sprites = true;
            Emojione::$imagePathSVGSprites = $container->get('templating.helper.assets')->getUrl(
                'bundles/appmain/emojione/sprites/emojione.sprites.svg'
            );
            Emojione::$ascii = true;
        }
    }

    function text($raw)
    {
        return Emojione::shortnameToImage(
            $this->purifier->purify(
                parent::text($raw)
            )
        );
    }

    private function safeHeader($Block)
    {
        if ($Block && isset($Block['element'])) {
            /**
             * Change h1, h2, h3 to h4
             */
            if (in_array($Block['element']['name'], array('h1', 'h2', 'h3'))) {
                $Block['element']['name'] = 'h4';
            }
        }

        return $Block;
    }

    protected function blockHeader($Line)
    {
        return $this->safeHeader(
            parent::blockHeader($Line)
        );
    }

    protected function blockSetextHeader($Line, array $Block = null)
    {
        return $this->safeHeader(
            parent::blockSetextHeader($Line, $Block)
        );
    }
}

答案 2 :(得分:-2)

接受用户输入并将其与应用程序代码无缝集成永远不会是安全的。这是不行的。

如果这只是显示代码,那么您可以使用<textinput>标记来执行此操作。您可以设置样式,使其看起来不像输入。或者您只需将htmlescape()这样的函数与<pre>标记结合使用。