sherlock-2024-11-VVV审计报告
一
Summary
If the admin records the investment using the investment token instead of the stablecoin, it will lead to an error.
Root Cause
Vulnerable code:
2024-11-vvv-exchange-update-HeYuan-33/vvv-platform-smart-contracts/contracts/vc/VVVVCInvestmentLedger.sol
Lines 268 to 277 in c1e47db
for (uint256 i = 0; i < _kycAddresses.length; i++) { |
From the above, we can see that there is no validation for the investment amount, which can lead to issues such as the admin passing a non-stablecoin investment. This could result in errors, or if a zero investment amount is recorded, it could cause subsequent operations to fail or behave incorrectly.
Internal pre-conditions
When the admin calls the addInvestmentRecords function and passes the uint256[] calldata _amountsToInvest, it is an investment amount array that has not been validated.
External pre-conditions
This leads to the admin recording incorrect investments for the investor. As a result, when the investor later claims project tokens, they may receive an incorrect amount of tokens.
Attack Path
No response
Impact
If the admin makes an incorrect investment record, and it is irreversible, it will lead to an irreversible error. Additionally, if a zero investment amount is recorded, it will also affect subsequent calculations.
PoC
No response
Mitigation
I recommend adding a validation check for the uint256[] calldata _amountsToInvest when recording investment amounts,When passing parameters, add a token address to check whether it is a stablecoin.as shown below:
function addInvestmentRecords( |
I believe this is the best way to ensure the safety of investment records and prevent any potential errors from occurring.
二
Summary
Due to the lack of a minimum investment amount requirement, attackers can make a large number of malicious investments with a zero investment amount, consuming the investment round’s time and preventing other users from making legitimate investments.
Root Cause
Vulnerable Code
2024-11-vvv-exchange-update-HeYuan-33/vvv-platform-smart-contracts/contracts/vc/VVVVCInvestmentLedger.sol
Lines 143 to 156 in c1e47db
if (investmentIsPaused) revert InvestmentPaused(); |
here is no check for the minimum investment amount here; The check only verifies if the basic requirements are met.
Internal pre-conditions
The investment meets some basic requirements, as follows:
if (investmentIsPaused) revert InvestmentPaused();
// check if signature is valid
if (!_isSignatureValid(_params)) {
revert InvalidSignature();
}
// check if the investment round is active
if (
block.timestamp < _params.investmentRoundStartTimestamp ||
block.timestamp > _params.investmentRoundEndTimestamp
) {
revert InactiveInvestmentRound();
}
This allows attackers to send a large number of zero-amount investments, causing other users’ investment requests to experience prolonged delays,Miss the investment round
External pre-conditions
No response
Attack Path
The attacker first calls the invest function, sending a large number of zero-amount investment requests. The contract can only slowly process the attacker’s requests, consuming the time allocated for the investment round.
Impact
Possible consequences:
DOS (Denial of Service) attack
High gas consumption
Excessive array iteration: Due to the length limitation of the investor address array, this can cause the administrator to take an excessive amount of time to add investment records.
function addInvestmentRecords(
address[] calldata _kycAddresses,
uint256[] calldata _investmentRounds,
uint256[] calldata _amountsToInvest
) external onlyAuthorized
//...
for (uint256 i = 0; i < _kycAddresses.length; i++) {
address kycAddress = _kycAddresses[i];
uint256 investmentRound = _investmentRounds[i];
uint256 amountToInvest = _amountsToInvest[i];
//...
}
PoC
No response
Mitigation
You can take the following measures:
Set a minimum investment amount
Modify the investment address array to have a fixed length
Add a check to ensure the investment amount is not zero, as follows:
if (investmentIsPaused) revert InvestmentPaused();
// check if signature is valid
if (!_isSignatureValid(_params)) {
revert InvalidSignature();
}
// check if the investment round is active
if (
block.timestamp < _params.investmentRoundStartTimestamp ||
block.timestamp > _params.investmentRoundEndTimestamp
) {
revert InactiveInvestmentRound();
}
//check if the _params.amountToInvest isn't 0
if(
_params.amountToInvest == 0
){
revert()
}
三
Summary
During the process where the administrator adds multiple investment records to the contract, a condition was missed, which allowed bypassing the check for matching investment addresses and amounts, leading to the creation of incorrect investment records.
Root Cause
The vulnerability code is as follows:
2024-11-vvv-exchange-update-HeYuan-33/vvv-platform-smart-contracts/contracts/vc/VVVVCInvestmentLedger.sol
Lines 261 to 266 in c1e47db
if ( |
We can see that the if condition only checks if the lengths of the investment address array and the investment round array are equal, and compares the length of the investment amount array with the investment round array. It overlooks the comparison between the lengths of the investment address array and the investment amount array. This is a careless oversight.
Internal pre-conditions
No response
External pre-conditions
No response
Attack Path
No response
Impact
Due to the uncertainty about whether the lengths of the investment address array and the investment amount array are equal, this can lead to the administrator recording incorrect investment information, causing mismatched investment amounts and rounds for users. If an attacker exploits this vulnerability, they could enter an investment round as a low-investment participant and manipulate the number of investment addresses, resulting in a mismatch between the length of the investment amount array and the address array. This could cause the administrator to record a higher investment amount, leading to irreversible losses.
PoC
No response
Mitigation
Add a validation check to ensure that the lengths of the investment address array and the investment amount array are equal, as shown below:
if ( |
虽然都被判为无效了。但是正确的有俩个漏洞时一是msg.sender的调用,二是抢跑攻击,只对nouce的检查,会让攻击者预先知道监听。让我们来看一下正确的报告:
Token Claim Hijacking Due to Missing Validation
Summary
The VVVVCTokenDistributor contract is vulnerable to a frontrunning attack due to missing validation of the msg.sender during the claim process. An attacker can exploit this by observing pending valid transactions and preemptively executing them with a higher gas price, thus claiming tokens intended for other users.
Root Cause
Lack of msg.sender Validation:
The claim function directly transfers tokens to msg.sender without checking if this address is the same as the kycAddress. This oversight allows any arbitrary address to execute the claim given access to a valid signature and calldata.
function claim(ClaimParams memory _params) public {
if (claimIsPaused) {
revert ClaimIsPaused();
}
if (_params.projectTokenProxyWallets.length != _params.tokenAmountsToClaim.length) {
revert ArrayLengthMismatch();
}
if (_params.nonce <= nonces[_params.kycAddress]) {
revert InvalidNonce();
}
if (!_isSignatureValid(_params)) {
revert InvalidSignature();
}
// update nonce
nonces[_params.kycAddress] = _params.nonce;
// define token to transfer
IERC20 projectToken = IERC20(_params.projectTokenAddress);
// transfer tokens from each wallet to the caller
for (uint256 i = 0; i < _params.projectTokenProxyWallets.length; i++) {
projectToken.safeTransferFrom(
_params.projectTokenProxyWallets[i],
msg.sender,
_params.tokenAmountsToClaim[i]
);
}
Internal pre-conditions
none
External pre-conditions
none
Attack Path
An attacker can exploit this vulnerability through the following steps:
Monitoring Transactions:
The attacker continuously monitors the Ethereum network for pending transactions targeting the VVVVCTokenDistributor contract, specifically those invoking the claim function.
Identifying Valid Claims:
The attacker identifies a pending transaction with valid ClaimParams and a correctly generated signature, submitted by a legitimate user intending to claim their tokens.
Replicating Transaction Data:
The attacker copies the calldata from the pending transaction. This calldata contains all necessary details, including the signature proving the claim’s validity.
Executing Frontrunning Attack:
The attacker creates a new transaction using the copied calldata, setting themselves as the msg.sender.
They submit this new transaction with a higher gas price, incentivizing miners to prioritize it over the original pending transaction.
Claiming Tokens:
Once mined, the attacker’s transaction executes before the original one, allowing them to receive the tokens intended for the legitimate claimant.
Impact
The ability to front-run valid claims effectively nullifies the security guarantees provided by the cryptographic signature process, allowing attackers to exploit the system for illicit gain.
PoC
Setup:
A legitimate user intends to execute a claim for tokens using the claim function, constructing valid ClaimParams with a correct signature.
Transaction Broadcast:
The user broadcasts their transaction on the Ethereum network, intending to receive tokens from specified proxy wallets into their own address.
Attacker Monitoring:
An attacker monitors pending transactions on the network, focusing on those interacting with the VVVVCTokenDistributor contract.
Data Replication:
Upon identifying the legitimate transaction, the attacker copies the calldata, including the ClaimParams and signature, retaining all necessary details.
Frontrunning Execution:
The attacker submits their own transaction using the duplicated calldata but specifies themselves as msg.sender.
The attacker sets a higher gas price to prioritize their transaction over the original.
Successful Claim Theft:
The attacker’s transaction gets mined before the original, claiming the tokens intended for the legitimate user.
The original user’s transaction fails due to the incremented nonce, rendering their claim invalid.
Mitigation
Modify the claim function to include a check ensuring that msg.sender matches the kycAddress specified in the ClaimParams. This alignment verifies that the account executing the claim is the same account authorized to receive the tokens.
function claim(ClaimParams memory _params) public { |