개발하기좋은날

재진입 공격 방지 (Re-Entrancy) 본문

BlockChain

재진입 공격 방지 (Re-Entrancy)

devbi 2022. 10. 28. 18:17
반응형

External call에 이어서 관련된 재진입 공격에 대해 알아봅시다.

 

이더리움의 DAO 해킹 사건이후 가장 요구되는 기본 보안 사항이다 

보통 재진입 공격같은 경우 External Call의 address.call에 의해 발생한다

DAO 사건또한 이러한 문제 떄문에 발생한 이슈입니다 

 

1. 단일 함수에 대한 재진입 입니다 

// INSECURE
mapping (address => uint) private userBalances;

function withdrawBalance() public {
    uint amountToWithdraw = userBalances[msg.sender];
    (bool success, ) = msg.sender.call.value(amountToWithdraw)(""); // At this point, the caller's code is executed, and can call withdrawBalance again
    require(success);
    userBalances[msg.sender] = 0;
}

위 코드 블록은 6번째줄과 8번째 줄에 문제가있습니다 

Checks effect Interaction 패턴에 위반되는 코딩 스타일입니다 

 

첫번째 check하지 않았고 

두번째 effect를 수행하지 않고 Interaction 을 수행 그리고 External call에대한 예외 처리만 하였습니다 

위와 같은 방식의 스타일은 사용자의 잔액은 함수가 끝날떄까지 0으로 설정되지 않으므로 두번째 호출부터 계속 성공하여 추가적인 잔액을 인출 할수있습니다

 

mapping (address => uint) private userBalances;

function withdrawBalance() public {
    uint amountToWithdraw = userBalances[msg.sender];
    userBalances[msg.sender] = 0;
    (bool success, ) = msg.sender.call.value(amountToWithdraw)(""); // The user's balance is already 0, so future invocations won't withdraw anything
    require(success);
}

 위 방식은 내부 작업이 모두 완료된후에 외부 함수를 호출하는 방식으로 수정된 방식이다 

가장 중요한건 순서 바뀌어도 방지할수있다는것이다 

 

추가적으로 하나더 패턴을 추가하자면 

mapping (address => uint) private userBalances;

function withdrawBalance() public {
	require(userBalances[msg.sender] > 0,"Checks pattern") // 4번 라인
    uint amountToWithdraw = userBalances[msg.sender];
    userBalances[msg.sender] = 0;
    (bool success, ) = msg.sender.call.value(amountToWithdraw)(""); // The user's balance is already 0, so future invocations won't withdraw anything
    require(success);
}

기존 Checks effect Interaction 에서 Checks를 넣는것이다 

Effects를 적용하기 앞서 호출자의 올바른 요청을 하고있는지를 확인하는것이다

또 다른 방법으로는 아래와 같은 방법을 사용 할 수 있다. 

 

bool private reentrancyLock = false;
modifier nonReentrancy() {
    require(!reentrancyLock);
    reentrancyLock = true;
    _;
    reentrancyLock = false;
}

위와 같은 방식은 Mutex 솔루션이라고 한다, Checks에 속한다 

해당 modifier를 바인딩한 함수는 External Call로 인한 반복적인 재귀호출의 진입을 막을수가있다 

 

Mutex 솔루션은 단일 트랜잭션 공격에대해 강력하게 대응할수있지만 단점이 존재합니다 

협업에 있어 해당 솔루션은 개발하는데 까다로운 조건이 될것입니다 

아래는 안전하지않은 Mutext 솔루션 입니다 

// INSECURE
contract StateHolder {
    uint private n;
    address private lockHolder;

    function getLock() {
        require(lockHolder == address(0));
        lockHolder = msg.sender;
    }

    function releaseLock() {
        require(msg.sender == lockHolder);
        lockHolder = address(0);
    }

    function set(uint newState) {
        require(msg.sender == lockHolder);
        n = newState;
    }
}

위와 같은 형태는 현재의 컨트랙트 사용자가 lockHolder로 사용되고 컨트랙트를 점유하고있음을 보여줍니다 

하지만 공격자 입장에서는 해당 계약을 완전히 잠글수있는 구조를 가지고있기에 보안의 취약함이 드러납니다 

getLock을 통해서 lockHolder를 점유한 상태에서 releaseLock()을 실행하지 않을것입니다 

그렇게 되면 프로그램은 교착상태에 빠져 계약을 사용할수없게 됩니다 

 

 

지금까지 DAO 사건이후의 재진입 공격에대한 솔루션과 대책을 알아 봤습니다 

기본적은 CEI 패턴을 적용을 하였을경우 단일 트랜잭션 공격에대한 재진입 공격을 막을수있습니다 

 

이후 포스팅에서는 다른 형태의 공격 유형으로 같은 블록에서 트랜잭션 순서 자체가 쉽게 조작을 방지하기위한

솔루션을 알아보겠다 

 

반응형

'BlockChain' 카테고리의 다른 글

스마트 컨트랙트 시큐리티 패턴  (0) 2022.11.02
Fallback, Receive 그리고 재진입 공격  (0) 2022.10.30
External Call 보안  (0) 2022.10.28
채굴 (Mining)  (0) 2022.10.17
프루닝  (0) 2022.08.31
Comments