Solidity 재진입 공격 - Solidity jaejin-ib gong-gyeog

(A)콘트랙트에서 (B)콘트랙트로의 어떠한 상호작용 및 이더의 전송은 (B)로 제어권을 넘겨주게 된다. 이러한 사실은 B가 상호작용이 끝나기 전에 다시 A를 호출할 수 있는 상황을 초래한다. 예를 들어, 다음 코드는 버그를 포함하고 있다(완전한 코드는 아닌 요약본).

pragma solidity ^0.4.0; // 이 콘트랙트는 버그를 포함하고 있으니 절대 사용하지 말 것!!! contract Fund { /// 콘트랙트의 이더 지분 정보 매핑 mapping(address => uint) shares; /// 지분 인출 function withdraw() { if (msg.sender.send(shares[msg.sender]) shares[msg.sender] = 0; } }

이 코드는 (send를 쓰기 때문에; send함수는 gas소비량이 정해져 있다)이더 전송에 가스 소비를 제한하고 있기 때문에 문제가 심각하지는 않지만 여전히 약점에 노출되어 있다. 이더의 전송은 항상 코드를 실행시키기 때문에 수신자는 withdraw함수를 다시 실행하여 콘트랙트의 모든 이더를 훔쳐갈 수 있다.

재진입공격을 막기 위해서 아래 방식으로 작성된 Check-Effects-Interaction패턴을 사용해야 한다.

pragma solidity ^0.4.0; contract Fund { /// 콘트랙트의 이더 지분을 매핑 mapping(address => uint) shares; /// 지분을 인출 function withdraw() { var share = shares[msg.sender]; shares[msg.sender] = 0; if (!msg.sender.send(share)) throw; } }

주목할 점은 재진입이 이더 전송간 이뤄지는 것이 아니라 다른 콘트랙트의 함수를 호출할 때 이뤄진다는 것이다.(Note that re-entrancy is not only an effect of Ether transfer but of any function call on another contract.) 더해서 여러 개의 콘트랙트가 연관된 상황도 고려해야 한다. 호출된 콘트랙트는 다른 콘트랙트의 상태를 변화시킬 수 있기 때문이다.

results matching ""

    No results matching ""

    Reentrancy Attack을 공부하면서 의문이 생겼다.

    fallback function은 ether가 Contract에 송금되면 정말 무조건 실행되는 것인지..

    실제로 코드를 짜서 재진입 상황을 재현해보려고 했는데.

    gas 비용이 부족하다고 재진입이 안되는 것이었다....안심스러우면서도 대략난감한 상황....

    Reentracy Attack이란?

    재귀적인 방법을 사용해서 Contract에서 보유한 모든 이더를 출금하는 방법이다.

    해커가 계약을 작성해서 다른 계약으로부터 송금받는 코드를 작성한다.

    그리고 해커가 정의한 fallback function에서 다시 해커가 작성한 계약에 이더를 송금하는 function(앞서 호출한 함수와 동일한 함수)을 호출하는 방법으로  퍼브릭 블록체인의 경우 Contract의 코드가 모두 공개되어 있으므로 코드의 허점을 사용해서 돈을 다 털어비리는 방법이다.

    필자가 부족하여 설명이 어려우므로 아래 글을 보면 이해하기 쉬울 것 같다..

    Reentrancy Attack 관련 글

    pragmasolidity ^0.5.0;

    contractA {

    B b;

    address wallet;

    eventEventA(stringindexed name);

    constructor (B _b) publicpayable{

    b =B(_b);

    }

    function () externalpayable {

    sendToB();

    emitEventA('excuted A fallback function');

    }

    functionsendToB( ) publicreturns (uint) {

    b.get();

    }

    functiongetBalance() publicviewreturns (uint256) {

    returnaddress(this).balance;

    }

    }

    contract B {

    eventEventB(stringindexed name);

    constructor () publicpayable{

    }

    functionget() public {

    msg.sender.transfer(1ether);

    //msg.sender.call.value(1 ether)("")

    }

    functiongetBalance() publicviewreturns (uint256) {

    return address(this).balance;

    }

    function () externalpayable {

    emitEventB('excuted B fallback function');

    }

    }

    Contract B의 get() 메소드를 보면 msg.sender.transfer()를 사용하고 있다.

    아래의 순서대로 실습을 해보자

    1. 두 컨트랙트를 RemixIDE를 이용해서 배포한다.

    2. B에 fallback function을 이용해 적정량의 ether를 송금해준다.

    3. A에 fallback function을 이용해 적정량의 ether를 송금해준다.

    그러면 아래와 같은 결과를 받을 수 있다.

    transact to A.(fallback) errored: VM error: revert. revert The transaction has been reverted to the initial state. Note: The constructor should be payable if you send value. Debug the transaction to get more information.

    트랜잭션에서 좀 더 정확한 상태를 조회하니 fail을 리턴했다.

    결과적으로 설명하자면 실행할 gas가 부족해서 fail을 리턴한 것이다.

    address.send()나 transfer()는 사용할 수 있는 gas의 양이 제한되어 있다. (2300gas)

    contract B에서 transfer를 실행했을 때 상대의 fallback function에서 기본적인 log을 출력할 수 있는 2300gas만 사용할 수 있고

    그 이상의 gas가 소모되는 작업은 fail을 return한다. (자세한 사항은 글 최하단 글 상자 참고)

    fallback function에서 제한된 gas 이상을 소모하는 작업을 실행하려면 address.call.value()()를 사용하여야한다.

    사실인지 확인해보기 위해 contract B의 get() 메소드에서 msg.sender.transfer(1 ether)를 msg.sender.call.value()()로 바꿔서 실행해보자.

    성공적으로 실행된다. 

    재귀적으로 함수가 호출 되었는지 로그도 확인해보자.

    메소드를 한번 실행했는데. EventA가 여러번 발생했다. 통장잔고도 확인해보자.

    contract B의 잔고가 텅텅비었다.(의도한 텅장...)

    참고하면 좋은 글들이다.

    1. 솔리디티 공식 문서

    In the worst case, the fallback function can only rely on 2300 gas being available (for example when send or transfer is used), leaving little room to perform other operations except basic logging. The following operations will consume more gas than the 2300 gas stipend:

        • Writing to storage
        • Creating a contract
        • Calling an external function which consumes a large amount of gas
        • Sending Ether

    Toplist

    최신 우편물

    태그