Reentrancy attack 예제

iwin1203·2022년 11월 3일
1

블록체인

목록 보기
10/11
post-custom-banner

요약

  • 재진입 공격을 살펴보고 직접 시연해봤다.
  • 가장 유명한 공격 사례인 만큼 직접 돌려볼 가치가 있다고 생각했다.
  • 재진입 공격의 원리를 제대로 이해했다. payable과 external, public 등의 역할과 차이를 정확히 알게 되었다. web3js + truffleHDwallet 조합을 오랜만에 사용해봤다.

출처: https://cryptomarketpool.com/reentrancy-attack-in-a-solidity-smart-contract/


재진입 공격은 가장 유명한 스마트컨트랙트 공격 방법이다. 실례로는 무려 이더리움 하드포크까지 (이더리움 / 이더리움 클래식) 야기한 The DAO 공격이 있다.


시나리오

아래와 같은 EtherStore 컨트랙트가 있다. 얘는 일종의 은행이다.
유저가 deposit()으로 저금하면, 잔고가 balances라는 mapping에 저장되는 아주 간단한 구조이다. withdraw()로 한번에 인출도 가능하다.

간단하고 깔끔한 이 컨트랙트에 사람들은 이더를 저금한다.


똑똑하지만 나쁜 공격자는 아래와 같은 Attack 컨트랙트를 만든다.


공격자는 1이더와 함께 이 Attack.sol의 attack()을 호출한다.

  1. require문을 통과하고, etherStore.deposit{value: 1 ether}();문을 통해 위 EtherStore에 공격자의 이름으로 1이더가 저금된다.

  2. 다음으로 etherStore.withdraw()에 의해 위 EtherStore의 withdraw() 함수가 호출된다.
    바로 위에서 공격자의 이름으로 1이더를 저금했으니, 무난하게 require(bal > 0);을 통과한다.

  3. 곧바로 withdraw() 함수 내의 (bool sent, ) = msg.sender.call{value: bal}("");가 실행된다.

  • 만약 EOA가 withdraw()를 호출한 것이라면, 위 코드는 정상적으로 bal에 해당하는 금액을 EOA로 전송한다.
  • 문제는 공격자가 CA로 withdraw()를 호출했기에 위 코드는 1이더를 Attack의 폴백함수로 보낸다. 이때 폴백함수는 payable로 선언되어있기 때문에 EtherStore로부터 성공적으로 1이더를 받는다.
  1. 이제 폴백함수의 내용이 실행된다. EtherStore에는 공격자를 제외한 많은 사람들이 이더를 저금해놨고, 그렇기에 EtherStore의 balance는 당연히 1 이상이다. 따라서 폴백함수의 if (address(etherStore).balance >= 1 ether) 조건은 충족되고, etherStore.withdraw()가 다시 호출된다.

  2. 공격자는 분명 위에서 1이더를 인출했다. 그러나 EtherStore의 withdraw()의 balances[msg.sender] = 0;는 아직 실행되지 않았다. 해당 코드가 실행되기 전 Attack의 폴백함수로 이동했기 때문. 결국 EtherStore는 공격자가 여전히 1이더를 가지고 있다고 판단한다.

  • 3번 과정에 의해 Attack에 1이더를 보내고 폴백함수를 호출한다. 폴백함수가 호출되면 다시 4번 과정에 의해 withdraw()가 호출된다.
  • 결국 3->4->3->4->... 반복되며 EtherStore의 잔고가 1이더 밑으로 떨어질때까지 돈이 Attack 컨트랙트로 전송된다.
  1. 잔고가 1이더 밑으로 떨어졌다. 그럼 4번 과정에서의 조건문을 충족하지 못해 withdraw()의 호출이 더이상 발생하지 않는다. 그때서야 withdraw()에 밀려있던 다음 과정, balances[msg.sender]=0;이 반복해서 우수수 실행된다. 그러나 이미 늦었지..

  2. 결국 EtherStore의 이더는 전부 Attack 컨트랙트로 이동된다.



구현해보자

ganache & truffle 세팅

  • 저번 포스팅에서 똑같이 했으므로 생략

위 컨트랙트들 작성 후 migrate

  • EtherStore를 먼저 배포하고 해당 주소를 Attack 컨트랙트의 생성자 인자로 넣어준다.

EtherStore에 자금 세팅 (사람들이 저금해놓은 상황 재연)

  • deprecated되었지만,, 편리한 truffle hdwallet 사용
  • deposit만 약식으로 abi 작성.
  • 총 세 계정으로 각각 5이더씩 저금
  • EtherStore의 balance 총 15이더임을 확인

Attack 컨트랙트 호출

  • Attack 컨트랙트에 balance가 0임을 확인
  • 위 소스코드에서 abi, 컨트랙트 주소, 계정pk 변경
  • value는 2이더로 attack 함수 호출. 공격!!!
  • Attack 컨트랙트에 공격자가 넣은 2이더 뿐 아니라 EtherStore의 15이더까지, 총 17이더가 들어왔음을 확인. 이제 EtherStore의 잔고는 0일 것.
  • 아까 찍어놓은 이벤트도 확인해보자.
  • fallback을 통해 계속 withdraw로 재진입하며 돈을 야무지게 빼내고 있다. log index 35에서 모든 돈이 drain되었고, "시나리오"의 6번 과정이 시작되어 fallbackSuccess가 처음 찍혔다.
  • 이제 Attack의 balance를 공격자가 가져오면 끝!


레퍼런스

post-custom-banner

0개의 댓글