有许多Stack Overflow问题(例如,白名单,阻止XSS在C#和WMD Markdown和服务器端使用WMD控制)关于如何对WMD编辑器生成的Markdown进行服务器端清理,以确保生成的HTML不包含恶意脚本,像这样:
但我也找不到在客户端插上漏洞的好方法.当然,客户端验证不能替代服务器上的擦除验证,因为任何人都可以伪装成客户端并发布令人讨厌的Markdown.如果你在服务器上清理HTML,攻击者就无法保存坏的HTML,因此以后没有其他人能够看到它并且他们的cookie被盗或被坏脚本劫持的会话.因此,有一个有效的案例可能不值得在WMD预览窗格中强制执行无脚本规则.
但想象一下,攻击者找到了将恶意Markdown放到服务器上的方法(例如,来自其他站点的受感染的源,或者在修复XSS错误之前添加的内容).将markdown转换为HTML时应用的服务器端白名单通常可以防止向用户显示错误的Markdown.但是,如果攻击者可以让某人编辑该页面(例如,通过发布另一个条目,说恶意条目有一个断开的链接并要求某人修复它),那么编辑该页面的任何人都会被劫持.这无疑是一个极端的案例,但它仍然值得防范.
此外,允许客户端预览窗口允许不同于服务器允许的HTML,这可能是一个坏主意.
Stack Overflow团队通过更改WMD来堵塞这个漏洞.他们是如何做到的呢?
[注意:我已经想到了这一点,但它需要一些棘手的JavaScript调试,所以我在这里回答我自己的问题,以帮助其他人可能想要做同样的事情].
一种可能的解决方法是在pushPreviewHtml()
方法中的wmd.js中.这是GitHub上来自Stack Overflow版本的WMD的原始代码:
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: // // 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 = /^(]+")?\s?>|<\/a>)$/i; var okImg = /^(]*")?(\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堆栈溢出版本中 - 显然更改是在稍后进行的,而不是检查回GitHub.
更新:为了避免破坏在键入URL时自动创建超链接的功能,您还需要对showdown.js进行更改,如下所示:
原始代码:
var _DoAutoLinks = function(text) { text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"$1"); // Email addresses: /* 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, '$1'); return text; }