原文地址: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,它将直接调用该函数(不带任何参数),并使用函数的返回值作为上下文,而不是函数本身。

下面的代码:

{{#with something}}
{{something}}
{{/with}}

等价于:

output(something())

但是,我们需要的却是:

output(something)

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

利用__defineGetter____setGetter__

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

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

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

它将被编译为:

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

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

绕过补丁

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

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

{{#with __lookupGetter__ }}
{{! override propertyIsEnumerable with a function that always returns 1 using valueOf.bind(1).bind() }}
{{__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报告了这个安全漏洞,但一直没有收到回复。由于这个漏洞需要借助于模板注入漏洞,所以,我决定将它公之于众。

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖