PHP 有个开源的 Markdown 解析器 —— Parsedown,很强大,但是在 XSS 过滤方面并不是做得特别好,我在用 Parsedown 对 Markdown 进行解析的时候,遇到了一些 XSS 过滤方面的问题。

Parsedown 会对 代码 区域内的 html 代码进行转义,代码区域外的却不进行转义,如以下代码所示

<?php
include 'Parsedown.php';
$test = "```\n<script>alert('test')</script>\n```\n<script>alert('test')</script>";
$Parsedown = new Parsedown();
echo $Parsedown->text($test);

/**
  * 得到结果是:
  * <pre><code>&lt;script&gt;alert('test')&lt;/script&gt;</code></pre>
  * <script>alert('test')</script>
  *
  */

这样,<script>alert('test')</script>这句还是被成功执行了

既然如此,那我先自己给它转义一下

<?php
include 'Parsedown.php';
include 'com.func.php';
$test = "```\n<script>alert('test')</script>\n```\n<script>alert('test')</script>";
$test = htmlspecialchars($test, ENT_QUOTES);
$Parsedown = new Parsedown();
echo $Parsedown->text($test);

/**
  * 得到结果是:
  * <pre><code>&amp;lt;script&amp;gt;alert(&amp;#039;test&amp;#039;)&amp;lt;/script&amp;gt;</code></pre>
  * <p>&lt;script&gt;alert(&#039;test&#039;)&lt;/script&gt;</p>
  */

虽然 XSS 是被过滤掉了,但是代码区域的内容就被转义了两次

然后我发现 SF 的 Markdown 解析在 XSS 过滤方面就做得挺好的,例如这个页面,它的部分源码如下:

<p>我现在的代码是这样的<br>
&lt;html&gt;<br>
&lt;head&gt;<br><br>
    &lt;title&gt;Untitled Document&lt;/title&gt;</p>
<pre><code>&lt;script&gt;
    function test(){
        &lt;?php
            $conn = mysql_connect("localhost", "username", "123123");
            mysql_select_db("username", $conn);
            mysql_query("INSERT INTO ChargerTogether (Chat) VALUES ('test')");
            $result= mysql_query("SELECT *FROM ChargerTogether");

            echo "&lt;p&gt; {$result} &lt;/p&gt;&gt;";
        ?&gt;
    }
&lt;/script&gt;
</code></pre>
<p>&lt;/head&gt;<br>
&lt;body&gt;<br>
&lt;input type="button" onClick="javascript:test();"&gt;</p>
<p>&lt;/body&gt;<br>
&lt;/html&gt;</p>
<p>但这样是错误的<br>
我该怎样做呢</p>

后来尝试去看 Parsedown 的源码,终于找到了解决方案:

  1. 把 Parsedown 源码里所有(共三处)转义用的语句(如下所示)给注释掉

    $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
    这句出现在了以下三个方法中:
    protected function blockCodeComplete($Block)protected function blockFencedCodeComplete($Block)protected function inlineCode($Excerpt)
  2. 然后再修改一下 function text($text)

    function text($text)
    {
        #添加下面这行
        $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
        # Code...
    }

原项目我已经 fork 到我的 GitHub 上并且做了上述改动了,戳这里


后记:在遇到这个问题并多次尝试无解后,我在 Segmentfault 上提出了问题,因为一开始意外发错版块,导致首页没有显示出我这个问题,所以一直没有人来回答,最后暂时放下这个工作,去做了点其他事,几个小时后自己通过查看并修改 Parsedown 的代码就成功解决了 XSS 过滤的问题了。所以说有时候想不出来就先去做点其他事吧:-)

标签: php, markdown, xss

添加新评论