[마스터링 이더리움] 스마트 컨트랙트 보안 - 패턴2 오버/언더플로우

in #kr5 years ago

9장 스마트 컨트랙트의 보안에 대한 매우 중요한 챕터입니다. 정말 마스터링 이더리움의 보안관련 내용은 최고입니다! 그런데 번역본은 실망입니다. 번역자들이 블록체인에 대한 지식이 없는 것처럼 느껴지는 순간이 매우 많습니다.

스마트 컨트랙트 보안에 대해 깊이 파고 싶으시면 꼭 원서를 보십시오. 번역서는 ....

스마트 컨트랙트의 코딩은 어렵지 않습니다. 하지만 이것은 단순히 구현할 때의 얘기입니다. 대충 구현한 컨트랙트는 분명히 보안 이슈가 발생할 것입니다. 9장 내용은 지금까지 발견된 스마트 컨트랙트의 주요 보안 이슈와 이를 예방하는 패턴에 대한 내용입니다.
매우 매우 중요한 챕터입니다. 그래서 패턴들을 하나 하나 살펴보겠습니다.

두번째 패턴은 범하기 수윈 데이터 타입의 오버/언더 플로우입니다.

패턴2: 산술 오버/언더 플로우

  • 변수의 타입에 따른 숫자 범위 오버/언더 플로우를 이용한 해킹
  • uint8 타입의 변수에 256을 지정하면 변수는 오버 플로우가 발생하여 0을 저장
  • 컨트랙트(솔리디티) 코드에서 변수의 범위를 체크하지 않으면 문제가 발생!

취약패턴

  • 데이터 타입을 벗어나는 숫자(또는 데이터)로 인해 오버플로우, 언더플로우 발생
  • uint8 타입의 변수의 값이 0일 때, 이것에서 -1을 하면 언더플로우가 발생하여 변수의 값은 255가 됨.
  • uint8 타입의 변수 값이 0일 때, 257을 더하게 되면, 오버플로우가 발생하여 변수 값은 1이 됨 (256 모듈라 계산, 257%256=1)
  • 부호 있는 타입의 변수 경우, 가장 큰 음수에 도달했을 때, 여기에 -1을 하면, 언더플로우가 발생하여 최대의 양수값을 가지게 됨
// TimeLcok 취약 코드 예제 - 오버플로우
1 contract TimeLock {
2
3   mapping(address => uint) public balances;
4   mapping(address => uint) public lockTime;
5
6   function deposit() public payable {
7     balances[msg.sender] += msg.value;
8     lockTime[msg.sender] = now + 1 weeks;
9   }
10
11  function increaseLockTime(uint _secondsToIncrease) public {
12    lockTime[msg.sender] += _secondsToIncrease;
13  }
14
15  function withdraw() public {
16    require(balances[msg.sender] > 0);
17    require(now > lockTime[msg.sender]);
18    balances[msg.sender] = 0;
19    msg.sender.transfer(balance);
20  }
21 }
  • 4행에서 lockTime 변수가 public으로 선언되어 있음 (외부 노출)
  • 11행의 increaseLockTime함수 호출시에 인자로 2^256 - userLockTime을 전달하면, lockTime을 0으로 만들 수 있음.
  • 2^256 - userLockTime + userLockTime = 2^256으로 오버플로우가 발생하여 0이됨.
  • 해커는 15행의 withdraw함수를 호출하고, lockTime 요구문을 통과하여 이더를 빼내감
 // 보안 취약 예제 - 언더플로우
1 pragma solidity ^0.4.18;
2
3 contract Token {
4
5   mapping(address => uint) balances;
6   uint public totalSupply;
7
8   function Token(uint _initialSupply) {
9     balances[msg.sender] = totalSupply = _initialSupply;
10  }
11
12  function transfer(address _to, uint _value) public returns (bool) {
13    require(balances[msg.sender] - _value >= 0);
14    balances[msg.sender] -= _value;
15    balances[_to] += _value;
16    return true;
17  }
18
19  function balanceOf(address _owner) public constant returns (uint balance) {
20    return balances[_owner];
21  }
22 }
  • 잔액이 0인 해커가 12행의 transfer함수를 호출. 이 때 인자인 _value값에 양수를 입력함
  • 13행에서 uint로 선언한 balance 변수값이 0이고 여기에 음수를 빼면, 언더플로우가 발생하여 양수가 됨
  • 해커는 13행을 통과하고, 14행도 마찬가지로 양수의 잔액을 갖게 됨

예방기법

  • 일반적 연산 라이브러리 대신에 수학 표준 연산 라이브러리를 사용하라. 더하기, 빼기, 곱하기 연산. 나누기는 오버/언더 플로우를 발생시키지 않는다. 또 EVM은 0으로 나누게 되면 원상태로 되돌린다.
  • 예를 들어, 오픈제플린의 SafeMath 라이브러리가 있다. Vyper는 이와 유사한 연산 라이브러리를 사용하여 변수의 범위를 체크해 준다.
  • SafeMath와 같은 연산 라이브러리를 사용하여 오버/언더 플로우가 발생하지 않도록 차단한다

Coin Marketplace

STEEM 0.30
TRX 0.12
JST 0.034
BTC 64136.70
ETH 3128.20
USDT 1.00
SBD 3.94