更新时间:2021-11-14 12:57:01点击:143
对data-v-3f0fa3ab data-v-5f989728的历史回顾:
Rust智能合约培养日记(1)合同状态数据的定义和方法Rust智能合约培养日记的实现)2) Rust智能合约单元测试Rust智能合约培养日记的制作)3) Rust智能合约的导入、 函数调用和浏览器的使用Rust智能合约培养日记)4) Rust智能合约整数溢出期间展示Rust合约中的再入攻击本文的相关代码已上传到BlockSec的Github上。 读者可以自己下载。 3359 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部分展示了NEAR LocalNet中具体的入口攻击示例,说明了代码对引入NEAR链的智能合约的入口的危险性。 在本文的最后,我们将具体介绍防止再次攻击的技术,以帮助创建Rust智能合约。
NEP141是NEAR公链上的Fungible Token (以下均简称Token )标准。 大多数NEAR的Token都符合NEP141标准。
如果用户从某个Pool请求一定数量的Token,例如DEX、deposite、withdraw等,用户可以调用适当的合同接口进行具体操作。
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 )的余额增加之外,还会产生应收帐款此时,Token合同提醒用户注意receiver_id合同,并有用户存入指定金额的Token。 receiver_id合同使用ft_on_transfer函数自己维护内部帐户的余额管理。
假设您有三个智能合约:
合同A: Attacker合同攻击者利用该合同实施后续攻击交易。 合同B: Victim合同。 是DEX合同。 初始化时,Attacker账户有100余额,DEX的其他用户有100余额。 也就是说,此时DEX合同总共拥有200个Token。 # #“near _ bind gen”#“derive (边界序列化, 基本化) ]物理连接(attacker _ balance 3360 u 1220 ) Impldefaultforvictimcontract (-自适应)
因为攻击发生前,Attacker账户没有从Victim合同中提取,所以余额为0,此时Victim合同(DEX )的余额为100 ) 100=200。
# #“near _ bind gen”#“derive (边界序列化, borshserialize ] pubstructfungibletoken (attacker _ balance 3360 u 128 victim _ balance 3360 u 128 ) impldefaultforfungibletoken (应用程序平衡3360 u 128 )
Attacker合同通过malicious_call函数调用Victim合同(合同b )中的withdraw函数; 例如,在这种情况下,Attacker传递给withdraw函数的amount参数的值为60,希望从合同b中取出60;
implmaliciouscontract (自动: u 128 (ext _ victim 33603360 withdraw ) Amount.into ) . (self.Attacker_balance=amount ) `检查attacker帐户中是否有足够的余额。 此时,余额10060将通过断言执行withdraw的后续步骤。 implvictimcontract (pubfnwithdraw ) mutself,amount:u128 ) -承诺! (self.attacker_balance=amount )//呼叫attacker的收款函数ext _ ft _ token :3360 ft _传输_呼叫(amount.into env :3360前置_ gas (-gas _ for _ single _ call *2).then (退出_自助3360:传输) }.}合同b中的withdraw函数然后调用合同c(ft_token合同)中的ft_transfer_call函数。 在上述代码的ext _ ft _ token :3360 ft _ transfer _ call中实施合同间呼叫。
合同c中的ft_transfer_call函数更新attacker账户的余额=0 60=60,以及Victim合同账户的余额=200 - 60=140,然后是ext _ fungible _ token _。 implfungibletoken { pubfnft _ transfer _ call (mut self,amount:u128 )-promiseorvalueu128(//) ) ) )。 self.victim _ balance-=亚马逊;//呼叫链接器的收款函数ext _ fungible _ token _ receiver 33603360 ft _ on _ transfer (Amount.into ),链接器,0, 因此,这个“恶意”的ft_on_transfer函数通过再次执行ext_victim:withdraw来重新调用合同b的withdraw函数# # [ near _ bind gen ] implmaliciouscontract { pubfnft _ on _ transfer (mut self,amount:u128(//恶意合同的接收函数if self.) ……自从上次进入withdraw后,victim合同的attacker_balance还没有更新,所以还是100,所以这个时候也可以通过assert了! (self.attacker_balance=amount )的检查。 然后,withdraw再次在FT_Token合同中在合同之间调用ft_transfer_call函数,attacker账户的余额=60 60=120,以及Victim合同账户的余额=140-60 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-660
$ node triple _ contracts _ reen trancy.jsfinishinitnearfinishdeploycontractsandcreatetestaccountsvictim 3603360 atacker _ 尽管nce :120 ft _ token :3360 victim _ balance 336080,即用户Attacker在DEX上锁定的fungibletetacker
4.1更新状态后再转账。
更改合同b代码withdraw的执行逻辑如下:
# # near _ bind gen implictimcontract { pubfnwithdraw、amount : u 128 } -承诺! (self.attacker_balance=amount )自身. attacker _ balance-=amount;//呼叫链接器的收款函数ext _ ft_token : ft _传输_呼叫(Amount.into ),ft _ token,0, env :3360前置_ gas (-gas _ for _ single _ call *2).then (退出_自助3360:传输) ) # [私有] pubfnft _解决_传输(mut self,amount:u128 )匹配资源336666 )、传输资源33603360成功; }此时的执行效果如下。
$ node triple _ contracts _ reen trancy.jsfinishinitnearfinishdeploycontractsandcreatetestaccountsreceipt 360873 C5 wqmyaxbfm广播公司3360错误: { '索引' :0、 ' kind ' : { '执行元件' : '智能控制封装3360封装的at '辅助故障3360自身.附件_电缆连接器victim :3360连接符_平衡:40 ft _连接符:3360连接符_平衡336060 ft _连接符3360: victim _连接符cer 存储在Victim合同中的attacker_balance已更新为40,因此无法通过声明! (self.attacker_balance=amount )由于Attcker的调用过程触发了Assertion Panic,因此无法再利用代码重新输入进行仲裁。
4.2引入互斥
这种方法类似于店员-A没有时间将用户的余额更新为40元时,用户走到旁边告诉另一店员-B“想取60元”。 尽管用户刚才向橱柜-A隐瞒了取钱的事实。 但是,柜员-B可以知道用户去了柜员-A那里,如果还没有做完所有事项,柜员-B可以拒绝用户取钱。 通常,可以通过引入状态变量实现互斥
4.3 gas限制的设定
例如,当DEX合同的withdraw方法调用ext _ ft _ token :3360 ft _ transfer _ call时,将设置相应的gas限制。 该Gas Limit支持以下代码重新注册到DEX合同的withdraw函数,因此无法阻止重新注册攻击。
例如,将代码修改为在withdraw方法调用外部函数时限制Gas Limit:如下所示
pubfnwithdraw (自动: u 128 ) -推荐! (self.attacker_balance=amount )//呼叫attacker的收款函数ext _ ft _ token :3360 ft _传输_呼叫(amount.into - ENV :3360前置_ GAS (-GAS _ for _ Single * 2g AS _ for _ Single _ call *3).Then _ Sent _ Self 33333330 ENV 3630 ENV
$ node triple _ contracts _ reen trancy.jsfinishinishdeploycontractsandcreatetestaccountsreceipt 33605 xsywur4sepqfur 3360错误: { '索引' :0、 ' kind ' : { '执行元件' : ' exceededtheprepaidgas.' } victim :3360连接板3360连接板336040 ft _ token 336040连接板
此次,对rust智能合约中的整数溢出问题进行了说明,另外,提出了在写入代码时尽量更新状态后执行转账操作,设定适当的gas值,从而有效地防止再次输入攻击。 下一期将对rust智能合约中的DoS问题进行说明,敬请关注。