深入探讨Handlebars 4.1.2之命令执行漏洞

2019-09-11 约 1888 字 预计阅读 4 分钟

声明:本文 【深入探讨Handlebars 4.1.2之命令执行漏洞】 由作者 mss**** 于 2019-09-11 09:00:29 首发 先知社区 曾经 浏览数 495 次

感谢 mss**** 的辛苦付出!

原文地址:https://blog.tarq.io/handlebars-4-1-2-command-execution/?tdsourcetag=s_pcqq_aiomsg

在阅读了Mahmoud Gamal的文章 中涉及的[NPM Advisory 755](https://www.npmjs.com/advisories/755)安全问题的材料后,我决定开始一次冒险之旅,看看能否通过其他其他方法实现沙箱逃逸。

Exploit代码



译者注:具体代码,请参见原文。

背景知识


Handlebars是一个弱逻辑的模板引擎,为了限制用户可以执行的操作,该引擎会为模板提供限制性的沙箱。最后,该引擎会将模板编译为javascript代码,因此,实现逃逸是一件非常棘手的任务。

对于NPM Advisory 755中的原型污染漏洞的补丁来说,本质上就是禁止对不可枚举的属性执行读写操作。但是,为了运行我们的payload,就必须访问Function.prototype.constructor,所以,我们需要设法绕过这项检查。

我们的目标


为了正常运行这个exploit,我们需要完成下列任务:

-使用始终返回true的函数覆盖PropertyIsEnumerable,以便绕过针对NPM 755漏洞的缓解措施
-获取对Function构造函数的引用
-使用攻击者控制的对象来调用函数的构造函数
-调用构造的函数以执行payload

创建payload


首先,我们要创建一个包含单个字符串元素的数组,该元素定义了我们要执行的javascript代码。为此,只需将this设置为包含我们的payload的字符串,然后,调用split即可:

滥用helper


在handlebars中,可以用this来调用绑定到当前上下文的各种函数。正如您在上面所看到的,这里使用with helper将上下文设置为字符串,然后调用了相应的split函数。

需要注意的是,这里的with helper很像Javascript中的with操作符。实际上,这个helper改变了this的含义。在这里,我们的本意是将上下文设置为“函数”,但是handlebars却做了一件出乎意料的事情! 具体来说,如果将函数传递给with helper,它将直接调用该函数(不带任何参数),并使用函数的返回值作为上下文,而不是函数本身。

下面的代码:

222#with something}}
222something}}
222/with}}

等价于:

output(something())

但是,我们需要的却是:

output(something)

那么,我们如何解决这个问题呢?实际上,我们需要借助一个返回函数的函数。

利用__defineGetter____setGetter__

尽管很少有人会记得这两个函数,但它们却非常有用。本质上将,函数__defineGetter__可以用来定义每次访问属性时调用的函数并为其返回一个值。而函数__lookupGetter__只返回用于生成值的函数。

如果我们考察exploit的第一步动作:

222__defineGetter__ "undefined" valueOf }}
   222! sets context to valueOf, this is what we'll be calling bind on later }}
   222! handlebars ends up calling context.__lookupGetter__() which returns the same thing as __lookupGetter("undefined") }}
  222#with __lookupGetter__ }}

它将被编译为:

this.__defineGetter__("undefined", this.valueOf)
with(this.__lookupGetter__()) {
  ....
}

我们无法控制传递给__lookupGetter__函数的参数,但幸运的是,在JavaScript中如果不传递参数的话,那么相应的变量将被设置为undefined。之后,该变量将被转换为字符串,最终与调用__lookupGetter__("undefined")的效果是一样的。因此,要将上下文设置为函数,我们只需定义一个属性名为"undefined"的getter,然后借助于{#with __lookupGetter__}即可。

绕过补丁

为了绕过补丁,我们需要使propertyIsEnumerable始终返回1

借助于我们的新原语,可以通过以下方式完成上述任务:

222#with __lookupGetter__ }}
222! override propertyIsEnumerable with a function that always returns 1 using valueOf.bind(1).bind() }}
222__defineGetter__ "propertyIsEnumerable" (this.bind (this.bind 1)) }}

上述代码的作用是,将propertyisEnumerable的getter设置为valueof,并绑定数字1为其上下文。

上面的代码等价于:

valueOf.__defineGetter__("propertyIsEnumerable", valueOf.bind(valueOf.valueOf.bind(1)))
// valueOf.propertyIsEnumerable = function() {
//   return (1).valueOf()
//}

现在,context.propertyIsEnumerable将始终返回1!

这样一来,我们只需要获得this.constructor(对应的是Function)的引用,然后,我们的payload调用它即可。

时间线

5个月前,我就向NPM Security报告了这个安全漏洞,但一直没有收到回复。由于这个漏洞需要借助于模板注入漏洞,所以,我决定将它公之于众。

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now