Ethereum Hacking: MSG.VALUE reuse
There is a myriad of security vulnerability in solidity. You may already know many of them like reentrancy, denial of service, call to untrusted contract and so on…
Here we will talk about less common vulnerabilities in Ethereum. msg.value
reuse is one of them.
Here is a simple smart contract:
pragma solidity ^0.8.0
contract Test {function batchBuy(address[] memory addr) external payable{ mapping (uint => address) nft; for (uint i = 0; i < addr.length; i++) {
buy1NFT(addr[i])
} function buy1NFT(address to) internal { if (msg.value < 1 ether) { revert("not enough ether") }
nft[numero] = address;
}
}
The goal of this contract is to handle NFTs, I’ve not included others ERC721 functions as we don’t need them.
Can you spot the vulnerability ?
2. The issue with msg.value.
When you send some ether to a smart contract msg.value
should contain the number of Ethers sent in wei (1 wei = 10^-18
ETH)
The particularity with msg.value
is that his value remains fixed during the whole function call. (excluding external calls, and sending ether)
In the smart contract, the function buy1NFT
mints 1 NFT to the caller of this function.
The only condition to call this function is to send more than 1 ETH:
if (msg.value < 1 ether) { revert("") }
But now what will happen if we call the same function 2 times in the SAME transaction? What will happen?
As we ALREADY sent 1 ETH to the contract, every time we will reach this condition: if (msg.value < 1 ether) { revert("") }
The condition will be false, hence the EVM will continue the execution flow and NOT revert. Hence, we will be able to mint 2 times the NFT instead of 1 which isn’t the expected behavior.
Therefore, the goal for an attacker is to call 2 times the function in the same transaction.
3. Exploiting the contract
In the function batch-buy() we see that we can buy lot of NFTs at once
But the issue is that in every iteration of a for loop, Buy1NFT() is called. It verifies that msg.value is at least 1 ether, but don’t subtract 1 ether from the smart contract.
As a result i can send only 1 ETH to the smart contract and put x addresses in the “addr” argument (or x times my address) to get x NFTs for 1/x the price each.
4. How to mitigate this attack
- Avoid using msg.value in a loop. (as it increases the chances to call 2 times the vulnerable function or even more)
- msg.value persists when you’re using delegate-call, if you call delegatecall to the same smart contract it may result in an undefined behavior.
- Don’t forget that when you verify that msg.value > 1 ether doesn’t mean that you subtract 1 ether from the contract!
I hope you enjoyed this post, feel free to check my others articles if you’re interested on smart contract cybersecurity.