shaneson.eth

Posted on Sep 25, 2022Read on Mirror.xyz

合约攻防比赛--- tctf 《NFT Market》

凌晨12点半左右,咖啡突然让我帮忙打个合约安全比赛。我想着先看看题,然后,不小心就通宵了~~~~~

背景

这道题的难度设计非常有意思,最难的地方利用了0.8.15以下的编译器漏洞。注意:0.8.15以下的编译器都会有这种漏洞。原因:0.8.15在动态内存管理的时候,由于太过着急清除内存,导致了数据丢失,在特定的情况下可以被黑客利用,从而绕开检验,从而进行攻击。由于攻击条件太过苛刻,所以一直没有被黑客利用。但,我们在设计calldata结构的时候,还是要知道有这个的存在~~

题目分析

题目非常简单:

存在一个NFT Market合约,在初始化的时候会产出3个NFT,分别是NFT 1,NFT 2,和NFT 3。黑客需要攻击NFT Market,从而窃取这三个NFT。这三个NFT都分别上架道NFT Market上,价格为:1, 1337, 133333337。

这个时候,NFT Order为:[ #1, #2, #3]

攻击 NFT 1

NFT 1是最容易获取的,我们先触发一下airdrop,拿到5个TcffToken,然后用1个Token来买NFT 1就可以了。

此时,

黑客余额:4 Token

NFT Order为:[ #3, #2]

攻击 NFT 2

Tips:

NFT Market有余额:1337

用户拥有的NFT: NFT1

窃取NFT 2需要用到 purchase Test函数(如下),注意到NFT 2和 NFT 3的owner是NFT Market合约本身。所以,这里刚好利用purchase Test函数,让黑客创建一个NFT 1价值1337的order,然后让NFT Market通过purchase Test函数利用1337余额去买用户手里的NFT 1。这样,用户就可以获得 1337 + 4的余额。

然后,就有足够的金额去购买NFT 2。(价值1337,用户的余额是1337 + 4 )。这里不着急把NFT 1买回,因为攻击3的时候会用到。但为了让用户可以把NFT 1买回,这里需提前插一个NFT 1的订单(因为此时NFT 1还是属于黑客)。

攻击步骤:

此时,

NFT Order为:[ #1, #3]

攻击 NFT 3

攻击NFT 3 非常困难,需要利用到0.8.15以下的编译器漏洞。这个漏洞的详细报告在:

https://blog.soliditylang.org/2022/08/08/calldata-tuple-reencoding-head-overflow-bug/

总结一下就是:

这个错误源于,编译器在ABI编码期间将calldata数组复制到内存中时过度急于清理的结果。内存中的数组总是占用32字节的倍数,当基本类型没有填满整个字时,未使用的空间保证被清零,并在所有高级Solidity操作后保持清洁。在受影响的情况下,编译器会发出代码,错误地清理存储在元组最后一个组件中的数组的末端,将属于元组第一个动态组件的32字节清零。

我们要利用这个漏洞,绕过verifiCoupon的合约验签,从而黑客产生一个可以修改价格的Coupon。(当时我和薛神在纠结如何通过合约地址,暴力破解反推出私钥。看来思路是错的,应该是攻击calldata从而绕过签名验证

攻击步骤:

a、创建一个FakeNFT ,并且插入到NFT Market之中。此时,NFT OrderList:

NFT Order为:[ #1, #3, #fakeNFT]

然后我们把#1用1 Token买回,此时:

NFT Order为:[#fakeNFT,#3]

b、call data攻击,攻击的步骤很简单,就是利用上面说的编译器漏洞,然后coupon的orderId传1,但是orderInfo传0。由于编译器漏洞,在verify的时候,orderId1,会以order 0进行验证。

然后 NFT 3的价格就可以被我们修改成1 Token了。我们买回NFT 3即可。

源码

https://github.com/shanxuanchen/ParadigmCTF2022

NFT