재진입 공격은 외부 호출이 현재 함수의 실행을 방해하거나 변경할 수 있는 상황을 이용하는 것이다.
contract noReEntrancy{
uint public x = 10;
bool public locked;
constructor(){
owner = msg.sender;
}
modifier noReentrancy(){
require(!locked, "No reentrancy");
locked = true;
_;
locked = false;
}
function decrement(uint i) public noReentrancy{
x -= i;
if(i > 1){
decrement(i - 1);
}
}
}
modifier
키워드를 사용해 noReEntrancy
라는 modifier를 정의하였습니다. noReEntrancy
modifier는 locked
라는 상태 변수를 사용하여 재진입을 방지합니다.
require(!locked, "No ReEntracy");
함수가 호출될 때 locked
가 false인지 확인합니다. true
라면 함수는 즉시 종료되며 에러메세지가 반환됩니다.
locked = true;
함수가 재진입을 방지하기 위해 locked
를 true
로 설정합니다.
_;
modifier에서 이 특수기호는 원래 함수의 코드가 위치하는 곳을 나타냅니다. 즉, 이 위치에서 decrement
함수의 코드가 실행됩니다.
locked = false;
함수의 실행이 끝나면 locked
를 false
로 다시 설정하여 다른 함수 호출을 허용합니다.
decrement
함수는 noReEntrancy
modifier를 사용하여 재귀 호출을 안전하게 수행합니다. i
가 1보다 큰 경우 함수는 자기 자신을 다시 호출하여 i-1
을 인자로 넘깁니다. 이렇게 재귀로출이 일어나더라도, noReEntracy
modifier 덕분에 재진입 공격이 방지됩니다. 하지만 재귀호출은 안일어날겁니다(?)
예를 들어서 decrement(2)를 실행했다고 하겠습니다.
참고) bool 값을 처음 선언하면 그 값은 기본적으로 false로 설정됩니다. 왜냐하면 솔리디티에서 초기화되지 않은 상태변수에 대해서 기본 값을 설정하도록 설계되어있기 때문
입니다. uint도 마찬가지입니다. 초기화하지 않으면 0으로 설정됩니다. 기본값 설정은 개발자가 실수로 변수를 초기화하지 않았을 때, 예기치 않은 동작이 일어나는 것을 막기 위한 것입니다.
말이 샜네요. 다음을 살펴보겠습니다. decrement
메서드는 noReentrancy
라는 수정자가 들어가있습니다. 먼저 require에서 locked
가 false가 맞는지 검사합니다.
false라면 먼저 locked
를 true로 설정합니다.
그리고 실행할 함수의 로직이 들어갑니다.
로직이 끝나면 locked
를 false로 바꿉니다.
그럼 이걸 decrement(2)로 보면
10에서 먼저 2를 뺍니다. 그러면 x는 8이 되겠습니다.
그리고 2는 1보다 크니까 자기 자신을 재귀호출합니다. 그러나 이미 locked
가 true기 때문에 require문에 의해서 함수는 실행이 중지되고 "No ReEntrancy"
에러를 반환합니다.
정말 간단한 예제긴 하지만
http://wiki.hash.kr/index.php/%EB%A7%88%EC%9A%B4%ED%8A%B8%EA%B3%A1%EC%8A%A4
마운트곡스 해킹사건
에 사용된 해킹 방법이라고 합니다.
간단한 수정자 하나가 보안에 이런 영향을 미친다고 하니까 열심히 공부해야겠다는 생각이 듭니다..