Reentrancy Attack

Natreeum's Blog·2026년 1월 30일

Blockchain

목록 보기
5/6

Reentrancy Attack(재진입 공격)이란?

컨트랙트의 상태가 업데이트 되기 전, 함수를 다시 호출(재진입)하여 공격하는 기법

다음과 같은 기능을 갖고있는 컨트랙트는 reentrancy attack 에 취약합니다.

  • 외부 컨트랙트의 함수를 호출
  • 외부 컨트랙트로 native token 을 전송
  • 자산 전송 이후에 상태업데이트

다음은 Reentrancy 에 취약한 컨트랙트와 이를 공격하는 컨트랙트 코드 예시입니다.

contract vulnerableContract{
	mapping(address => uint256) public balances;
	
	function withdraw() public{
		require(balances[msg.sender] > 0);
		
		(bool success, ) = msg.sender.call{value: balances[msg.sender]}("");
		require(success);
		
		balances[msg.sender] = 0;
	}
}
contract maliciousContract{
	IVulnerableContract public vCa = "0xsomeAddress..";
	
	function exploit() external {
		vCa.withdraw();
	}
	
	fallback() external payable {
		if(address(vCa).balance >= vCa.balances(address(this))){
			vCa.withdraw();
		}
	}
}

위의 두 컨트랙트는 다음과 같은 흐름으로 exploit이 됩니다.

대응법

1. 자산 전송 전 상태업데이트 먼저 하기

contract safeContract{
	mapping(address => uint256) public balances;
	
	function withdraw() public{
		require(balances[msg.sender] > 0);
		
		uint256 transferAmount = balnaces[msg.sender];
		balances[msg.sender] = 0;
		
		(bool success, ) = msg.sender.call{value: transferAmount}("");
		require(success);
	}
}

상태(msg.sender 의 balance) 업데이트 → 자산 전송 의 순서로 코드가 실행되면 재진입을 하더라도 잔액을 확인하는 부분에서 오류가 발생하기 때문에 재진입 공격에 실패하게 됩니다.

2. Mutex 사용

contract safeContract{
	mapping(address => uint256) public balances;
	bool mutex = false;
	
	function withdraw() public{
		require(!mutex, "Mutex is locked");
		require(balances[msg.sender] > 0, "Insufficient balance");
		
		mutex = true;
		
		(bool success, ) = msg.sender.call{value: balances[msg.sender]}("");
		require(success, "tx is failed");
		
		balances[msg.sender] = 0;
		
		mutex = false;
	}
}

재진입을 하더라도 mutex가 잠겨있는 상태이기 때문에 tx가 실패하게 됩니다.

3. Openzeppelin ReentrancyGuard 사용

import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract safeContract {
	mapping(address => uint256) public balances;
	
	function withdraw() public nonReentrant{
		require(balances[msg.sender] > 0, "Insufficient balance");
		
		(bool success, ) = msg.sender.call{value: balances[msg.sender]}("");
		require(success, "tx is failed");
		
		balances[msg.sender] = 0;
	}
}

mutex와 동일한 원리입니다. openzeppelin 에서 표준화한 코드를 사용합니다.

profile
BlockChain DEV

0개의 댓글