js原型链污染

写在前面

已经遇见过好几道原型链污染的题目了,一直没有整体学习整理过.国赛又碰见了,然而没做出来,痛定思痛,准备学习一下.

原型与原型链

JavaScript是一门面向对象语言,有意思的一点是,它语法中并没有class的概念,但是它仍然大量使用了对象。为了保证对象之间的联系,JavaScript引入了原型和原型链的概念。

构造函数与原型

为了保证JavaScript“看起来像Java”,JavaScript中也加入了new操作符:

1
var obj = new FunctionName()

但与Java不同的是,JavaScript中的new操作符后面跟的并不是类名而是函数名,JavaScript并非通过类二十直接通过构造函数来创建实例。

1
2
3
4
5
6
7
8
9
10
function Dog(name, color) {
this.name = name
this.color = color
this.bark = () => {
console.log('wangwang~')
}
}

const dog1 = new Dog('dog1', 'black')
const dog2 = new Dog('dog2', 'white')

上述代码就是JavaScript中通过构造函数创建实例的过程,但这种方式有一个很大的问题。通过构造函数创建实例的时候,每创建一个实例,都需要重新创建这个方法,再把它添加新的实例中。
test
这样就造成了很大的浪费,所以需要一种方法来解决这个问题。
原型(prototype)的使用就是为了解决这个问题:

  • 每一个构造函数都拥有一个prototype属性,这个属性指向一个对象,也就是原型对象。当使用这个构造函数创建实例的时候,prototype属性指向的原型对象就成为实例的原型对象。

  • 原型对象默认拥有一个constructor属性,指向指向它的那个构造函数(也就是说构造函数和原型对象是互相指向的关系)。

  • 每个对象都拥有一个隐藏的属性prototype,指向它的原型对象,这个属性可以通过 Object.getPrototypeOf(obj) 或 obj.proto 来访问。
    例如上述代码
    test

  • 如上文所述,原型对象就是用来存放实例中共有的那部分属性。

  • 在JavaScript中,所有的对象都是由它的原型对象继承而来,反之,所有的对象都可以作为原型对象存在。

  • 访问对象的属性时,JavaScript会首先在对象自身的属性内查找,若没有找到,则会跳转到该对象的原型对象中查找。

上述代码中各对象关系可表示为下图
test

通过原型,我们可以将上述代码修改为:

1
2
3
4
5
6
7
8
function Dog(name, color) {
this.name = name
this.color = color
}

Dog.prototype.bark = () => {
console.log('wangwang~')
}

test
实例化dog1和dog2,我们发现他们bark函数相同,也就解决了之前的问题.

原型链与继承

上文提到,JavaScript中所有的对象都是由它的原型对象继承而来。而原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链(prototype chain)

所有原型链的终点都是Object函数的prototype属性,因为在JavaScript中的对象都默认由Object()构造。Objec.prototype指向的原型对象同样拥有原型,不过它的原型是null,而null则没有原型。

通过原型链就可以在JavaScript中实现继承,JavaScript中的继承相当灵活,有多种继承的实现方法,这里只介绍一种最常用的继承方法也就是组合继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Dog(name, color) {
this.name = name
this.color = color
}

Dog.prototype.bark = () => {
console.log('wangwang~')
}

function Husky(name, color, weight) {
Dog.call(this, name, color)
this.weight = weight
}

Husky.prototype = new Dog()

这里声明了一个新的构造函数Husky,通过call方法继承Dog中的属性(call方法的作用可以简单理解为将Dog中的属性添加到Husky中,因为还涉及到其他的知识点所以不多赘述),并添加了一个weight属性。然后用Dog函数创建了一个实例作为Husky的原型对象赋值给Husky.prototype以继承方法。这样,通过Husky函数创建的实例就拥有了Dog中的属性和方法。

原型链污染

为了更方便描述,我们修改一下之前的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Dog(name) {
this.name = name
this.color = "white"
this.bark = () => {
console.log('wangwang~')
}
}

function Husky(name, weight) {
Dog.call(this, name)
this.weight = weight
}

Husky.prototype = new Dog()

const dog1 = new Husky('dog1', 50)
const dog2 = new Husky('dog2', 100)

我们发现如果修改dog1.Proto.color = 1, dog2的color也会改变,这样的操作污染了原型链,称之为原型链污染.
test
也就是,在一个应用中,如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染。

实例

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!')
})

查看源码可知,我们需要令md5(user.admintoken) === req.query.querytokenuser.admintoken存在,而源码中显示user是个 []类这个类并没有开发完成,所以我们的目标就是给user.admintoken赋值.

根据原型链的概念,当user.admintoken没有值时,会递归访问proto查询,所以我们可以尝试寻找某个[]类并修改它的原型.
/api接口中,函数会接收传参,并matrix[client.row][client.col] = client.data;.
而matrix[“proto“]就是matrix对象的原型,这样就造成了原型链污染.

test

payload:
test
test

xss13

靶场地址

源码如下

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
 function escape(input) {
// extend method from Underscore library
// _.extend(destination, *sources)
function extend(obj) {
var source, prop;
for (var i = 1, length = arguments.length; i < length; i++) {
source = arguments[i];
for (prop in source) {
obj[prop] = source[prop];
}
}
return obj;
}
// a simple picture plugin
try {
// pass in something like {"source":"http://sandbox.prompt.ml/PROMPT.JPG"}
var data = JSON.parse(input);
var config = extend({
// default image source
source: 'http://placehold.it/350x150'
}, JSON.parse(input));
// forbit invalid image source
if (/[^\w:\/.]/.test(config.source)) {
delete config.source;
}
// purify the source by stripping off "
var source = config.source.replace(/"/g, '');
// insert the content using mustache-ish template
return '<img src="{{source}}">'.replace('{{source}}', source);
} catch (e) {
return 'Invalid image data.';
}
}

我们分析下源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
try {
// pass in something like {"source":"http://sandbox.prompt.ml/PROMPT.JPG"}
var data = JSON.parse(input);
var config = extend({
// default image source
source: 'http://placehold.it/350x150'
}, JSON.parse(input));
// forbit invalid image source
if (/[^\w:\/.]/.test(config.source)) {
delete config.source;
}
// purify the source by stripping off "
var source = config.source.replace(/"/g, '');
// insert the content using mustache-ish template
return '<img src="{{source}}">'.replace('{{source}}', source);
} catch (e) {
return 'Invalid image data.';
}
}

主函数将用户传参进行JSON.parse(input)替换成原有的图片资源,这边造成了原型链污染.
如果我们传入的source值不符合正则,他将删除config.source

test

我们尝试传入{"source":"*", "__proto__": {"source":"1"}},验证了我们之前的分析

test

接下来要做的就是xss注入的绕过"
查看js文档中replace函数性质:

字符串 stringObject 的 replace() 方法执行的是查找并替换的操作。它将在 stringObject 中查找与 regexp 相匹配的子字符串,然后用 replacement 来替换这些子串。如果 regexp 具有全局标志 g,那么 replace() 方法将替换所有匹配的子串。否则,它只替换第一个匹配子串。
replacement 可以是字符串,也可以是函数。如果它是字符串,那么每个匹配都将由字符串替换。但是 replacement 中的 $ 字符具有特定的含义。如下表所示,它说明从模式匹配得到的字符串将用于替换。
test

我们先来测试下这个特性

test

这样我们就可以通过'<img src="_posts/js原型污染.md">'.replace('_posts/js原型污染.md', source)中_posts/js原型污染.md前的"来闭合,进而注入

payload如下:

1
{"source":"*", "__proto__": {"source":"$`  onerror='prompt(1)' "}}

test

Thejs

题目源码地址https://github.com/phith0n/code-breaking/blob/master/2018/thejs
题目部署地址http://106.15.177.94:10025/

看了很多wp,不太清楚清楚师傅们是怎么泄露到源码或者fuzz到注入点的,可能是赛程中放出了主要代码吧.
这里就直接分享p神的题解了深入理解 JavaScript Prototype 污染攻击

参考资料

三分钟看完JavaScript原型与原型链
JavaScript 原型链污染
深入理解 JavaScript Prototype 污染攻击
浅析javascript原型链污染攻击