链资讯 链资讯
Ctrl+D收藏链资讯
首页 > Polygon > 正文

Rust 智能合约养成日记: 合约安全之重入攻击

作者:

时间:

往期回顾:

Rust智能合约养成日记合约状态数据定义与方法实现Rust智能合约养成日记编写Rust智能合约单元测试Rust智能合约养成日记Rust智能合约部署,函数调用及Explorer的使用Rust智能合约养成日记Rust智能合约整数溢出这一期中我们将向大家展示Rust合约中重入攻击,并提供给开发者相应的建议。本文中的相关代码,已上传至BlockSec的Github上,读者可以自行下载:https://github.com/blocksecteam/near_demo

1.重入攻击原理

我们用现实生活中的简单例子来理解重入攻击:即假设某用户在银行中存有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智能合约。

Binance礼品卡服务已集成至多链非托管钱包Trust Wallet:9月1日消息,Binance礼品卡服务已集成至多链非托管钱包Trust Wallet,目前无法使用Binance服务的地区也可以使用该礼品卡服务。此外,Binance还将在9月推出Trust Wallet系列礼品卡。[2022/9/1 13:01:58]

2.背景知识:NEP141的转账操作

NEP141为NEAR公链上的FungibleToken标准。大部分NEAR上的Token都遵循NEP141标准。

当某一用户想要从某一个Pool中,如去中心化交易所,充值(deposite)或者提现(withdraw)一定数额的Token时,用户便可以调用相应的合约接口完成具体的操作。

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函数中自行维护内部账户的余额管理。

3.代码重入的具体实例

假设存在如下3个智能合约:

合约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???}?}}

TrustBase生态通证TBE首发上线Gate,最高涨幅达916%:北京时间2021年5月18日, TrustBase生态通证上线Gate StartUP,并于20:00开启交易,截至目前,TBE最高报价0.11USDT,最高涨幅916%。

据悉,TBE令牌是TrustBase基于Substrate协议发行的生态通证,现已在HECO生态开放TBE Pool质押挖矿,同开三个矿池,适应多种持币用户选择。

TBE通证将用于激励并维护生态的健康发展,串联TrustBase生态各参与角色形成正向流转。?

注:数字资产是一种高风险的投资方式,本信息仅供消息分享,不构成投资建议。[2021/5/19 22:20:02]

合约C:Token合约。

攻击发生前,因为Attacker账户没有从Victim合约提现,所以余额为0,此时Victim合约(DEX)的余额为100+100=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中后续的步骤。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,?????))?}...}?

TrustBase尔尔:宏观角度来说,牛市会持续很久,从微观角度来讲,做好生态永远都是牛市:在4月12日举办的《佟掌柜的海外朋友们优质海外项目分享》会上,TrustBase中国区负责人尔尔表示,对长久的项目而言没有牛熊市之分,做好产品,服务好客户,做好生态,则是永远的牛市。投机者看到牛市只是挣快钱,对于长期持有者或者共识者来讲,他们看重的是底层技术和生态规划,没有技术就没有生态,我们其实是基于原生元语言包建立起整个生态,不管技术生态还是开发者生态,或是社区生态,以及后期的模型生态,都是基于技术产生的,两者相辅相成。

关于细分赛道的展望,第一是NFT,Trustbase平台会推行大家第一批在实现NFT商业落地,因为开发周期足够短,另外可以产生更多的流量,毕竟金融型用户比应用型用户少很多,所以能让NFT创造更多商业价值,趁着波卡在NFT上发行是我们比较看好的赛道。第二是DeFi,这是不会衰退的赛道,金融本身是区块链的核心根本,DeFi的发展是随着区块链在不断地起伏,但永远不会衰落,这是作为区块链从业者的信仰。

Trustbase底层是波卡原生元,具有扎实底层技术的项目来讲生态规划比较长远,相对来说不惧牛熊,主要是基于未来的生态和规划。[2021/4/13 20:14:00]

合约B中的withdraw函数接着将调用合约C中的ft_transfer_call函数;通过上述代码中的ext_ft_token::ft_transfer_call实现跨合约调用。

合约C中的ft_transfer_call函数,将更新attacker账户的余额=0+60=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()?}...}

动态 | 美国能源公司Crusoe通过比特币挖矿解决天然气能源过剩问题:据彭博社12月7日消息,总部位于丹佛的Crusoe能源系统公司正在利用部分剩余的天然气将其转化为电力,为数据中心提供动力,这些数据中心又通过比特币挖矿来产生收入。他们称这些数据中心为解决日益严重的天然气燃除问题的一种方法,即燃烧掉多余的天然气。Upper90 Capital Management LLC首席投资官Alex Urdea表示:“这是解决石油和天然气行业环境和经济问题的一种非常有创意的方式,该公司已同意向Crusoe提供4000万美元的项目融资。”他补充说,这种商业模式吸引了大型石油和天然气生产商的兴趣,并且最终可能涉及收益分享。[2019/12/8]

由于合约A被Attacker所控制,并且代码存在恶意的行为。所以该“恶意”的ft_on_transfer函数可以再次通过执行ext_victim::withdraw,调用合约B中的withdraw函数,以此达到重入的效果。#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账户的余额=60+60=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,因此将导致整数的溢出现象。最终执行的结果如下:

动态 | 传统信托公司Legacy Trust推出独立加密监管业务:香港传统信托公司Legacy Trust正在推出一项行业级加密货币托管解决方案的新业务,通过公司First Digital Trust实现。First Digital Trust有独立的合作伙伴和股东,将Legacy Trust现有的数字资产托管部门发展成为一个独立的实体。这将使Legacy Trust专注于其传统的信托和养老金业务,同时赋予First Digital Trust在数字资产托管交付方面的灵活性。据悉,本月早些时候,Legacy Trust宣布为参与公司和自雇人士提供基于加密货币的养老金计划。(CoinDesk )[2019/9/11]

$node?Triple_Contracts_Reentrancy.js?FinishinitNEARFinishdeploycontractsandcreatetestaccountsVictim::attacker_balance:3.402823669209385e+38FT_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问题,敬请关注。

标签:ACKRANKENLANC6 Pack RickData TransactionOG Fan TokenDisbalancer

Polygon热门资讯
OOT:Loot终极指南,看这一篇就够了!_LooksCoin

来源:DappRadar作者:HristinaYordanova 编译:小回 什么是Loot及其衍生品LootforAdventurers发布还不到一周,已经在NFT社区中引起了巨大轰动.

数字人:数字人民币苏州试点答卷:“综合科技服务商”价值初显现_OOKS

这个双十二,数字人民币再次走进公众视野,金融与科技又一次碰撞出不一样的火花。一、数字人民币试点再突破,线上场景等诸多亮点引关注继10月深圳罗湖数字人民币试点之后,苏州成为了又一个派发数字人民币红.

数字人:数字人民币试点调查:苏州试水双离线支付,香港欲打通跨境支付闭环_LOOKS

本文来源:21世纪经济报道,作者:陈植随着央行法定数字人民币试点步伐提速,越来越多地方政府都在积极争取数字人民币试点.

TRA:Findora宣布完成业务战略调整,图灵奖得主与多名高级开发者加入团队,明年1季度上线主网_FMS Token

今日,金融隐私公链Findora宣布完成对业务发展的系列战略性调整:由第一阶段公链与隐私技术应用结合的科研研究,升级为下一阶段对技术落地及应用场景的实现.

ANC:P2E智库Salad Ventures完成1350万美元融资,Polygon Studios等参投_GAS

据GamesBeat2月12日报道,区块链和Play-to-Earn智库SaladVentures完成1350万美元私募融资.

区块链:陈晓华:“五链“并发推动数字经济发展新高地_npc币中立金融链

“十四五”规划纲要在“打造数字经济新优势”章节中明确了七大数字经济重点产业,并强调“要充分发挥海量数据和丰富应用场景优势,促进数字技术与实体经济深度融合,赋能传统产业转型升级.