Root + Impact
Description
Depositing assets to the vault for another person will give shares to the sender of the transaction instead of the address specified as the receiver. This allows an attacker to freely mint shares (minus the fees) by deposit for another address, and then cancel his participation which allows him to redeem their assets while keeping his shares. It creates a vault shares attack.
function deposit(uint256 assets, address receiver) public override returns (uint256) {
...
uint256 stakeAsset = assets - fee;
stakedAsset[receiver] = stakeAsset;
uint256 participantShares = _convertToShares(stakeAsset);
IERC20(asset()).safeTransferFrom(msg.sender, participationFeeAddress, fee);
IERC20(asset()).safeTransferFrom(msg.sender, address(this), stakeAsset);
_mint(msg.sender, participantShares);
emit deposited (receiver, stakeAsset);
return participantShares;
}
function cancelParticipation () public {
...
uint256 refundAmount = stakedAsset[msg.sender];
stakedAsset[msg.sender] = 0;
uint256 shares = balanceOf(msg.sender);
_burn(msg.sender, shares);
IERC20(asset()).safeTransfer(msg.sender, refundAmount);
}
Risk
Likelihood:
Impact:
Proof of Concept
function testDepositAndRedeemFreely() public {
vm.startPrank(user1);
mockToken.approve(address(briVault), 1 ether);
briVault.deposit(1 ether, user1);
vm.stopPrank();
vm.startPrank(user2);
mockToken.approve(address(briVault), 1 ether);
briVault.deposit(1 ether, user3);
vm.stopPrank();
vm.assertTrue(briVault.stakedAsset(user3) > 0 ether);
vm.assertEq(briVault.balanceOf(user3), 0);
vm.assertTrue(briVault.balanceOf(user2) > 0);
vm.prank(user3);
briVault.cancelParticipation();
vm.assertEq(briVault.stakedAsset(user3), 0);
vm.assertTrue(briVault.balanceOf(user2) > 0);
}
Recommended Mitigation
function deposit(uint256 assets, address receiver) public override returns (uint256) {
...
- _mint(msg.sender, participantShares);
+ _mint(receiver, participantShares);
emit deposited (receiver, stakeAsset);
return participantShares;
}