// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract VulnerableVault {
address public admin;
// Maps user addresses to their deposited balances
mapping(address => uint256) public userBalances;
constructor() {
// The deployer of the contract becomes the admin
admin = msg.sender;
}
/// @notice Allows users to deposit Ether into the vault.
/// A 1% fee is applied to the deposit and sent to a fee pool.
function deposit() public payable {
require(msg.value > 0, "Deposit must be greater than zero");
// Calculate 1% fee. This fee theoretically belongs to the protocol.
uint256 fee = msg.value / 100; // 1% of deposited amount
uint256 amountToDeposit = msg.value - fee;
// User's balance is updated with their net deposit.
userBalances[msg.sender] += amountToDeposit;
// The 'fee' part of msg.value remains in the contract's total balance.
// It is not explicitly tracked in a separate variable, which is a design flaw
// if only fees should be withdrawable by admin.
}
/// @notice Allows users to withdraw their deposited funds.
/// @param amount The amount to withdraw.
function withdraw(uint256 amount) public {
require(userBalances[msg.sender] >= amount, "Insufficient balance");
// Decrement user's balance and transfer funds.
userBalances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
/// @notice Admin can withdraw *any* amount from the contract's balance.
/// This is the rug pull vulnerability! This simulates a `withdrawStuckToken()`-like function.
/// @param amount The amount to withdraw from the contract.
function adminRugPullWithdraw(uint256 amount) public {
require(msg.sender == admin, "Admin control required"); // Only admin can call.
// NO checks to ensure 'amount' is only from accrued fees.
// The admin can withdraw any amount up to the contract's total Ether balance (address(this).balance),
// effectively stealing user deposits, as user deposits also contribute to address(this).balance.
// This function treats all funds in the contract as if they are available for the admin to take.
require(address(this).balance >= amount, "Insufficient contract balance for withdrawal");
payable(admin).transfer(amount); // Transfer chosen amount to admin.
}
}
В этом контракте VulnerableVault функция adminRugPullWithdraw позволяет администратору снимать любую сумму эфира с общего баланса контракта. Это критическая уязвимость. Если ключ администратора скомпрометирован или если лицо, контролирующее ключ, решает действовать злонамеренно (как это было поставлено под сомнение в деле Zunami в отношении «внутренней работы»), оно может использовать эту функцию для снятия всего эфира, депонированного в хранилище, включая средства пользователей.
Оператор require только проверяет, что вызывающий является администратором, полностью игнорируя проверку суммы на соответствие тому, что должно быть законно снято (например, только комиссии протокола, а не основная сумма пользователя).
И для того, чтобы снизить эту уязвимость и предотвратить мошенничество, рассмотрите следующие надежные стратегии:
- Ограничьте доступ администратора к определенным средствам: ограничьте любую функцию вывода администратора только средствами, принадлежащими протоколу (например, собранными комиссиями, средствами казны), и никогда не допускайте вывода депозитов пользователей. Депозиты пользователей должны быть доступны для вывода только самим пользователям.