function _safeMint(
address to,
uint256 tokenId,
bytes memory _data
) internal virtual {
_mint(to, tokenId);
require(
_checkOnERC721Received(address(0), to, tokenId, _data),
"ERC721: transfer to non ERC721Receiver implementer"
);
}
An early malicious user can send 1 wei of the vault token to the contract (as its wallet balance) then call deposit() as the first depositor of the Vault.
Beacuse deposit() -> depositors.mint() will mint a NFT to _msgSender() which can be an contract, this opens the oppotunity of reentrancy attck: the attacker can call deposit() again in onERC721Received().
As a result, the contract can malfunction or even freeze users' funds if the attack is planned sophisticatedly.
In the PoC bellow, we demoed an attack that essentially planted a bomb to the contract, which will be triggered once 1M are deposited and freeze users' funds for overflow due to the extremely large _totalShares.
PoC
Attacker send 1 wei of underlying token to the contract.
Attacker call deposit() with 340282366920938496 wei:
Handle
WatchPug
Vulnerability details
https://github.com/code-423n4/2022-01-sandclock/blob/a90ad3824955327597be00bb0bd183a9c228a4fb/sandclock/contracts/Vault.sol#L476-L481
https://github.com/code-423n4/2022-01-sandclock/blob/a90ad3824955327597be00bb0bd183a9c228a4fb/sandclock/node_modules/@openzeppelin/contracts/token/ERC721/ERC721.sol#L256-L266
An early malicious user can send
1 wei
of the vault token to the contract (as its wallet balance) then calldeposit()
as the first depositor of the Vault.Beacuse
deposit()
->depositors.mint()
will mint a NFT to_msgSender()
which can be an contract, this opens the oppotunity of reentrancy attck: the attacker can calldeposit()
again inonERC721Received()
.As a result, the contract can malfunction or even freeze users' funds if the attack is planned sophisticatedly.
In the PoC bellow, we demoed an attack that essentially planted a bomb to the contract, which will be triggered once 1M are deposited and freeze users' funds for overflow due to the extremely large
_totalShares
.PoC
1 wei
of underlying token to the contract.deposit()
with340282366920938496 wei
:In
onERC721Received()
, Attacker calldeposit()
with340282366920938496 wei
again:deposit()
with100,000 * 1e18
of underlying token:deposit()
with1,000,000 * 1e18
of underlying token:withdraw()
:The transaction will revert at
_computeShares()
due to overflow on_amount * _totalShares
.Recomandation
Consider using
ReentrancyGuard
from OpenZeppelin, addnonReentrant()
modifiers todeposit()
and other important entry functions.