以太坊智能合约编码安全之Call注入
摘要: Solidity作为一种用于编写以太坊智能合约的图灵完备的语言,除了常见语言特性以外,还提供了调用/继承其他合约的功能。在call、delegatecall、callcode三个函数来实现合约之间相互调用及交互。
前言
Solidity作为一种用于编写以太坊智能合约的图灵完备的语言,除了常见语言特性以外,还提供了调用/继承其他合约的功能。在call、delegatecall、callcode三个函数来实现合约之间相互调用及交互。正是因为这些灵活各种调用,也导致了这些函数被合约开发者“滥用”,甚至“肆无忌惮”提供任意调用“功能”,导致了各种安全漏洞及风险。
上述代码就是一个典型的存在call注入问题直接导致重入漏洞的demo。
2016年7月,The DAO被攻击者使用重入漏洞取走了所有代币,损失超过60亿,直接导致了eth的硬分叉,影响深远。
2017年7月20日,Parity Multisig电子钱包版本1.5+的漏洞被发现,使得攻击者从三个高安全的多重签名合约中窃取到超过15万ETH ,其事件原因是由于未做限制的 delegatecall 函数调用了合约初始化函数导致合约拥有者被修改。
2018年6月16日,有人在先知大会上提到了一种新的攻击场景——call注⼊,主要介绍了利用对call调用处理不当,配合一定的应用场景的一种攻击手段。
接着于 2018年6月20日,ATN代币团队发布《ATN抵御黑客攻击的报告》,报告指出黑客利用call注入攻击漏洞修改合约拥有者,然后给自己发行代币,从而造成 ATN 代币增发。
Solidity 的三种调用函数
在Solidity中,call函数簇可以实现跨合约的函数调用功能,其中包括call、delegatecall和callcode三种方式。
以下是Solidity中call函数簇的调用模型:
这些函数提供了灵活的方式与合约进行交互,并且可以接受任何长度、任何类型的参数,其传入的参数会被填充至32字节最后拼接为一个字符串序列,由EVM解析执行。
在函数调用的过程中,Solidity中的内置变量msg会随着调用的发起而改变,msg保存了调用方的信息包括:调用发起的地址,交易金额,被调用函数字符序列等。
三种调用方式的异同点
call: 最常用的调用方式,调用后内置变量msg的值会修改为调用者,执行环境为被调用者的运行环境(合约的 storage)。
delegatecall: 调用后内置变量msg的值不会修改为调用者,但执行环境为调用者的运行环境。
callcode: 调用后内置变量msg的值会修改为调用者,但执行环境为调用者的运行环境。
delegatecall滥用问题
delegatecall: 调用后内置变量msg的值不会修改为调用者,但执行环境为调用者的运行环境。
原理
在智能合约的开发过程中,合约的相互调用是经常发生的。开发者为了实现某些功能会调用另一个合约的函数。比如下面的例子,调用一个合约A的test()函数,这是一个正常安全的调用。
但是在实际开发过程中,开发者为了兼顾代码的灵活性,往往会有下面这种写法:
这将引起任意 public 函数调用的问题:合约中的delegatecall的调用地址和调用的字符序列都由用户传入,那么完全可以调用任意地址的函数。
除此之外,由于delegatecall的执行环境为调用者环境,当调用者和被调用者有相同变量时,如果被调用的函数对变量值进行修改,那么修改的是调用者中的变量。
call 安全问题
call: 最常用的调用方式,调用后内置变量msg的值会修改为调用者,执行环境为被调用者的运行环境。
call注入是一种新的攻击场景,原因是对call调用处理不当,配合一定的应用场景的一种攻击手段。
call 注入原理
call 调用修改 msg.sender 值
通常情况下合约通过call来执行来相互调用执行,由于call在相互调用过程中内置变量msg会随着调用方的改变而改变,这就成为了一个安全隐患,在特定的应用场景下将引发安全问题。
外部用户通过call函数再调用合约函数:
高度自由的 call 调用
在某些应用场景下,调用函数可以由用户指定;下面是call函数的调用方式:
从上面可以看出,call函数拥有极大的自由度:
1.对于一个指定合约地址的call调用,可以调用该合约下的任意函数
2.如果call调用的合约地址由用户指定,那么可以调用任意合约的任意函数
为了便于理解,可以将智能合约中的call函数类比为其他语言中的eval函数,call函数相当于给用户提供了随意调用合约函数的入口,如果合约中有函数以msg.sender作为关键变量,那么就会引发安全问题。
call 函数簇调用自动忽略多余参数
call函数簇在调用函数的过程中,会自动忽略多余的参数,这又额外增加了call函数簇调用的自由度。下面的例子演示call自动忽略多余参数:
例子中test()函数仅接收一个uint256的参数,但在callFunc()中传入了三个参数,由于call自动忽略多余参数,所以成功调用了test()函数。
callcode 安全问题
callcode: 调用后内置变量msg的值会修改为调用者,但执行环境为调用者的运行环境。
由于callcode同时包含了call和delegatecall 的特性,通过上文对call和delegatecall的安全问题进行了分析和举例,可以得出的结论是call和delegatecall存在的安全问题将同时存在于callcode中,这里不再进行详细的分析。
总结
针对文中所提到的安全隐患,建议:
1.call、callcode、delegatecall调用的自由度极大,并且call会发生msg值的改变,需要谨慎的使用这些底层的函数;同时在使用时,需要对调用的合约地址、可调用的函数做严格的限制。
2.call与callcode调用会改变msg的值,会修改msg.sender为调用者合约的地址,所以在合约中不能轻易将合约本身的地址作为可信地址。
3.delegatecall与callcode会拷贝目标代码到自己的环境中执行,所以调用的函数应该做严格的限制,避开调用任意函数的隐患。
4.智能合约在部署前必须通过严格的审计和测试。
本文内容由 曲速未来 (WarpFuture.com) 安全咨询公司编译,转载请注明。 曲速未来提供包括主链安全、交易所安全、交易所钱包安全、DAPP开发安全、智能合约开发安全等相关区块链安全咨询服务。
(作者:区块链安全档案,内容来自链得得内容开放平台“得得号”;本文仅代表作者观点,不代表链得得官方立场)
评论(0)
Oh! no
您是否确认要删除该条评论吗?