作者:LoRexxar'@知道创宇404区块链安全研究团队
时间:2018年11月1日
系列文章:
在知道创宇404区块链安全研究团队整理输出的《知道创宇以太坊合约审计CheckList》中,我们把超过10个问题点归结为开发者容易忽略的问题隐患,其中包括“语法特性”、“数据私密性”、“数据可靠性”、“gas消耗优化”、“合约用户”、“日志记录”、“回调函数”、“Owner权限”、“用户鉴权”、 “条件竞争”等,统一归类为“以太坊智能合约编码隐患”。
“昊天塔(HaoTian)”是知道创宇404区块链安全研究团队独立开发的用于监控、扫描、分析、审计区块链智能合约安全自动化平台,目前已经集成了部分基于opcode的审计功能。我们利用该平台针对上述提到的《知道创宇以太坊合约审计CheckList》中“以太坊智能合约编码隐患”类问题在全网公开的智能合约代码做了扫描分析。详见下文:
以太坊智能合约是以太坊概念中非常重要的一个概念,以太坊实现了基于solidity语言的以太坊虚拟机(Ethereum Virtual Machine),它允许用户在链上部署智能合约代码,通过智能合约可以完成人们想要的合约。
这次我们提到的问题多数属于智能合约独有问题,与我们常见的各类代码不同,在编写智能合约代码时还需要考虑多种问题。
在智能合约中小心整数除法的向下取整问题
在智能合约中,所有的整数除法都会向下取整到最接近的整数,当我们需要更高的精度时,我们需要使用乘数来加大这个数字。
该问题如果在代码中显式出现,编译器会提出问题警告,无法继续编译,但如果隐式出现,将会采取向下取整的处理方式。
错误样例
uint x = 5 / 2; // 2 正确代码 uint multiplier = 10; uint x = (5 * multiplier) / 2;
在合约中,所有的数据都是公开的。包括私有变量等,不得将任何带有私密性的数据储存在链上。
在合约中,许多开发者习惯用时间戳来做判断条件,例如
uint someVariable = now + 1; if (now % 2 == 0) { // now可能被矿工控制 }
now、block_timestamp会被矿工所控制,并不可靠。
contract EUXLinkToken is ERC20 { using SafeMath for uint256; address owner = msg.sender; mapping (address => uint256) balances; mapping (address => mapping (address => uint256)) allowed; mapping (address => bool) public blacklist; string public constant name = "xx"; string public constant symbol = "xxx"; uint public constant decimals = 8; uint256 public totalSupply = 1000000000e8; uint256 public totalDistributed = 200000000e8; uint256 public totalPurchase = 200000000e8; uint256 public totalRemaining = totalSupply.sub(totalDistributed).sub(totalPurchase); uint256 public value = 5000e8; uint256 public purchaseCardinal = 5000000e8; uint256 public minPurchase = 0.001e18; uint256 public maxPurchase = 10e18;
在合约中,涉及到状态变化的代码会消耗更多的,为了经可能优化gas消耗,对于不涉及状态变化的变量应该加constant来限制
合约中,交易目标可能为合约,因此可能会产生的各种恶意利用。
contract Auction{ address public currentLeader; uint256 public hidghestBid; function bid() public payable { require(msg.value > highestBid); require(currentLeader.send(highestBid)); currentLeader = msg.sender; highestBid = currentLeader; } }
上述合约就是一个典型的没有考虑合约为用户时的情况,这是一个简单的竞拍争夺王位的代码。当交易ether大于合约内的highestBid,当前用户就会成为合约当前的"王",他的交易额也会成为新的highestBid。
contract Attack { function () { revert(); } function Attack(address _target) payable { _target.call.value(msg.value)(bytes4(keccak256("bid()"))); } }
但当新的用户试图成为新的“王”时,当代码执行到require(currentLeader.send(highestBid));
时,合约中的fallback函数会触发,如果攻击者在fallback函数中加入revert()函数,那么交易就会返回false,即永远无法完成交易,那么当前合约就会一直成为合约当前的"王"。
当合约跑在链上之后,链上的一切数据都难以监控,对于一个健康的智能合约来说,记录合理的event,为了便于运维监控,除了转账,授权等函数以外,其他操作也需要加入详细的事件记录,如转移管理员权限、其他特殊的主功能。
fonction transferOwnership(address newOwner) onlyOwner public { ownner = newOwner; emit OwnershipTransferred(owner, newowner); }
fallback机制是基于智能合约的特殊性而存在的。对于智能合约来说,任何函数的执行都是通过交易来完成的,但函数的执行过程中可能会遇到各种各样的问题,在交易失败或者交易结束后,就会执行fallback来最后处理结果和返回。
而在合约交易中,执行的每一个操作都会花费巨大的gas,如果gas不足,那么fallback函数也会执行失败。在evm中规定,交易失败时,只有2300gas用于执行fallback函数,而2300gas只允许执行一组字节码指令。一旦遇到极端情况,可能会因为gas不够用导致某种情况发生,导致未知的不可挽回的后果。
例如
function() payable { LogDepositReceived(msg.sender); } function() public payable{ revert();};
避免owner权限过大
部分合约owner权限过大,owner可以随意操作合约内各种数据,包括修改规则,任意转账,任意铸币烧币,一旦发生安全问题,可能会导致严重的结果。
function destroy() onlyOwner public onlyOwner{ selfdestruct(owner); }
合约中不要使用tx.origin做鉴权
tx.origin代表最初始的地址,如果用户a通过合约b调用了合约c,对于合约c来说,tx.origin就是用户a,而msg.sender才是合约b,对于鉴权来说,这是十分危险的,这代表着可能导致的钓鱼攻击。
下面是一个范例:
pragma solidity >0.4.24; // THIS CONTRACT CONTAINS A BUG - DO NOT USE contract TxUserWallet { address owner; constructor() public { owner = msg.sender; } function transferTo(address dest, uint amount) public { require(tx.origin == owner); dest.transfer(amount); } }
我们可以构造攻击合约
pragma solidity >0.4.24; interface TxUserWallet { function transferTo(address dest, uint amount) external; } contract TxAttackWallet { address owner; constructor() public { owner = msg.sender; } function() external { TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance); } }
当用户被欺骗调用攻击合约,则会直接绕过鉴权而转账成功,这里应使用msg.sender来做权限判断。
https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin
在智能合约中,经常容易出现对交易顺序的依赖,如占山为王规则、或最后一个赢家规则。都是对交易顺序有比较强的依赖的设计规则,但以太坊本身的底层规则是基于矿工利益最大法则,在一定程度的极限情况下,只要攻击者付出足够的代价,他就可以一定程度控制交易的顺序。开发者应避免这个问题。
真实世界事件
使用Haotian平台智能合约审计功能可以准确扫描到该类型问题。
基于Haotian平台智能合约扫描功能规则,我们对全网的公开的共47305个合约代码进行了扫描。
其中存在数据可靠问题的合约共2732个,
存在int型变量gas优化问题的合约共18285个,
存在string型变量gas优化问题的合约共194个,
存在Owner权限过大或合约后门的合约共1194个,
存在tx.origin 鉴权问题问题的合约共52个。
截止2018年10月31日,我们发现了2732个存在数据可靠问题的合约代码,存在潜在的安全隐患。其中交易量最高的10个合约情况如下:
截止2018年10月31日,我们发现了18285个存在int型变量gas优化问题的合约代码,存在潜在的安全隐患。其中交易量最高的10个合约情况如下:
截止2018年10月31日,我们发现了194个存在string型变量gas优化问题的合约代码,存在潜在的安全隐患。其中交易量最高的10个合约情况如下:
截止2018年10月31日,我们发现了8321个存在复杂回调的合约代码,存在潜在的安全隐患。其中交易量最高的10个合约情况如下:
截止2018年10月31日,我们发现了1194个存在Owner权限过大或合约后门,其中交易量最高的10个合约情况如下:
截止2018年10月31日,我们发现了52个存在tx.origin 鉴权问题,其中交易量最高的10个合约情况如下:
在智能合约中小心整数除法的向下取整问题,可以通过先乘积为整数再做处理。
uint multiplier = 10; uint x = (5 * multiplier) / 2;
在处理一些隐私数据是尽量保留在服务端,可以通过hash-commit的方式来check变量值。
尽量使合约内容不依赖时间顺序,如果需要外部变量影响,那尽量采用block.height和block.hash等这类难以控制的变量。
对于某些不涉及状态变化的函数和变量可以加constant来避免gas的消耗
合约中,应尽量考虑交易目标为合约时的情况,避免因此产生的各种恶意利用。
关键事件应有Event记录,为了便于运维监控,除了转账,授权等函数以外,其他操作也需要加入详细的事件记录,如转移管理员权限、其他特殊的主功能。
fonction transferOwnership(address newOwner) onlyOwner public { ownner = newOwner; emit OwnershipTransferred(owner, newowner); }
合约中定义Fallback函数,并使Fallback函数尽可能的简单。尽量避免在回调函数中调用transfer、call等涉及状态变化的操作,避免gas不够用直接导致未知情况发生。
部分合约owner权限过大,owner可以随意操作合约内各种数据,包括修改规则,任意转账,任意铸币烧币,一旦发生安全问题,可能会导致严重的结果。
关于owner权限问题,应该遵循几个要求:
在需要用户鉴权的时刻,尽量使用msg.sender作为目标方。 https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin
在智能合约的设计中,避免对交易顺序的依赖,或者想办法强制要求交易顺序。
在这一次整理合约编码隐患的过程中,对智能合约本身的特殊性进行了深入了解。和每个语言一样,智能合约有基于区块链这个大前提在,许多代码都出现了新的问题,如果开发者没有注意到这些隐患,一旦出现问题,这些隐患就可能导致更大的问题发生。
截止2018年10月31日,以太坊合约审计Checklist的所以问题完成了第一轮扫描,第一轮扫描针对以太坊公开的所有合约,其中超过80%的智能合约存在1个以上的安全隐患问题。在接下来的扫描报告中,我们会公开《以太坊合约审计Checklist》并使用HaoTian对以太坊公链上的所有智能合约进行基于opcode的扫描分析。
智能合约审计服务
针对目前主流的以太坊应用,知道创宇提供专业权威的智能合约审计服务,规避因合约安全问题导致的财产损失,为各类以太坊应用安全保驾护航。
知道创宇404智能合约安全审计团队: https://www.scanv.com/lca/index.html
联系电话:(086) 136 8133 5016(沈经理,工作日:10:00-18:00)
区块链行业安全解决方案
黑客通过DDoS攻击、CC攻击、系统漏洞、代码漏洞、业务流程漏洞、API-Key漏洞等进行攻击和入侵,给区块链项目的管理运营团队及用户造成巨大的经济损失。知道创宇十余年安全经验,凭借多重防护+云端大数据技术,为区块链应用提供专属安全解决方案。