1. 程式人生 > 實用技巧 >JavaScript原型鏈及其汙染

JavaScript原型鏈及其汙染

JavaScript原型鏈及其汙染

一.什麼是原型鏈?

1.JavaScript中,我們如果要define一個類,需要以define“建構函式”的方式來define:
function xluo() {				//定義類xluo()
    this.a = 10086				//this.a是xluo類的一個屬性
}
new xluo()
2.瞭解prototype and __proto__ , JavaScript裡面'一切皆物件'。
(通用規則,JavaScript預設原型鏈指向邏輯)
  1. 物件有__proto__屬性,函式有prototype屬性;
  2. 物件由函式生成;
  3. 生成物件時,物件的__proto__
    屬性指向函式的prototype屬性。
var xluo = {}			//建立空物件時,we actually use Object fuction to generate
xluo.__proto__ === Object.prototype
//true

var xluo = Object()
xluo.__proto__ === Object.prototype
//true

------------------------------------------------
//JavaScript一切皆物件,即函式也是物件的一種

function xluo(){}				//function函式建立物件
xluo.__proto__ === Function.prototype
//true

typeof xluo
//"function"

Function.__proto__ === Function.prototype
//true
Object.__proto__ === Function.prototype
//true

var xluogood = new xluo()
xluogood.__proto__ === xluo.prototype
//true
3.原型鏈繼承

物件的屬性時從generate他的函式的prototype那裡得到的。

xluogood為何有xluo、Object的原型方法,其實就是通過原型鏈繼承。

繼承的過程可以表示為xluogood.__proto__ = xluo.prototype

物件.__proto__ = 構造器.prototype

xluogood為普通物件,它的構造器為xluo,以xluo為原型,

第一鏈為xluogood.__proto__ === xluo.prototype

xluo.prototype(注意這邊不是xluo)為json物件,即普通物件,構造器為Object,以Object為原型,

第二鏈為xluo.prototype.__proto__ === Object.prototype;

Object.prototype以Null為原型,

第三鏈為Object.prototype.__proto__ === null

此時我們再看下面那張圖是不是很容易理解呢。

[]

4.通過prototype鏈實現繼承

一般函式預設的prototype是系統自動生成的一個物件

當js引擎執行物件的屬性或方法時,先查詢物件本身是否存在該方法,如果不存在則會在原型鏈上查詢。

  1. 每個建構函式(constructor)都有一個原型物件(prototype)
  2. 物件的__proto__屬性,指向類的原型物件prototype
  3. JavaScript使用prototype鏈實現繼承機制

一般函式預設的prototype是一個型別為"object"的物件,它有兩個屬性:constructor__proto__

大多數情況下,__proto__可以理解為“構造器的原型”,即__proto__===constructor.prototype,但是通過 Object.create()建立的物件有可能不是。

其中constructor屬性指向這個函式自身,__proto__屬性指向Object.prototype,這說明一般函式的prototype屬性是由Object函式生成的。

function xluo(){}
typeof xluo.prototype
//"object"
xluo.prototype.constructor === xluo
//true

特殊函式Function,Object

typeof Object.prototype
//"oject"

可以看到Object函式的prototype屬性也是一個型別為"object"的物件,但和一般函式的預設prototype屬性不一樣的是,它多了一大堆方法,這些方法都是JavaScript物件的系統預設方法。

再仔細看,Object函式的prototype屬性裡沒有__proto__屬性,我們試著把它的__proto__屬性打出來看看:

Object.prototype.__proto__
//null

Object.prototype.__proto__ === null,提前到達了終點。

typeof Object.prototype === "object",說明它是一個Object物件,如果它由Object函式生成,於是按照我們上面的通用規則,就該是Object.prototype.__proto__ === Object.prototype

問題出現了,Object.prototype.__proto__屬性指向了它自身,這樣以__proto__屬性構成的原型鏈就再也沒有終點了!所以為了讓原型鏈有終點,在原型鏈的最頂端,JavaScript規定了Object.prototype.__proto__ === null

typeof Function.prototype			//有別於其他函式的object
//"function"
Function.prototype.__proto__ === Object.prototype
//true

一個"function"型別的物件,應該是由Function函式生成的,那它的prototype屬性應該指向Function.prototype,也就是Function.prototype.__proto__ === Function.prototype

為了避免迴圈引用,所以JavaScript規定Function.prototype.__proto__ === Object.prototype,這樣既避免了出現迴圈引用,又讓__proto__構成的原型鏈指向了唯一的終點:Object.prototype.__proto__ === null

5.JavaScript原型鏈繼承實踐
function me() {
    this.first_name = 'xu'
    this.last_name = 'ruilong'
}
function you() {
    this.first_name = 'nb'
}
you.prototype = new me()
you = new you()				//通過you類建立物件you

you類繼承了me類的last_name屬性。

in fact,在呼叫you.last_name的時候,JavaScript執行以下操作:

​ 1.在物件you尋找last_name

​ 2.如果找不到,則在you.__proto__尋找last_name

​ 3.如果還找不到,則在you.__proto__.__proto__中尋找last_name

​ 4.本題you.__proto__.__proto__.__proto__.__proto__ === null

6.JavaScript汙染實踐(一)
var xluo = {
    age : 3
}
xluo.__proto__.age = 18
xluoxluo = {}
console.log(xluoxluo.age)				//已汙染

因為我們修改了xluo的原型,而xluo和xluoxluo同樣是Object類的例項,所以事實上我們修改了Object,給Object增加了屬性age,值為18。

所以,in the same app,If an attacker controls and modifies the prototype of an object, it will be able to affect all objects that come from the same class as the object. This attack method is prototype chain pollution.

在JavaScript中訪問一個物件的屬性可以用a.b.c或者a["b"]["c"]來訪問。由於物件是無序的,當使用第二種方式訪問物件時,只能使用指明下標的方式去訪問。因此我們可以通過a["__proto__"]的方式去訪問其原型物件。

7.JavaScript汙染實踐(二)

現在我們大概弄清了有關原型鏈汙染的基礎知識,那麼現在構建一串程式碼

function merge(a,b){				//merge(a,b)意為合併a,b
    for (flag in b){
        if(flag in a && flag in b){
            merge(a[flag],b[flag])
        }
        else{
            a[flag] = b[flag]
        }
    }
}
xluo = {}
xluoxluo = JSON.parse('{"flag":1,"__proto__":{"flagflag":2}}')
merge(xluo,xluoxluo)
console.log(xluo.flag,xluo.flagflag)

xluogood = {}
console.log(xluogood.flagflag)

JSON.parse() 方法用於將一個 JSON 字串轉換為物件。

此時通過__proto__汙染Object。

7.JavaScript汙染實踐(三)ctf

以下是來自 hackit 2018 的一道題目

(該題目原文連結https://www.smi1e.top/javascript-%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93/)

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

對於以上的程式碼,我們首先關注到if語句,這是一個很明顯的判斷語句。

我們可以發現訪問admin獲取flag需要user.admintoken===req.query.querytoken

user = [],他是Array的例項,繼承自Array.prototype

api介面處有一處賦值操作,我們可以通過__proto__Array.prototype進行賦值。