# XCarnival 攻击事件分析及攻击模拟重现

By [xyyme.eth](https://paragraph.com/@xyyme) · 2022-07-01

---

XCarnival 是一个 NFT 借贷协议，用户可以将 NFT 抵押给协议，从而借出 token。同时也可以将 token 抵押给协议，获得利息收益。

这次攻击事件的原理是，用户在将 NFT 从协议中取出后，仍然可以借出 token。黑客利用这个 bug，不断新建合约，将其作为抵押人，抵押 NFT 并取出，然后再去借出 token，从而实现无本套利。

代码分析
----

XCarnival 的核心在于三个合约：

*   `XNFT`，用户抵押 NFT 入口
    
*   `XToken`，用户借款入口
    
*   `P2Controller`，校验用户的权限，例如订单是否可借出，用户是否可赎回等
    

这次的 bug 在于，用户可以将 NFT 抵押到协议之后，再取出 NFT，而由于合约借贷逻辑中，没有订单的状态进行判断，因此仍然可以调用借出方法进行借贷，造成资产流失。

我们先来看看抵押的代码：

![](https://storage.googleapis.com/papyrus_images/ff5e1f1765c5376e2dae0bc7e9c1d57b67365d1dcee2babc44a73b4c93259acd.png)

注意到抵押方法的 xToken 地址是作为参数传进去的，指定要借哪个 token。

再来看看取出 NFT 的方法（由于这段代码太长，我们只截取最重要的部分）：

![取出 NFT （前部分）](https://storage.googleapis.com/papyrus_images/d1428a035a94bfcede38a07641f107455383ac2e5249c729b34e76ba84069bcd.png)

取出 NFT （前部分）

![取出 NFT （后部分）](https://storage.googleapis.com/papyrus_images/6920c8dea839bf3f79df34f6e2923a57fbbe490d858df9b6ec8086a0ee4f2b93.png)

取出 NFT （后部分）

我们看到，在取出 NFT 的最后，将订单的 `isWithdraw` 置为了 true，指明订单已经被取出。

接下来我们看看借出 xToken 的部分：

![借出方法](https://storage.googleapis.com/papyrus_images/23b9dd4158e0b76016dad16c3ec04f87ba8f0bc9459cf7c7c1a51da14a247312.png)

借出方法

xToken 中会调用 `controller` 的各种 `allow`，`verify` 方法等来判断该 token 是否可借出。那么我们再看看 `controller` 中是如何实现的：

![借出检查](https://storage.googleapis.com/papyrus_images/9e885147a0ed1bf0a5b484e05420681ce10ce07f26d790d05da0d3e888504ce0.png)

借出检查

注意到没有，这里并没有对订单中的 NFT 是否取出，也就是上面的 `isWithdraw` 进行检查。如果我们先去抵押 NFT，这时已经生成了订单，然后再取出。此时再去调用 xToken 的 `borrow` 方法，那么就可以借款，而此时是没有任何抵押的。此次黑客事件就是利用了这个 bug。

攻击步骤
----

我们来看看黑客的几笔交易都在干什么：

1.  [从 OpenSea 中购买 BAYC （tokenId：5110）作为抵押物](https://etherscan.io/tx/0x16bb7799cf4e919bcb81f3ed531743ea6a6857e9a5121500fa1e3619bb2b82cf)
    
2.  [创建攻击合约](https://etherscan.io/tx/0xe4f99b2fb86a317eb16f7f288fda74ab07f0ffcbf645fb3b1a6490ca23206d09)
    
3.  [将 BAYC 转到攻击合约中](https://etherscan.io/tx/0x7cd094bc34c6700090f88950ab0095a95eb0d54c8e5012f1f46266c8871027ff)
    
4.  [新建内部合约并将其作为抵押人，抵押 NFT 后取出](https://etherscan.io/tx/0x422e7b0a449deba30bfe922b5c34282efbdbf860205ff04b14fd8129c5b91433)
    
5.  [内部合约行使借贷权利，从协议中借出 token](https://etherscan.io/tx/0xabfcfaf3620bbb2d41a3ffea6e31e93b9b5f61c061b9cfc5a53c74ebe890294d)
    

其中第四步的详细逻辑为：

![](https://storage.googleapis.com/papyrus_images/2cb784caeebec6bdb2a22725472b2de214279f2bf5ddc47b37fb5fce50e45e3c.png)

这样在一笔交易中就可以构建多个抵押人（即新建的合约）。在实际的攻击中每一笔交易新建了四个内部合约，然后多次调用该方法，就可以构建出许多的抵押人。并且这些抵押人在协议中已经没有了抵押，但是仍然有借出 token 的权利。

接下来第五步，利用内部合约抵押人的身份，在没有任何抵押的情况下，直接从协议中借出 token，完成套利。

注意黑客在调用抵押方法 `pledgeAndBorrow` 的时候，传入了自定义的 xToken 地址。由于在这一步只需要实现抵押，不需要借贷，因此传入自定义的 xToken，可以避免 xToken 自带的一堆校验。

整体的逻辑还是比较简单的，我们也来尝试写写攻击合约。

攻击重现
----

要重现攻击，就需要模拟主网的各种状态，这时我们可以利用 hardhat 的 fork 功能，不熟悉的朋友可以看看我之前写的一篇[文章](https://mirror.xyz/xyyme.eth/Z2qjTJJtaQHcwLc-9yHOGpXbeE7RHGdm5EafGjq7qhw)，对其用法有详细的解释。

黑客的第一笔交易发生在区块 15028718 中，是购买 BAYC 的交易，那么我们就 fork 区块 15028720，并且使用 `hardhat_impersonateAccount` 模拟黑客的地址，那么此时在本地我们就拥有了 BAYC（5110），只需要关注攻击逻辑本身即可。

根绝我们前面的分析，在第四步中，需要新建内部的辅助合约，并具有抵押、取出、转移 NFT 的功能，最重要的，需要有借出 xToken 的功能。同时，在外部的攻击合约中，需要维护一个内部合约的地址列表，方便后续集中调用借款方法。

综上，我们最终实现的内部辅助合约为：

![内部辅助合约](https://storage.googleapis.com/papyrus_images/893995e1c21239f3aab868d2b4f3ddc27ced1f153a46f1298b566d10076b1efd.png)

内部辅助合约

外部攻击合约为：

![外部攻击合约](https://storage.googleapis.com/papyrus_images/c1a778be83a574cc7a7220d42687e5f189769279fdf6ad1319d5d39e4f6fee70.png)

外部攻击合约

感兴趣的朋友可以多看几遍，结合注释好好理解一下，我们不再详细解释。

接着我们在本地自测，调用两次 `prepare` 之后调用 `attack`，测试结果为：

> balance before hack is 27.69746933937151467 balance after hack is 315.51434111624879089

差值恰好是 `36 * 4 * 2 = 288`，说明我们模拟成功。

总结
--

这次的 bug 原因在于在一个很小的细节，也就是 NFT 取出后没有修改状态。这种小问题，审计人员也没有查出来，说明细节真的很重要。我们在编写合约的时候，对各个状态的变更都要仔细考虑几遍。

关于我
---

欢迎[和我交流](https://linktr.ee/xyymeeth)

参考
--

[

熊市新考验 -- XCarnival NFT 借贷协议漏洞分析
-------------------------------

慢雾安全团队建议在进行借款操作时应做好订单状态中是否已经提走抵押品的判断，避免再次出现此类问题。

https://mp.weixin.qq.com

![](https://storage.googleapis.com/papyrus_images/115ad2eeaccb91bd7497ce79ffd5ac450c8214e7fe611ef3076cfe38748ebb94.jpg)

](https://mp.weixin.qq.com/s/F2hpBNRzhZmfCn7BQanGOA)

[

门有锁，但钥匙在锁孔上 - 详解 XCarnival 攻击
-----------------------------

这个安全检查，一言难尽

https://mp.weixin.qq.com

![](https://storage.googleapis.com/papyrus_images/115ad2eeaccb91bd7497ce79ffd5ac450c8214e7fe611ef3076cfe38748ebb94.jpg)

](https://mp.weixin.qq.com/s/WEzNmTDGI2GUNrF7DJuZ6A)

---

*Originally published on [xyyme.eth](https://paragraph.com/@xyyme/xcarnival)*
