code4rena-2023-10-ens-m01

[M-01] Some tokens enable the direct draining of all approved ERC20Votes tokens

보고서

Summary

transferFrom의 리턴값을 확인하지 않아 ERC20Votes 토큰 전송에 실패하더라도 정상 처리 된다. 이를 통해 무료로 ERC20MultiDelegate를 발행하고 예치된 ERC20Votes 토큰을 전부 빼낼 수 있다.

Keyword

theft, safetransfer, erc20, dao, vote, delegate vote

Vulnerability

ERC20는 transferFrom 이 실패하더라도 트랜잭션이 취소되지 않고, 단순히 false를 리턴하는 경우도 있다. 이를 처리하기 위해서 일반적으로 SafeERC20.safeTransferFrom 를 이용하지만, 이 코드베이스에서는 사용하지 않았다.

    function _reimburse(address source, uint256 amount) internal {
        // Transfer the remaining source amount or the full source amount
        // (if no remaining amount) to the delegator
        address proxyAddressFrom = retrieveProxyContractAddress(token, source);
@>      token.transferFrom(proxyAddressFrom, msg.sender, amount);
    }
 
    function createProxyDelegatorAndTransfer(
        address target,
        uint256 amount
    ) internal {
        address proxyAddress = deployProxyDelegatorIfNeeded(target);
@>      token.transferFrom(msg.sender, proxyAddress, amount);
    }
 
    function transferBetweenDelegators(
        address from,
        address to,
        uint256 amount
    ) internal {
        address proxyAddressFrom = retrieveProxyContractAddress(token, from);
        address proxyAddressTo = retrieveProxyContractAddress(token, to);
@>      token.transferFrom(proxyAddressFrom, proxyAddressTo, amount);
    }

transferFrom의 결과를 확인하지 않아 토큰을 가져오는 데 실패하더라도 토큰이 정상적으로 이동한 것으로 인식하고 작업을 진행한다.

    function _delegateMulti(
        uint256[] calldata sources,
        uint256[] calldata targets,
        uint256[] calldata amounts
    ) internal {
        ...
 
        for (
            uint transferIndex = 0;
            transferIndex < Math.max(sourcesLength, targetsLength);
            transferIndex++
        ) {
            ...
            if (transferIndex < Math.min(sourcesLength, targetsLength)) {
                // Process the delegation transfer between the current source and target delegate pair.
@>              _processDelegation(source, target, amount);
            } else if (transferIndex < sourcesLength) {
                // Handle any remaining source amounts after the transfer process.
@>              _reimburse(source, amount);
            } else if (transferIndex < targetsLength) {
                // Handle any remaining target amounts after the transfer process.
@>              createProxyDelegatorAndTransfer(target, amount);
            }
        }
 
        if (sourcesLength > 0) {
@>          _burnBatch(msg.sender, sources, amounts[:sourcesLength]);
        }
        if (targetsLength > 0) {
@>          _mintBatch(msg.sender, targets, amounts[:targetsLength], "");
        }
    }

이로 인해 다음과 같은 문제가 발생할 수 있다.

  1. ERC20Votes 토큰을 예치하지 않고 무료로 투표권 위임 토큰(ERC1155, ERC20MultiDelegate)을 발행받을 수 있다.
  2. ERC20MultiDelegate를 무료 발행받은 뒤 _reimburse를 트리거하여, 무료 ERC20MultiDelegate를 burn 하며 다른 유저가 투표권 위임을 위해 예치해 둔 ERC20Votes를 빼낼 수 있다.

Impact

ERC20Votes를 지불하지 않고도 표를 다수 위임하여 투표권을 조작할 수 있다. 다른 유저가 예치한 ERC20Votes를 탈취할 수 있다.

Mitigation

safeTransferFrom 을 사용한다.

Memo

봇 레이스에서 비표준 ERC20에 transferFrom을 사용할 시 안전하지 않다고 지적한 것은 무효화됐다. 참고

봇 레이스에서 예시로 든 USDT는 ERC20Votes가 아니기 때문에 의미가 없어보였지만, 대상 ERC20Votes를 제한하지 않으므로(프로토콜 사용 토큰을 ENSToken로 한정하지 않았음) 여전히 발생 가능한 이슈라고 설득했다.

자산 탈취의 문제가 있지만, trasferFrom에서 실패 시 revert 하지 않고 false를 리턴하는 ERC20Votes 토큰의 실제 예를 아무도 찾지 못하여 심각도가 미디움으로 내려갔다.


tags: bughunting, ens, smart contract, solidity, dao, erc20, safeTransfer, crypto theft, dao delegate, severity medium