code4rena-2023-09-ondo-m01

[M-01] Chain support cannot be removed or cleared in bridge contracts

보고서

Summary

주소를 문자열로 변환하거나 문자열의 해시를 저장하고 있다. 빈문자열 또는 해시값을 0으로 만들어야 설정을 삭제할 수 있는데, 그럴 방법이 없어 삭제할 수 없다.

Keyword

bug, logic flaw, string, hash, bridge

Vulnerability

출발지 체인에서 메시지를 전달할 목적지 컨트랙트는 Ondo finance에서 제작한 DestinationBridge 컨트랙트로 고정되어야 한다. 따라서 관리자가 체인별 DestinationBridge 컨트랙트 주소를 지정하는 기능이 있다. Axelar 브릿지는 목적지 컨트랙트 주소를 문자열로 받으므로 이를 문자열로 변환해 저장한다. 다음은 SourceBridge 컨트랙트에 이를 설정하는 setDestinationChainContractAddress 함수이다.

121:   function setDestinationChainContractAddress(
122:     string memory destinationChain,
123:     address contractAddress
124:   ) external onlyOwner {
125:     destChainToContractAddr[destinationChain] = AddressToString.toString(
126:       contractAddress
127:     );
128:     emit DestinationChainContractAddressSet(destinationChain, contractAddress);
129:   }

다음은 출발지 체인에서 토큰 이동을 요청하는 burnAndCallAxelar 함수이다. 관리자가 셋팅하지 않은 컨트랙트는 destContract 값이 디폴트 값인 빈 문자열이 되고, 이를 통해 셋팅되지 않은 체인으로 브릿징을 시도할 수 없도록 막는다.

  function burnAndCallAxelar(
    uint256 amount,
    string calldata destinationChain
  ) external payable whenNotPaused {
    // check destinationChain is correct
    string memory destContract = destChainToContractAddr[destinationChain];
 
@>  if (bytes(destContract).length == 0) {
      revert DestinationNotSupported();
    }
 
    if (msg.value == 0) {
      revert GasFeeTooLow();
    }
 
    // burn amount
    TOKEN.burnFrom(msg.sender, amount);
 
    bytes memory payload = abi.encode(VERSION, msg.sender, amount, nonce++);
 
    _payGasAndCallContract(destinationChain, destContract, payload);
  }

그런데 주소 문자열을 설정하는 setDestinationChainContractAddress 함수에서는 빈문자열을 설정할 수 없다. 기존에 등록했던 목적지를 삭제하고 싶다면 빈 문자열을 저장해야 하는데, address(0)를 파라미터로 주더라도 이를 문자열로 변환한 0x0000000000000000000000000000000000000000 가 저장되므로 bytes(destContract).length == 0 조건에 걸리지 않는다. 즉, 한 번 추가한 체인을 삭제할 방법이 없다.

이는 목적지 체인 역시 마찬가지이다. 다음은 DestinationBridge에 메시지를 보낼 수 있는 출발지 SourceBridge 컨트랙트의 주소를 설정하는 addChainSupport 함수이다. 문자열을 비교는 해시값으로 하는 것이 더 빠르기에 주소 문자열의 해시값을 저장한다. 설정 삭제를 위해 address(0) 를 설정한다 하더라도 0x0000000000000000000000000000000000000000 문자열의 해시값이 저장된다.

234:   function addChainSupport(
235:     string calldata srcChain,
236:     string calldata srcContractAddress
237:   ) external onlyOwner {
238:     chainToApprovedSender[srcChain] = keccak256(abi.encode(srcContractAddress));
239:     emit ChainIdSupported(srcChain, srcContractAddress);
240:   }

DestinationBridge에서 메시지를 처리할 때 등록된 컨트랙트로부터 왔는지 확인한다. 이 역시 해시값이 0인지 확인하므로, 빈문자열의 srcContractAddress를 등록하더라도 삭제가 불가하다.

85:    function _execute(
        ...
96:     if (chainToApprovedSender[srcChain] == bytes32(0)) {
97:       revert ChainNotSupported();
98:     }

Impact

브릿징 허용 체인 설정을 삭제할 수 없다.

Mitigation

SourceBridge와 DestinationBridge에 각각 허용 컨트랙트를 삭제하는 기능을 추가한다.

function removeDestinationChainContractAddress(
  string memory destinationChain
) external onlyOwner {
  delete destChainToContractAddr[destinationChain];
  emit DestinationChainContractAddressRemoved(destinationChain);
}
function removeChainSupport(
  string calldata srcChain
) external onlyOwner {
  delete chainToApprovedSender[srcChain];
  emit ChainIdRemoved(srcChain);
}

tags: bughunting, ondo finance, smart contract, solidity, bridge, logic flaw, severity medium