背景
OSN 是一种 fee on transfer 代币,会根据用户分红账户的余额对用户发放分红。攻击者利用漏洞增发分红账户的余额,随后触发分红机制完成获利。
OSN:https://bscscan.com/address/0x810f4c6ae97bcc66da5ae6383cc31bd3670f6d13#code
攻击由三笔交易组成:
- https://app.blocksec.com/explorer/tx/bsc/0xbf22eabb5db8785642ba17930bddef48d0d1bb94ebd1e03e7faa6f2a3d1a5540
- https://app.blocksec.com/explorer/tx/bsc/0x69c64b226f8bf06216cc665ad5e3777ad1b120909326f120f0816ac65a9099c0
- https://app.blocksec.com/explorer/tx/bsc/0xc7927a68464ebab1c0b1af58a5466da88f09ba9b30e6c255b46b1bc2e7d1bf09
Trace 分析
攻击交易1
攻击者首先用 BUSD 换出 OSN 代币
然后将少量 BUSD 和 OSN 发放到新创建的地址中
攻击交易2
攻击者操控攻击交易1中创建的账户向 pair 添加流动性
攻击交易3
攻击者首先闪电贷 50000 BUSD
随后购入 70000 OSN
向 pair 添加流动性,获得 53032907572135909484703 流动性代币
把流动性代币发送到攻击合约,然后调用其 addLiq
函数 burn 35524 流动性代币(交易2中添加的),取回资产。随后将之前收到的 53032907572135909484703 流动性代币转移到下一个合约,不断重复这个操作。
移除流动性,取回 BUSD 和 OSN 代币,然后反复执行 BUSD -> OSN 和 OSN-> BUSD 两种的兑换操作。
执行攻击合约的 cc
函数,向 pair 添加流动性,触发 OSNLpDividendTracker.setBalance
函数,随后给攻击合约分红 55 BUSD。最后将攻击合约内全部的 121 BUSD 转移到攻击者地址。
从调试界面可以知道攻击合约内原有 66 BUSD,加上分红得到的 55 BUSD,一共 121 BUSD。
最后攻击者归还闪电贷 500250 BUSD,获利 1767 BUSD。
漏洞分析
攻击者在第一笔交易中创建了大量的账户,然后在第二笔交易中通过这些账户向 pair 添加少量的流动性
在 OSN._transfer
函数中,会记录流动性账户 userInfo
所持有的流动性代币,以及分红账户所持有的流动性代币。
随后在第三笔交易中,攻击者首先是添加流动性获取到大量的流动性代币,然后把流动性代币发送到攻击合约,再移除攻击合约之前添加的流动性。
此时会进入到 OSN._transfer
函数的另一个分支------移除流动性分支。函数先获取了 to
地址的流动性代币余额,然后通过 lpDividendTracker.setBalance
函数将该数值记录到 to
地址的分红账户上。
从下面的 trace 可以看出,攻击合约的 newBalance 被设为了一个很大的数值。随后攻击者故技重施,将这笔流动性代币转移到下一个攻击合约并更新其 newBalance 值,在创建的多个攻击合约之间不断重复这个操作。
随后攻击者移除了流动性,取回 BUSD 和 OSN 代币,然后反复执行 BUSD -> OSN 和 OSN-> BUSD 两种的兑换操作。
当 OSN -> BUSD 时,会执行红框内的三个函数
swapAndSendDividends
:将 OSN -> BUSD,然后将 BUSD 发送给lpDividendTracker
,随后调用lpDividendTracker.distributeDividends
函数。burnPoolToken
:burn 掉 pair 中一部分的 OSN 代币。swapAndAddLiqidity
:将 OSN -> BUSD,把 4/7 BUSD 发送给marketAddress
,把 3/7 BUSD 发送给lpDividendTracker
,随后调用lpDividendTracker.distributeDividends
函数。
lpDividendTracker.distributeDividends
函数的主要功能就是累加奖励数值 magnifiedDividendPerShare
,也就是说每调用一次该函数,奖励就增加一次。这也就是攻击者反复进行 swap 操作的原因。
在进行完 swap 操作后,攻击者调用攻击合约的 cc
函数向 pair 添加少量流动性,目的是触发分红发放函数来获利。
函数调用流程如下:_transfer -> setBalance -> processAccount -> _withdrawDividendOfUser -> withdrawableDividendOf -> accumulativeDividendOf
accumulativeDividendOf
函数计算用户可以分得的分红,其中 magnifiedDividendPerShare
被攻击者通过反复 swap 操控,而 balanceOf 则被那笔复用的流动性代币所操控。
在 _withdrawDividendOfUser
函数中,根据被操控的分红金额向攻击地址进行分红,完成此次攻击的获利。
后记
原本以为代币上的安全问题不会太复杂,比较一个代币的代码量相比其他 DeFi 协议还是比较小的。但是查看了代码以后才发现这个代币还是挺复杂的,在 ERC20 的基础上加了很多机制:收税,通缩,分红,回购等等。所以在本篇文章也只是跟踪关键的几个受影响的变量进行追踪分析,如果读者想要深入全面了解 OSN 代币的机制的话可以阅读他的源码。如果文章有哪些地方分析有误也请多多指教,感谢你的阅读。