Double Entry Point
Exploit a legacy contract interaction pattern involving multiple contracts and a shared dependency (CryptoVault) to drain funds.
Vulnerable Code
Analyze the Solidity code below to find the vulnerability.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// Assume Forta is an external monitoring contract/service interface
interface IForta {
function setDetectionBot(address detectionBotAddress) external;
function notify(address user, bytes calldata msgData) external;
function raiseAlert(address user) external;
}
// Assume CryptoVault holds the underlying tokens
contract CryptoVault {
address public underlying; // The actual ERC20 token (e.g., DET)
constructor(address _underlying) { underlying = _underlying; }
// Simplified: Assume deposit/withdraw logic exists
function transfer(address to, uint amount) external {
IERC20(underlying).transfer(to, amount);
}
function sweepToken(address token) external {
// Allows transferring *any* token held by the vault
require(token != underlying, "Cannot sweep underlying");
IERC20(token).transfer(msg.sender, IERC20(token).balanceOf(address(this)));
}
}
// Legacy contract interacting with the vault
contract LegacyToken is IERC20 {
CryptoVault public vault;
// Standard ERC20 functions omitted for brevity...
// Interactions with vault happen internally
constructor(address _vault) { vault = CryptoVault(_vault); }
}
// The main vulnerable contract
contract DoubleEntryPoint is IERC20 { // Implements ERC20, likely wraps DET
address public player;
CryptoVault public vault;
address public delegatedToken; // Address of LegacyToken
IForta public forta;
constructor(address _vault, address _legacyToken, address _forta, address _player) {
vault = CryptoVault(_vault);
delegatedToken = _legacyToken;
forta = IForta(_forta);
player = _player;
}
// Delegate transfer logic (simplified)
function transfer(address recipient, uint256 amount) public override returns (bool) {
// Delegate actual transfer to CryptoVault
vault.transfer(recipient, amount);
return true;
}
// Other standard ERC20 functions (balanceOf, etc.) assumed to exist,
// likely querying the vault or internal state.
// Function to allow sweeping tokens *other than underlying*
function sweepVaultToken(address token) external {
// This seems redundant if CryptoVault.sweepToken exists? Maybe restricted access?
vault.sweepToken(token);
}
// Function potentially used by Forta bot
function delegateTransfer(address recipient, uint256 value, address origSender) public {
// This seems designed to be called by a trusted bot?
// Requires check that msg.sender is the registered Forta bot.
// Transfer underlying tokens.
require(msg.sender == address(forta), "Not Forta"); // Simplified check
vault.transfer(recipient, value);
}
}
// --- BOT Contract ---
// Deployed by player, registered with Forta
contract DetectionBot {
IForta forta;
address vaultAddress;
address doubleEntryPointAddress;
constructor(address _forta, address _vault, address _det) {
forta = IForta(_forta);
vaultAddress = _vault;
doubleEntryPointAddress = _det; // Address of DoubleEntryPoint contract
}
function handleTransaction(address user, bytes calldata msgData) external {
// Logic to detect malicious activity
// If delegateTransfer is called originating from DoubleEntryPoint contract, raise alert
(address target, ) = abi.decode(msgData[4:], (address, uint256)); // Decode target of internal call
bytes4 selector = bytes4(msgData[:4]);
// If DET token (DoubleEntryPoint) calls delegateTransfer... suspicious!
// The goal is to alert IF DoubleEntryPoint contract *originates* a call to its own 'delegateTransfer'
// This check is likely flawed. How can we trigger this?
if (selector == DoubleEntryPoint.delegateTransfer.selector && target == doubleEntryPointAddress) {
forta.raiseAlert(user); // Alert the user (player)
}
}
}
Submit Explanation
Explain the vulnerability and how to exploit it.
Hints (5)
Just a little peak
Hint 1
Hint 2
Hint 3
Hint 4
Hint 5
Explanation
Discomfort = Learning