导语:由于数据库的本地JSON解析器和在文档验证期间使用的Javascript JSON解析器之间的一些差异,导致CouchDB中存在一个漏洞。由于大量的安装的CouchDB数据库一般都直接暴露在公网,所以这个漏洞又可以导致远程特权升级,并最终远程执行代码。
由于数据库的本地JSON解析器和在文档验证期间使用的Javascript JSON解析器之间的一些差异,导致CouchDB中存在一个漏洞。由于大量的安装的CouchDB数据库一般都直接暴露在公网,所以这个漏洞又可以导致远程特权升级,并最终远程执行代码。如果这个漏洞已经被利用,这个bug可能允许修改npm registry 中的任意一个package,这个漏洞的CVE编号是CVE-2017-12635
背景
上一次,我写了一个关于导致rubygems.org上的代码执行的反序列化bug,是ruby程序依赖的关系库。将恶意软件注入到上游项目依赖项的能力是一个可怕的攻击方式,不过我怀疑大多数企业都做了充分的防护措施。
考虑到这一点,我开始在registry.npmjs.org这个负责分发npm包的服务器上寻找bug 。根据他们的主页提供的信息,npm registry每周提供了超过30亿个包的下载量。
CouchDB
npm registry使用CouchDB,在这个项目之前我没有听说过这种数据库。CouchDB是一个“NoSQL”数据库,能够使得数据复制非常容易。这有点像JSON blob(“documents”)的一个大键值存储对象,具有数据验证,查询和用户认证的功能,使其更接近成熟的数据库。CouchDB是用Erlang编写的,但是允许用户在Javascript中指定文档验证脚本。这些脚本在创建或更新文档时自动进行评估。这些脚本会启动一个新的进程,并从Erlang中传递JSON序列化的文档。
CouchDB通过一个特殊的数据库来管理用户帐户叫做_users。在CouchDB数据库中创建或修改用户(通常是执行PUT到 /_users/org.couchdb.user:your_username)时,服务器会使用Javascript的 validate_doc_update函数检查你提出的更改,以确保你不是尝试(例如)让自己成为管理员。
漏洞
问题是JavaScript JSON解析器(用于验证脚本)和CouchDB内部使用的名为jiffy的解析器之间存在着差异。让我们来看看这两个是如何处理对象上的重复键的:{"foo":"bar", "foo":"baz"}
Erlang:
> jiffy:decode("{"foo":"bar", "foo":"baz"}"). {[{<<"foo">>,<<"bar">>},{<<"foo">>,<<"baz">>}]}
Javascript:
> JSON.parse("{"foo":"bar", "foo": "baz"}") {foo: "baz"}
对于给定的键,Erlang解析器将存储这两个值,但是JavaScript解析器将只存储最后一个值。不幸的是,CouchDB内部数据表示的getter函数将只返回第一个值:
% Within couch_util:get_value lists:keysearch(Key, 1, List).
因此,我们可以绕过所有相关的输入验证,并利用这个漏洞创建一个管理员用户:
curl -X PUT 'http://localhost:5984/_users/org.couchdb.user:oops' --data-binary '{ "type": "user", "name": "oops", "roles": ["_admin"], "roles": [], "password": "password" }'
在Erlang部分,我们会看到自己的_admin角色,而在Javascript部分我们似乎没有特殊的权限。对于攻击者来说幸运的是,除了输入验证脚本外,几乎所有关于身份验证和授权的重要逻辑都发生在CouchDB的Erlang部分。
现在我们有一个管理员帐户,我们完全控制了数据库。从这里获取shell通常很简单,因为CouchDB允许你通过管理界面定义自定义query_server的语言,这个功能基本上只是一个execv的包装。这个漏洞的一个有趣的地方是通过Web GUI检测有点棘手,如果你尝试通过管理控制台检查刚刚创建的用户,那么该roles字段将显示为空,因为在用Javascript显示之前它已经被解析了!
对npm的影响
我一直在试图弄清楚npm是如何受到这个bug的影响的。由于我没有真正利用这个漏洞攻击npm的任何生产服务器,所以我必须根据公开的信息,对基础设施的容易受攻击的部分进行大胆猜测。
我几乎可以肯定的是,registry.npmjs.org容易受到此漏洞的特权升级/管理帐户创建部分的攻击,这将允许攻击者修改软件包。这是因为npm上的用户创建与vanilla CouchDB的用户创建的流程大致相同。然后,在认证为新创建的管理员用户之后,传递给后续验证脚本的用户上下文将具有可见的_admin角色,从而允许我们通过isAdmin 检查 registry 的一个验证文档。也就是说,据我所知的Github上的内容,他们的生产服务器没有提供一个路由到管理员的配置API,这意味着我不知道该错误是否可以在该服务器上成功实现RCE。
Npm还公开了一个“skim database”,看起来它不是RCE漏洞攻击的一部分,但我不清楚该数据库的如何在现在的基础设施中使用的。有一篇在 2014年发表的博客文章,说明了skimdb的一些东西,但我不知道现在是否仍然如此。
结论
使用多个解析器来处理相同的数据可能是一个糟糕的主意。如果你必须这么做,也许是因为你的项目使用了像CouchDB这样的多种语言,那就尽量确保解析器之间没有任何功能上的差异。然后不幸的是,JSON标准里没有指定重复键的行为。
感谢CouchDB团队发布了安全相关的电子邮件地址并尽快解决了这个安全问题。