Platform: Code4rena
Start Date: 21/12/2021
Pot Size: $30,000 USDC
Total HM: 20
Participants: 20
Period: 5 days
Judge: Jack the Pug
Total Solo HM: 13
Id: 70
League: ETH
Rank: 15/20
Findings: 1
Award: $141.51
🌟 Selected for report: 0
🚀 Solo Findings: 0
141.5133 USDC - $141.51
Critical
Attackers can steal funds from users' balances for those who approved the VaderPoolV2 contract.
nativeAsset.safeTransferFrom(from, address(this), nativeDeposit);
VaderPoolV2.mintSynth()
allows anyone to transfer funds from an arbitrary address (the from
parameter), and mint synth tokens to another arbitrary address (the to
parameter).
The current common practice usually ask users to approve an unlimited amount of allowance
to contracts. Normally, the allowance
can only be used by the user who initiated the transaction.
If that's the case, then the attacker will be able to steal all the wallet balances of the users who approved the VaderPoolV2
contract.
Even if the dApp only asks for the allowance of the amount they plan to mint with, the attacker can still frontrun the user's mintSynth()
transaction to initiate the attack.
For instance:
Alice approved 40,000 USDV to VaderPoolV2
contract;
The attacker called mintSynth()
with:
foreignAsset
= WETHnativeDeposit
= 40,000from
= Alice's addressto
= The attacker's addressAs a result, 40,000 USDV was transferred from Alice's balance, and the attacker received synth
tokens.
burnSynth()
to burn all the synth
tokens received.As a result, the attacker will receive ~40,000 USDV.
Tenderly, Kovan Testnet
The example in Proof of Concept has been tested on a forking network with Tenderly.
Remove from
parameter and always transfer funds from msg.sender
.
function mintSynth( IERC20 foreignAsset, uint256 nativeDeposit, address to ) external override nonReentrant supportedToken(foreignAsset) returns (uint256 amountSynth) { nativeAsset.safeTransferFrom(msg.sender, address(this), nativeDeposit); ISynth synth = synthFactory.synths(foreignAsset); if (synth == ISynth(_ZERO_ADDRESS)) synth = synthFactory.createSynth( IERC20Extended(address(foreignAsset)) ); (uint112 reserveNative, uint112 reserveForeign, ) = getReserves( foreignAsset ); // gas savings amountSynth = VaderMath.calculateSwap( nativeDeposit, reserveNative, reserveForeign ); // TODO: Clarify _update( foreignAsset, reserveNative + nativeDeposit, reserveForeign, reserveNative, reserveForeign ); synth.mint(to, amountSynth); }
The same issue exists in mintFungible()
and shall be fixed similarly:
#0 - jack-the-pug
2022-03-12T04:14:15Z
Dup of #147