Instance
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "openzeppelin-contracts-08/access/Ownable.sol";
import "openzeppelin-contracts-08/token/ERC20/ERC20.sol";
interface DelegateERC20 {
function delegateTransfer(address to, uint256 value, address origSender) external returns (bool);
}
interface IDetectionBot {
function handleTransaction(address user, bytes calldata msgData) external;
}
interface IForta {
function setDetectionBot(address detectionBotAddress) external;
function notify(address user, bytes calldata msgData) external;
function raiseAlert(address user) external;
}
contract Forta is IForta {
mapping(address => IDetectionBot) public usersDetectionBots;
mapping(address => uint256) public botRaisedAlerts;
function setDetectionBot(address detectionBotAddress) external override {
usersDetectionBots[msg.sender] = IDetectionBot(detectionBotAddress);
}
function notify(address user, bytes calldata msgData) external override {
if (address(usersDetectionBots[user]) == address(0)) return;
try usersDetectionBots[user].handleTransaction(user, msgData) {
return;
} catch {}
}
function raiseAlert(address user) external override {
if (address(usersDetectionBots[user]) != msg.sender) return;
botRaisedAlerts[msg.sender] += 1;
}
}
contract CryptoVault {
address public sweptTokensRecipient;
IERC20 public underlying;
constructor(address recipient) {
sweptTokensRecipient = recipient;
}
function setUnderlying(address latestToken) public {
require(address(underlying) == address(0), "Already set");
underlying = IERC20(latestToken);
}
/*
...
*/
function sweepToken(IERC20 token) public {
require(token != underlying, "Can't transfer underlying token");
token.transfer(sweptTokensRecipient, token.balanceOf(address(this)));
}
}
contract LegacyToken is ERC20("LegacyToken", "LGT"), Ownable {
DelegateERC20 public delegate;
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
function delegateToNewContract(DelegateERC20 newContract) public onlyOwner {
delegate = newContract;
}
function transfer(address to, uint256 value) public override returns (bool) {
if (address(delegate) == address(0)) {
return super.transfer(to, value);
} else {
return delegate.delegateTransfer(to, value, msg.sender);
}
}
}
contract DoubleEntryPoint is ERC20("DoubleEntryPointToken", "DET"), DelegateERC20, Ownable {
address public cryptoVault;
address public player;
address public delegatedFrom;
Forta public forta;
constructor(address legacyToken, address vaultAddress, address fortaAddress, address playerAddress) {
delegatedFrom = legacyToken;
forta = Forta(fortaAddress);
player = playerAddress;
cryptoVault = vaultAddress;
_mint(cryptoVault, 100 ether);
}
modifier onlyDelegateFrom() {
require(msg.sender == delegatedFrom, "Not legacy contract");
_;
}
modifier fortaNotify() {
address detectionBot = address(forta.usersDetectionBots(player));
// Cache old number of bot alerts
uint256 previousValue = forta.botRaisedAlerts(detectionBot);
// Notify Forta
forta.notify(player, msg.data);
// Continue execution
_;
// Check if alarms have been raised
if (forta.botRaisedAlerts(detectionBot) > previousValue) revert("Alert has been triggered, reverting");
}
function delegateTransfer(address to, uint256 value, address origSender)
public
override
onlyDelegateFrom
fortaNotify
returns (bool)
{
_transfer(origSender, to, value);
return true;
}
}
Solution
나는 위험을 감지하는 감지 봇을 만들어 등록해야 한다. 그리고 위험 발생을 막아야 성공한다.
보면 취약점은 LegacyToken이 DoubleEntryPoint를 delegateTransfer()를 호출할 때 발생하는 것을 볼 수 있다.
CryptoValut 컨트랙트에서 underlying Token은 D.E.P인데, 이는 따라서 Sweep할 수 없어야 하지만 legacyToken의 transfer()가 D.E.P의 취약한 함수를 호출하기에 해당 토큰을 다 터는게 가능한 문제인 것이다.
따라서 그 delegateTransfer()을 호출하면 위험 Alert를 내도록 감지봇을 설계해야 한다.
나는 msg.data의 첫 4바이트가 호출하는 함수의 시그니처임을 이용해서 풀었다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IForta {
function setDetectionBot(address detectionBotAddress) external;
function notify(address user, bytes calldata msgData) external;
function raiseAlert(address user) external;
}
contract DetectionBot{
function handleTransaction(address user, bytes calldata msgData) public{
address player=0x709c3046593A0b9ee7DFe43F16Fb0155D3D25Ace;
if (user==player){
if (bytes4(msgData[:4])==0x9cd1a121){// signature of delegateTransfer
IForta forta=IForta(0x7d921A956e866C438dbf55C6DD8D0c2F072e2653);
forta.raiseAlert(player);
}
}
}
//success
}
'Web3 > Ethernaut' 카테고리의 다른 글
[Ethernaut] Stake 풀이 (0) | 2025.01.18 |
---|---|
[Ethernaut] Gatekeeper Three 풀이 (0) | 2025.01.18 |
[Ethernaut] Re-entrancy 풀이 (0) | 2025.01.18 |
[Ethernaut] Naught Coin 풀이 (0) | 2025.01.18 |
[Ethernaut] Higher Order 풀이 (0) | 2025.01.18 |
댓글