2. Axelar Contest
• Contest 요약
• 컨테스트: https://code4rena.com/contests/2022-04-axelar-network-contest
• 보고서: https://code4rena.com/reports/2022-04-axelar
• 코드: https://github.com/code-423n4/2022-04-axelar
• 2022.04.07~2022.04.12 Code4rena
• 포상 총액 $50,000
• High 1개, Medium 4개
3. Axelar
• Axelar 란?
• 브릿지 서비스
• 코스모스 및 EVM 체인 간 토큰 전송을 할 수 있으며 체인 간에 임의의 메시지를 보낼 수 있다.
• 기본적인 브릿지 플로우
• 출발지 체인의 게이트웨이 컨트랙트에 컨트랙트콜을 하여 이동 요청
• 토큰을 이동하는 경우 이동할 토큰을 게이트웨이 컨트랙트에 전달하거나 burn
출처: https://docs.axelar.dev/dev/general-message-passing/overview
4. Axelar
• Axelar 란?
• Axelar 네트워크는 Cosmos SDK를 이용해 구현한 PoS를 이용하는 블록체인
• Axelar 네트워크를 이루는 탈중앙된 Validator가 출발지 체인에서 생성한 이벤트를 확인하고 서명
• 이 서명으로 유효함을 인증하며 목적지 체인의 게이트웨이 컨트랙트에 명령을 요청하거나 메시지를 전달
• Validator는 Axelar 노드 운영자가 AXL 토큰을 스테이킹하고 외부 EVM 호환 체인과 연결하면 될 수 있음
• 검증된 브릿징 요청이 목적지 체인에서 실행되기 위해서는 Executor 서비스를 이용해야 함
• 출발지 체인의 Gas Receiver 컨트랙트에게 가스 비용을 선불하면 Validator 검증을 마친 후 자동으로 목적지 체인의 게이트웨이에 전달해주는 서비스
• https://docs.axelar.dev/dev/general-message-passing/gas-services/intro
• Executor 서비스를 이용하지 않는다면 유저가 직접 컨트랙트콜을 해야함
• Axelarscan 익스플로러에서 찾아서 지갑을 연결하고, 목적지 체인의 게이트웨이에 직접 컨트랙트콜 하면 된다.
• 여타 작업은 완료되었지만 최종 작업인 목적지 체인으로의 트랜잭션이 실패했을 시 유저가 직접 컨트랙트콜을 재시도 하여 복구할 수 있다.
출처: https://axelarscan.io/gmp/0xdad17967df1600120b76eb6e6b1ea4dc177f4b9c4afc78887334ccafeb21bf2f:2
5. Axelar
• Axelar 란?
• validator 로직을 간단히 살펴보기
• ProcessGatewayTxConfirmation 함수
• https://github.com/axelarnetwork/axelar-
core/blob/v0.17.3/cmd/axelard/cmd/vald/evm/evm.go#L318
-L398
• 출발지 체인의 이벤트를 잡아 파싱하고,
TokenSentSig/ContractCallSig/ContractCallWithTokenSig
를 나누어 처리한다.
• 이들은 유저가 브릿징을 요청하기 위해 출발지 체인의 게이
트웨이에 호출하는 함수와 매칭되는 이벤트이다.
• 마지막에 Broadcast 함수를 호출해 Axelar 체인의 다른 노
드들에게 전달한다.
func (mgr Mgr) ProcessGatewayTxConfirmation(e tmEvents.Event) error {
chain, gatewayAddress, txID, confHeight, pollKey, err := parseGatewayTxConfirmationParams(mgr.cdc, e.Attributes)
if err != nil {
return sdkerrors.Wrap(err, "EVM gateway transaction confirmation failed")
}
rpc, found := mgr.rpcs[strings.ToLower(chain)]
if !found {
return sdkerrors.Wrap(err, fmt.Sprintf("Unable to find an RPC for chain '%s'", chain))
}
var events []evmTypes.Event
_ = mgr.validate(rpc, txID, confHeight, func(_ *geth.Transaction, txReceipt *geth.Receipt) bool {
for i, log := range txReceipt.Logs {
if !bytes.Equal(gatewayAddress.Bytes(), log.Address.Bytes()) {
continue
}
switch log.Topics[0] {
case ContractCallSig:
…
case ContractCallWithTokenSig:
…
case TokenSentSig:
…
default:
}
}
return true
})
v, err := packEvents(events)
if err != nil {
return err
}
msg := voteTypes.NewVoteRequest(mgr.cliCtx.FromAddress, pollKey, v)
mgr.logger.Info(fmt.Sprintf("broadcasting vote %v for poll %s", events, pollKey.String()))
_, err = mgr.broadcaster.Broadcast(context.TODO(), msg)
return err
}
6. Axelar
• 오딧팅 범위 로직 분석
• AxelarGateway
• 브릿지로 넘기고자 하는 토큰과 메시지를 수신, 발신하는 컨트랙트
• Axelar 네트워크의 Validator에서 출발지 체인에서 발생한 이벤트를 감지하고, 검증하여 서명함
• 서명을 얻으면 목적지 체인의 AxelarGateway 컨트랙트 함수를 호출하여 토큰을 이동하거나 메시지를 전달한다.
• AxelarGateway는 기본적인 기능을 구현한 abstract 컨트랙트이다.
• 이를 상속하고 기능을 추가해 구현한 단일 서명(Singlesig)와 다중 서명(Multisig) 컨트랙트가 있다.
7. Axelar
• 오딧팅 범위 로직 분석
• AxelarGateway
• 출발지 체인에서 작업 생성
• 브릿지를 이용하고 싶은 유저가 출발지 체인에 배포된 AxelarGateway 컨트랙트의 함수를 호출하여 이벤트를 발생하는 것에서 브릿지 플로우가 시작
• sendToken, callContract, callContractWithToken 세 개의 엔트리포인트가 있다. 유저가 용도에 따라 함수를 선택해 호출하면 된다.
• sendToken: 추가적인 메시지 없이 단순히 ERC20을 이동
• callContract: ERC20 이동 없이 메시지만 전송
• callContractWithToken: ERC20을 이동하며 메시지도 전송
8. Axelar
• 오딧팅 범위 로직 분석
• AxelarGateway
• 출발지 체인에서 작업 생성
• 토큰을 이동하는 sendToken와 callContractWithToken는 _burnTokenFrom 함수를 호출
• 옮기는 토큰이 Axelar에서 자체 배포한 wrapped 토큰이라면 타 체인으로 이동
할 때 burn 하고, 목적지 체인에서 새로 발행 또는 이동된다.
• burn/mint 권한이 없는 외부 토큰이라면 transferFrom 으로 끌어온다.
• Axelar wrapped 토큰에는 InternalBurnable와 InternalBurnableFrom 타입이 있다.
• InternalBurnable 타입
• 1.0.0 버전 wrapped 토큰으로
• 그 예로는 Avalanche의 UST(native to Terra) 토큰이 있다.
• 이 토큰을 burn할 때는 salt 값을 이용해 DepositHandler 컨트랙트가
배포될 주소를 계산하고, 그 주소로 토큰을 보내야 한다. 이후 토큰의
burn 함수를 호출하면 salt값을 이용해 DepositHandler 컨트랙트 주소
를 계산, 해당 주소의 토큰을 burn 한다.
• InternalBurnableFrom 타입
• 최신 wrapped 토큰
• 그 예로는 Avalanche의 axlATOM, axlUSDC 등이 있다
• burnFrom 함수를 호출해 바로 burn 한다.
9. Axelar
• 오딧팅 범위 로직 분석
• AxelarGateway
• 목적지 체인에서 작업 처리
• Validator에서 확인을 마치고, 그 서명을 이용하여 목적지 체인에서 작업을 처리한다. 크게 2가지 종류로 나눌 수 있다.
• 단순히 토큰을 이동
• 메시지 전송
• 단순히 토큰을 보내는 경우(유저가 sendToken을 호출) _mintToken 함수를 호출해 Axelar wrapped 토큰으로 발행해주거나, 외부 토큰이라면 게이트웨이
에 예치된 토큰을 보내준다.
10. Axelar
• 오딧팅 범위 로직 분석
• AxelarGateway
• 목적지 체인에서 작업 처리
• _mintToken
• 메시지 없이 단순히 토큰을 이동하는 경우 호출
• 이동한 토큰이 Axelar wrapped 토큰으로 새로 발행되어야 하는 경우 토큰을 발행하고, 외부 토큰인 경우 게이트웨이에 예치된 토큰을 보내준다.
11. Axelar
• 오딧팅 범위 로직 분석
• AxelarGateway
• 목적지 체인에서 작업 처리
• 메시지를 전송은 Approve 단계와 Execute 단계로 나뉜다.
• Approve 단계는 마치 편지를 우편함에 넣는 것과 유사하다.
• Execute 단계는 우편함에서 자신의 편지를 꺼내고 반응하는 것에 비유할 수 있다.
• 메시지 전달은 다시 두 종류로 나뉜다.
• 단순 메시지 전달 (유저가 callContract를 호출)
• 토큰과 메시지 전달 (유저가 callContractWithToken를 호출)
12. Axelar
• 오딧팅 범위 로직 분석
• AxelarGateway
• 목적지 체인에서 작업 처리 - Approve
• 다음은 Approve 단계에 해당하는 함수들이다.
• 목적지 체인 게이트웨이에 출발지로부터 생성된 메시지를 등록한다.
• 등록된 이 메시지는 보낼 당시 메시지를 수신자로 지정한 컨트랙트만 가져다 사용할 수 있다.
• 이들은 internal 함수로, AxelarGateway 컨트랙트를 상속해 구현하는 AxelarGatewaySinglesig, AxelarGatewayMultisig
에서 external 함수에서 호출된다.
• _approveContractCall: 단순 메시지 전달 (유저가 callContract를 호출)
• _approveContractCallWithMint: 토큰과 메시지 전달 (유저가 callContractWithToken를 호출)
13. Axelar
• 오딧팅 범위 로직 분석
• AxelarGateway
• 목적지 체인에서 작업 처리 - Execute
• Execute 단계에서는 게이트웨이의 함수를 호출하는 게 아니라, 메시지를 소비할 컨트랙트의 함수를 호출해야 한다.
• 메시지를 수신할 컨트랙트는 IAxelarExecutable 인터페이스를 준수하여 개발해야 한다. (그래야 Relayer 서비스를 이용할 때 인터페이스가 맞음)
• 다음은 abstract 로 정의된 IAxelarExecutable 컨트랙트이다.
• 게이트웨이에 등록된(Approve) 메시지를 가져와 처리한다. 마치 우편함을 열어 자신의 편지를 가져오고 처리하는 것과 같다.
• IAxelarExecutable를 상속한 구현 컨트랙트는 메시지를 받아 처리할 로직을 _execute 또는 _executeWithToken 함수에 구현해야 한다.
• 또한, 출발지 체인에서 메시지를 생성할 당시 메시지를 수신할 목적지 컨트랙트 주소를 이 구현 컨트랙트로 지정해야 한다.
14. Axelar
• 오딧팅 범위 로직 분석
• AxelarGateway
• 목적지 체인에서 작업 처리 – Execute
• 메시지가 게이트웨이에 등록되었는지(Approve 되었는지) 확인하기
위해 게이트웨이의 validateContractCall와
validateContractCallAndMint를 호출한다.
• execute 또는 executeWithToken 함수 콜은 유저가 직접 Approve
단계가 끝난 것을 확인하고 호출하든가, 가스를 미리 납부하여
Executor 서비스(Relayer 서비스 또는 Gas 서비스)를 이용해 호출해
야 한다.
• Executor 서비스를 이용한다면 Axelar의 Relayer가
ContractCallApproved 또는
ContractCallApprovedWithMint 이벤트를 감지하고
execute 또는 executeWithToken 함수를 호출해준다.
• 서비스를 이용하지 않는다면 유저가 직접 이벤트를 감지하
고 반응하도록 프로그램을 짜든가 직접 호출해야 한다.
15. Axelar
• 오딧팅 범위 로직 분석
• AxelarGateway
• 목적지 체인에서 작업 처리 - Execute
• validateContractCall
• 유저가 callContract를 호출하여 메시지만 전달한 경우
• validateContractCallAndMint
• 유저가 callContractWithToken를 호출하여 토큰과 메시지를 함께 전달한 경우.
• 이 시점에 브릿징된 토큰을 IAxelarExecutable 구현 컨트랙트에게 전달한다.
16. Axelar
• 오딧팅 범위 로직 분석
• AxelarGateway
• 목적지 체인에서 작업 처리 - Execute
• 다음은 예시로 주어진 컨트랙트이다. 브릿지를 통해 받은 토큰과 메시지를 이용하여
정해진 작업을 수행한다. 플로우는 다음과 같다.
• 먼저 출발지 체인의 게이트웨이에서 callContractWithToken를 호출한다.
• Validator에서 처리를 완료하고, Apporve 단계에 도착지 체인 게이트웨이의
approveContractCallWithMint 함수를 호출하여 출발지로부터 전달된 메시
지를 등록한다.
• 이후 Relayer에 의해 상속한 IAxelarGateway 컨트랙트의 executeWithToken
가 실행된다.
• validateContractCallAndMint가 호출되어 메시지가 게이트웨이에
등록되어 있는지 확인한다. 등록되어 있다면
DestinationSwapExecutable 컨트랙트에게 Axelar wrapped 토큰을
발행한다 (브릿지를 통해 토큰을 옮겨온 토큰)
• DestinationSwapExecutable에서 override하여 정의한
_executeWithToken 함수가 호출된다. 원하는 작업을 진행한다. 예시
에서는 브릿지로 넘겨받은 토큰을 swap하였다.
17. Axelar
• 오딧팅 범위 로직 분석
• AxelarGatewaySinglesig
• AxelarGateway를 상속하고 구체화한 컨트랙트. 메시지를 전달할 때 단일 서명(Singlesig)을 이용한다.
• 목적지 체인의 게이트웨이로 동작하기 위한 작업은 모두 execute 함수를 통해 호출된다.
• _execute 함수에서 먼저 서명을 확인한 후 요청 작업을 확인, 해당하는 함수를 호출한다.
• Operator로 등록된 유저의 서명을 받은 경우 mintToken, approveContractCall, approveContractCallWithMint, burnToken 함수를 호출할 수 있다.
• Owner 서명을 받은 경우 Operator가 가능한 작업과 더불어 관리자 함수를 호출할 수 있다.
• 잘못된 서명 또는 자격이 없는 서명을 이용한다면 작업을 수행할 수 없다.
18. Axelar
• 오딧팅 범위 로직 분석
• AxelarGatewaySinglesig
• Operator의 서명을 받아 다음과 같은 작업을 처리한다.
• SELECTOR_MINT_TOKEN
• 출발지 체인에서 호출한 토큰 전송 요청 sendToken에 대한 반응.
• Axelar wrapped 토큰을 발행하거나 게이트웨이에 에치된 외부 토큰을
보내줌. mintToken 함수 호출.
• SELECTOR_APPROVE_CONTRACT_CALL
• 출발지 체인에서 호출한 메시지 전달 요청 callContract에 대한
Approve 단계 실행.
• approveContractCall 함수 호출.
• SELECTOR_APPROVE_CONTRACT_CALL_WITH_MINT
• 출발지 체인에서 호출한 메시지 전달 요청 callContractWithToken에
대한 Apporve 단계 실행.
• approveContractCallWithMint 함수 호출
19. Axelar
• 오딧팅 범위 로직 분석
• AxelarGatewaySinglesig
• 다음은 단순 토큰 이동 요청(sendToken)를 처리하는 함수이다.
• onlySelf modifier를 붙여 execute 함수를 통해서만 호출되도록 제한한다.
• 내용은 단순히 AxelarGateway의 internal 함수를 호출하는 것이 전부이다.
• 토큰 전송 또는 mint
20. Axelar
• 오딧팅 범위 로직 분석
• AxelarGatewaySinglesig
• 다음은 메시지 전송의 Approve 단계를 처리하는 함수들이다
• onlySelf modifier를 붙여 execute 함수를 통해서만 호출되도록 제한한다.
• 내용은 단순히 AxelarGateway의 internal 함수를 호출하는 것이 전부이다.
21. Axelar
• 오딧팅 범위 로직 분석
• AxelarGatewayMultisig
• AxelarGateway를 상속하고 구체화했다.
• 메시지를 전달할 때 다중 서명(Multisig)을
이용한다.
• 기본적인 흐름은 AxelarGatewaySinglesig
와 동일하다.
• execute 실행시 여러개의 서명을 받아 확인
한다는 점이 다르다.
22. Audit report 분석
• [H-01] Cross-chain smart contract calls can revert but source chain tokens remain
burnt and are not refunded
• Summary
• Axelar wrapped 토큰을 다른 체인으로 이동할 때, 출발지 체인의 토큰을 burn하고 도착지 체인에서 새로 발행 또는 전송한다.
• 이 때, 도착지 체인 측의 작업이 잘못되어 트랜잭션이 취소되어도 출발지 체인 측에서 burn한 토큰을 돌려받을 방법이 없다.
• Keyword
• bridge, cross chain, business logic vul
• Vulnerability
• callContractWithToken 함수를 호출하면 ERC20 토큰을 타 체인으로 이동하며 메시지를 전달할 수 있다.
• 출발지 체인 측의 토큰이 Axelar에서 배포한 ERC20 컨트랙트가 아닌 외부 토큰이라면 transferFrom을 이용해 Gateway 컨트랙트로 토큰을 전송
• Axelar에서 제작한 wrapped 토큰이라면 출발지 체인측 토큰을 burn 하고 도착지 체인측에서 새로 발행 또는 전송한다(도착지 체인 토큰이 Axelar
wrapped 토큰인지 여부에 따라 다름)
• callContractWithToken로 보낸 토큰과 메시지는 이를 보내고자 한 목적지 체인 상의 목적지 컨트랙트(IAxelarExecutable를 상속해 구현)
의 executeWithToken 함수를 호출해 수령한다.
• 목적지 체인에서 executeWithToken를 호출하는 트랜잭션은 여러가지 이유로 실패할 수 있다.
• 하지만 목적지 체인에서 실패하여도 출발지 체인에서 이미 burn 또는 transfer한 토큰을 복구할 방법은 없다.
• 따라서 도착지 체인에서 토큰을 받지 못해 유저가 자산을 잃을 수 있다.
23. Audit report 분석
• [H-01] Cross-chain smart contract calls can revert but source chain tokens remain
burnt and are not refunded
• Impact
• 유저가 목적지에서 토큰을 받지 못하여 자산을 잃는다.
• Mitigation
• 목적지 체인에서 컨트랙트 콜이 실패할 경우 출발지 체인의 토큰을 다시 돌려준다.
• Memo
• 주최측은 목적지 체인의 트랜잭션이 revert 되었더라도 다시 실행하면 된다고 하였다. 이 컨테스트를 열 때는 아직 공식적인 방법을 제
공하지 않았던 것 같다. 현재는 Axelar의 익스플로러에서 유저의 지갑을 연결에 목적지 체인에서 트랜잭션을 재시도할 수 있는 방법을
제공한다.
• 주최측이 이렇게 설명했지만, 심판은 그래도 문제 자체가 존재하며, 이 시점에는 공식적인 방법이 없었으므로 High로 인정했다.
24. Audit report 분석
• [M-01] Low level call returns true if the address doesn’t exist
• Summary
• 로우레벨 call은 해당 주소에 컨트랙트가 배포되지 않았다면 revert 되지 않고, true를 리턴한다.
• 따라서 로우레벨 call을 하기 전 해당 주소에 컨트랙트가 배포되었는지 확인해야 한다.
• Keyword
• low level call
• Vulnerability
• solidity docs를 참고하면 로우레벨 call, delegatecall, staticcall은 해당 주소에 컨트랙트가 배포되지 않았다면 true를 리턴한다 명시한
다.
• https://docs.soliditylang.org/en/develop/control-structures.html#error-handling-assert-require-revert-and-exceptions
25. Audit report 분석
• [M-01] Low level call returns true if the address doesn’t exist
• Vulnerability
• AxelarGateway의 _callERC20Token 함수에서 컨트랙트 배포 여부를 확인하지 않는다.
• 또한 AxelarGatewayProxy의 constructor 에서도 확인하지 않는다.
26. Audit report 분석
• [M-01] Low level call returns true if the address doesn’t exist
• Impact
• 로우레벨 call이 실패했음에도 revert 되지 않는다.
• Mitigation
• 로우레벨 call을 하기 전에 해당 주소에 컨트랙트가 배포되었는지 확인한다.
27. Audit report 분석
• [M-02] User’s funds can get lost when transferring to other chain
• Summary
• 도착지 체인 측의 게이트웨이에 토큰이 부족하면 도착지 체인에서 토큰을 줄 수 없고 트랜잭션이 revert 된다.
• 출발지 체인에서 유저는 이미 토큰을 burn 또는 transfer 했기 때문에 목적지에서 받지 못하면 자산을 잃게 된다.
• Keyword
• bridge, cross chain, business logic vul
• Vulnerability
• 도착지 체인에서 토큰을 줄 때, Axelar wrapped 토큰이 아닌 외부 토큰을 주어야 한다면 게이트웨이에 예치된 토큰을 transfer 한다.
• 이 때, 게이트웨이에 토큰이 필요한 만큼 예치되어 있지 않다면 도착지 체인 측 트랜잭션이 revert 될 것이다.
• 따라서 출발지 체인에서 유저의 토큰은 burn 또는 transfer 되었지만 도착지에서는 토큰을 받지 못해 유저가 자산을 잃을 수 있다.
28. Audit report 분석
• [M-02] User’s funds can get lost when transferring to other chain
• Vulnerability
29. Audit report 분석
• [M-02] User’s funds can get lost when transferring to other chain
• Impact
• 도착지에서는 토큰을 받지 못해 유저가 자산을 잃을 수 있다.
• Mitigation
• 목적지 체인에서 토큰을 전송하는데 실패했다면 출발지 체인의 토큰을 반환한다.
30. Audit report 분석
• [M-03] _execute can potentially reorder a batch of commands while executing,
breaking any assumptions on command orders
• Summary
• 재진입을 통해 배치로 처리되는 작업의 실행 순서를 조작할 수 있다.
• Keyword
• reordering, reentrancy, replay attack, signature
• Vulnerability
• AxelarGatewayMultisig.execute 함수는 여러가지 커맨드를 배치로 실행한다.
• 각 커맨드는 고유한 commandId가 붙어있다. _execute 함수는 기존에 실행했던 commandId를 재시도하는 것을 허용한다 (revert 하지
않는다).
• 공격자는 배치 실행되는 커맨드의 실행 순서를 재배열할 수 있다.
• 재진입으로 execute 함수를 호출하면 원래 나중에 호출될 커맨드를 먼저 실행할 수 있다.
• 이를 통해 컨텍스트에 대한 가정(순서대로 실행될 것이란 가정)을 깨뜨릴 수 있다.
31. Audit report 분석
• [M-03] _execute can potentially reorder a batch of commands while executing,
breaking any assumptions on command orders
• Impact
• 재진입을 통해 커맨드의 실행 순서를 조작할 수 있다.
• Mitigation
• execute 함수를 재진입 불가하게 한다. 또한 서명을 재전송할 수 없도록 nonce를 추가하라 했다.
• Memo
• High로 제출했지만 Medium으로 조정되었다.
• 주최자 측은 Axelar와 게이트웨이가 명령 실행 순서를 구체적으로 보장하지는 않으므로 순서를 섞는 취약점(reordering)이 논쟁의 여지
가 있다 했다. 이를 악용하는 시나리오가 없기 때문에 Low 또는 non-risk 라고 주장했다.
• 심판은 위험도가 높은 문제는 아니지만, 재진입과 서명 재전송이 가능하기 때문에 이를 완화할 필요가 있다고 하며 Medium으로 인정
했다.
32. Audit report 분석
• [M-04] Unsupported fee-on-transfer tokens
• Summary
• fee-on-transfer 토큰을 고려하지 않았다. 출발지 체인에 실제로 예치된 토큰보다 많은 양의 토큰을 목적지 체인에서 받을 수 있다.
• Keyword
• fee-on-transfer token, erc20
• Vulnerability
• 출발지 체인에서 토큰을 이동할 시, 외부 토큰을 이용한다면 유저의 토큰을 transferFrom으로 게이트웨이에 예치한다.
• fee-on-transfer 토큰은 transfer 할 때 수수료를 뗀다. 따라서 실제로 게이트웨이에 예치되는 토큰은 요청한 것보다 적어진다.
33. Audit report 분석
• [M-04] Unsupported fee-on-transfer tokens
• Impact
• 실제로 예치된 토큰보다 많은 토큰을 목적지 체인에서 받을 수 있다.
• Mitigation
• 목적지 체인에서 토큰을 보내줄 때, 수수료를 제한 만큼(실제로 예치된 만큼) 보낸다.