5

关于如何对 WMD 编辑器生成的 Markdown 进行服务器端清理以确保生成的 HTML 不包含恶意软件,有许多 Stack Overflow 问题(例如,白名单、在 C#WMD Markdown 和服务器端使用 WMD 控制防止 XSS)脚本,像这样:

<img onload="alert('haha');" 
   src="http://www.google.com/intl/en_ALL/images/srpr/logo1w.png" />

但是我也没有找到一个很好的方法来堵住客户端的漏洞。客户端验证当然不能替代服务器上的擦洗验证,因为任何人都可以假装自己是客户端并向您发布讨厌的 Markdown。如果您在服务器上清理 HTML,攻击者无法保存坏的 HTML,因此其他人将无法在以后看到它,并且他们的 cookie 被盗或会话被坏脚本劫持。所以有一个有效的例子,它可能不值得在 WMD 预览窗格中执行无脚本规则。

但是想象一下,攻击者找到了一种将恶意 Markdown 下载到服务器上的方法(例如,来自另一个站点的受损提要,或者在修复 XSS 错误之前添加的内容)。在将 Markdown 转换为 HTML 时应用的服务器端白名单通常会阻止向用户显示错误的 Markdown。但是,如果攻击者可以让某人编辑页面(例如,通过发布另一个条目说恶意条目的链接已损坏并要求某人修复它),那么任何编辑页面的人都会被劫持他们的 cookie。诚然,这是一个极端的案例,但它仍然值得防御。

此外,允许客户端预览窗口允许与服务器允许的 HTML 不同的 HTML 可能是个坏主意。

Stack Overflow 团队通过对 WMD 进行更改来填补这个漏洞。他们是如何做到的呢?

[注意:我已经弄清楚了,但它需要一些棘手的 JavaScript 调试,所以我在这里回答我自己的问题,以帮助其他可能想做同样事情的人]

4

2 回答 2

6

一种可能的修复方法是在 wmd.js 中的pushPreviewHtml()方法中。这是GitHub 上 WMD的 Stack Overflow 版本的原始代码:

if (wmd.panels.preview) {
    wmd.panels.preview.innerHTML = text; 
}

您可以用一些清理代码替换它。这是 Stack Overflow为响应这篇文章而使用的代码的改编版本,它限制为标签的白名单,对于 IMG 和 A 元素,限制为属性的白名单(并且也以特定的顺序!)。请参阅 Meta Stack Overflow 帖子Stack Overflow、Server Fault 和 Super User 上允许哪些 HTML 标签?有关白名单的更多信息。

注意:此代码当然可以改进,例如允许以任何顺序列入白名单的属性。它还禁止 mailto: URL,这在 Internet 站点上可能是一件好事,但在您自己的 Intranet 站点上它可能不是最好的方法。

if (wmd.panels.preview) {

    // Original WMD code allowed JavaScript injection, like this:
    //    <img src="http://www.google.com/intl/en_ALL/images/srpr/logo1w.png" onload="alert('haha');"/>
    // Now, we first ensure elements (and attributes of IMG and A elements) are in a whitelist,
    // and if not in whitelist, replace with blanks in preview to prevent XSS attacks 
    // when editing malicious Markdown.
    var okTags = /^(<\/?(b|blockquote|code|del|dd|dl|dt|em|h1|h2|h3|i|kbd|li|ol|p|pre|s|sup|sub|strong|strike|ul)>|<(br|hr)\s?\/?>)$/i;
    var okLinks = /^(<a\shref="(\#\d+|(https?|ftp):\/\/[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+)"(\stitle="[^"<>]+")?\s?>|<\/a>)$/i;
    var okImg = /^(<img\ssrc="https?:(\/\/[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+)"(\swidth="\d{1,3}")?(\sheight="\d{1,3}")?(\salt="[^"<>]*")?(\stitle="[^"<>]*")?\s?\/?>)$/i;
    text = text.replace(/<[^<>]*>?/gi, function (tag) {
        return (tag.match(okTags) || tag.match(okLinks) || tag.match(okImg)) ? tag : ""
    })

    wmd.panels.preview.innerHTML = text;  // Original code 
}

另请注意,此修复不在GitHub 上的 WMD 的 Stack Overflow 版本中- 显然更改是稍后进行的,并且未检入 GitHub。

更新:为了避免破坏在您输入 URL 时自动创建超链接的功能,您还需要对 showdown.js 进行更改,如下所示:

原始代码:

var _DoAutoLinks = function(text) {

    text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"<a href=\"$1\">$1</a>");

    // Email addresses: <address@domain.foo>

    /*
        text = text.replace(/
            <
            (?:mailto:)?
            (
                [-.\w]+
                \@
                [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
            )
            >
        /gi, _DoAutoLinks_callback());
    */
    text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,
        function(wholeMatch,m1) {
            return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );
        }
    );

    return text;
}

固定代码:

var _DoAutoLinks = function(text) {
    // use simplified format for links, to enable whitelisting link attributes
    text = text.replace(/(^|\s)(https?|ftp)(:\/\/[-A-Z0-9+&@#\/%?=~_|\[\]\(\)!:,\.;]*[-A-Z0-9+&@#\/%=~_|\[\]])($|\W)/gi, "$1<$2$3>$4");
    text = text.replace(/<((https?|ftp):[^'">\s]+)>/gi, '<a href="$1">$1</a>');
    return text;
}
于 2010-05-14T20:59:21.823 回答
2

只要任何第三方都不可能提供脚本,允许本地用户在页面上下文中执行脚本就不是安全问题。如果没有编辑器,用户总是可以javascript:在你的页面上输入一个 url,或者使用 Firebug 或类似的东西。

于 2010-05-14T21:02:26.700 回答