作者:niubl@TSRC
2017年9月28日,公司扫描器发现某业务存在一例任意文件读取漏洞,团队跟进分析后发现这是Node.js和Express共同导致的一个通用漏洞。在我们准备告知官方时,发现Node.js官网于9月29号给出了漏洞公告,对应的CVE编号为CVE-2017-14849。
Node.js是一个Javascript运行环境(runtime),发布于2009年5月,由Ryan Dahl开发,实质是对Chrome V8引擎进行了封装。Node.js对一些特殊用例进行优化,提供替代的API,使得V8在非浏览器环境下运行得更好。
Express 是一个简洁而灵活的 node.js Web应用框架, 提供一系列强大特性帮助你创建各种Web应用。Express 不对 node.js 已有的特性进行二次抽象,我们只是在它之上扩展了Web应用所需的功能。丰富的HTTP工具以及来自Connect框架的中间件随取随用,创建强健、友好的API变得快速又简单。
Node.js 8.5.0 + Express 3.19.0-3.21.2
Node.js 8.5.0 + Express 4.11.0-4.15.5
Express依赖Send组件,Send组件0.11.0-0.15.6版本pipe()函数中,如图:
Send模块通过normalize('.' + sep + path)
标准化路径path后,并没有赋值给path,而是仅仅判断了下是否存在目录跳转字符。如果我们能绕过目录跳转字符的判断,就能把目录跳转字符带入545行的join(root, path)
函数中,跳转到我们想要跳转到的目录中,这是Send模块的一个bug,目前已经修复。
再来看Node.js,Node.js 8.5.0对path.js文件中的normalizeStringPosix
函数进行了修改,使其能够对路径做到如下的标准化:
assert.strictEqual(path.posix.normalize('bar/foo../..'), 'bar');
新的修改带来了问题,通过单步调试我们发现,可以通过foo../../
和目录跳转字符一起注入到路径中,foo../../
可以把变量isAboveRoot
设置为false
(代码161行),并且在代码135行把自己删掉;变量isAboveRoot
为false
的情况下,可以在foo../../
两边设置同样数量的跳转字符,让他们同样在代码135行把自己删除,这样就可以构造出一个带有跳转字符,但是通过normalizeStringPosix
函数标准化后又会全部自动移除的payload,这个payload配合上面提到的Send模块bug就能够成功的返回一个我们想要的物理路径,最后在Send模块中读取并返回文件。normalizeStringPosix
函数如下图: