JavaScript 原型链污染
2019-06-17 13:56:40

原型

JavaScript 是一门面向对象的语言,但是在 ES6 之前,JavaScript 中没有 class 语法。不过在它的构造函数(constructor)就相当于类,通过构造函数,我们可以生成实例化的对象。

1
2
3
4
5
6
function Cat() {
this.color = 'orange'
}

var cat = new Cat()
console.log(cat.color) // orange

prototype

JavaScript中的每个函数都有一个prototype 属性,它指向调用该构造函数而创建的实例对象的原型。 image.png

proto

同样的,在 JavaScript 中,每个实例对象也都有一个__proto__属性用来指向实例对象的原型。 image.png 实例对象的 __proto__与创建该实例对象的构造函数的 prototype 是相等的。

1
2
3
4
5
6
7
function Cat() {
this.color = 'orange'
}

var cat = new Cat()

console.log(cat.__proto__ === Cat.prototype) // true

image.png

constructor

每个原型对象都有一个 constructor 属性,指向相关联的构造函数,所以构造函数和构造函数的 prototype 是可以相互指向的。实例对象也可以访问constructor 属性指向其构造函数。 image.png

原型链

1
2
3
4
5
6
7
8
9
10
function Cat() {
this.color = 'orange'
}

Cat.prototype.age = 4

var cat = new Cat()

console.log(cat.color) // orange
console.log(cat.age) // 4

在 JavaScript 中,如果想访问某个属性,首先会在实例对象(cat)的内部寻找,如果没找到,就会在该对象的原型(cat.__proto__,即 Cat.prototype)上找,我们知道,对象的原型也是对象,它也有原型,如果在对象的原型上也没有找到目标属性,则会在对象的原型的原型(Cat.prototype.__proto__)上寻找,以此内推,直到找到这个属性或者到达了最顶层。在原型上一层一层寻找,这便就是原型链了。 实例对象原型的原型是Object.prototype,而它的原型是null,null 没有原型,所以 Object.prototype 就是原型链的最顶端。

1
2
3
4
5
6
function Cat() {
this.color = 'orange'
}

console.log(Cat.prototype.__ptoto__ === Object.prototype) // true
console.log(Object.prototype.__proto__) // null

JavaScript 中的所有对象都来自 Object,Object 位于原型链的最顶端,几乎所有 JavaScript 的实例对象都是基于 Object。 image.png

继承

JavaScript 的继承是基于原型链的,在原型链的任何位置设置属性,都能被对象访问到,原型的作用也是在此,它可以包含所有实例共享的属性和方法,就像该属性本来就在实例对象上一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Cat() {
this.color = 'orange'
this.age = 4
}

Cat.prototype.getColor = function() {
console.log(this.color)
}

Object.prototype.getAge = function() {
console.log(this.age)
}

var cat = new Cat()

cat.getColor() // orange
cat.getAge()

var a = ['hello', 'world']
function f() {}

console.log(a.__proto__ === Array.prototype) // true
console.log(f.__proto__ === Function.prototype) // true

原型链污染

在JavaScript中访问一个对象的属性可以用a.b.c或者a[“b”][“c”]来访问。由于对象是无序的,当使用第二种方式访问对象时,只能使用指明下标的方式去访问。因此我们可以通过a["__proto__"]的方式去访问其原型对象。 在一个应用中,如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染。

实例

hackit 2018

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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();
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('/static', express.static('static'));
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 ??*/

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;
console.log(matrix);
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!')
})

访问admin获取flag需要user.admintoken===req.query.querytoken,而user = [],他是Array的实例,继承自Array.prototype。而api接口处有一处赋值操作,我们可以通过__proto__Array.prototype进行赋值。 image.png image.png image.png

Referer

深入理解JavaScript Prototype污染攻击 Code Breaking 挑战赛 Writeup 原型与原型链 JavaScript 原型链污染 JavaScript原型链污染

Prev
2019-06-17 13:56:40
Next