BlockChain

Solidity 취약점 #3

부산대보금자리 2022. 1. 13. 17:47

1. Over/Under Flow

변수의 데이터 타입 범위를 벗어나는 숫자를 고정 크기 변수에 저장해야하는 연산이 수행되면 오버플로/언더플로가 발생한다.

이를 통해 악용하여 예기치 않은 논리 흐름을 생성할 있다.

 

  • 언더플로(underflow) : uint8(부호 없는 8비트 정수) 변수의 값이 0일때, 1을 빼면 결과는 255(8비트에서 가장 큰 수). 나타낼 수 있는 범위 아래에 숫자를 할당할 경우
  • 오버플로(overflow) : uint8 변수는 나타낼 수 있는 가장 큰 값이 255인데, 이 변수의 초기값이 0일 때 256을 더하면 0이 되고 257을 더하면 1이 된다. 나타낼 수 있는 범위 이상의 숫자를 할당할 경우

 

 

 contract Timelock{

     mapping(address => uint) public balances;

     mapping(address => uint) public lockTime;

     function deposit() public payable{

         balance[msg.sender] += msg.value;

         lockTime[msg.sender] = now + 1 weeks;

     }

    function increaseLockTime(uint _secondsToIncrease) public{

        lockTime[msg.sender] += _secondsToIncrease;

    }

    function withdraw() public {

        require(balance[msg.sender]>0);

        require(now > lockTime[msg.sender]);

        balance[msg.sender] = 0;

        msg.sender.transfer(balace);

    }

 }

 

위의 코드는 deposit 일주일 동안은 출금할 없게 설계되어 있다.

increaseLockTime함수를 이용해서 보관 기간을 연장할 있지만 최소 일주일은 컨트랙트에 이더를 잠궈놓을 있다.

여기서 공격자가 오버플로를 이용한다면 Locktime 무관하게 이더를 출금할 있게 된다.

 

  • 문제 요인 해결

Uint, uint256 경우 범위가 편이라 공격이 발생하지 않을것이라 생각할수 있지만 공격이 가능하며 산술연산의 경우 safemath라이브러리를 활용하여 방지한다.

 

 

 

2. Using tx.origin

글로벌 변수 tx.origin 트랜잭션의 발신자를 나타낸다.

Msg.sender 다른 점은 전체 호출을 가로지르고 원래 호출을 보낸 계정의 주소를 나타낸다. , call chain에서의 첫번째 account이다.

이는 호출을 보낸 이에 따라 달라지는 값이므로 msg.sender와 혼동한다면 공격당할 가능성이 있다.

 

Ex>

Victim

 

contract Phishable{

    address public owner;

    constructor(address _owner){

        owner = _owner;

    }

    function () public payable{}

    function withdrawAll(address _recipient) public {

        require(tx.origin == owner);

        _recipient.transfer(this.balance);

    }

}

 

Withdraw함수에서 tx.origin과 기존의 owner 확인함으로서 함수가 실행된다.

이떄의 공격대상은 해당 컨트랙트이자 owner 의미한다.

 

 

contract AttackContract{

    Phishable phishableContract;

    address attacker;

    constructor(Phishable _phishableContract, address _attackerAddress){

        phishableContract = _phishableContract;

        attacker = _attackerAddress;

    }

    fallback() payable{

        phishableContract.withdrawAll(attacker);

    }

 

}

 

컨트랙트로 공격 시도가 가능한데 owner에 컨트랙트를 실행하게끔 만든다.

Fallback function은 불려진 함수가 없을 때도 작동하므로 tx.origin owner 상태로 위의 fallback function이 실행되게 된다.

 

  • 문제 요인 해결

컨트랙트 로직 상에서 tx.origin 의미와 msg.sender를 혼동한 것이 가장 요인이 있다. 또한 tx.origin이 타의적인 트랜잭션에 의해 의도치 않게 들어올수 있다는 것을 체크해야한다.

이에 대한 해결책으로는 tx.origin 중요 로직에서 사용하지 않는 것이 좋다.

 

 

 

3. Unsafe type inference

Solidity 다양한 유형의 정수를 지원한다.(=uint8, uint256…)

값이 최대값을 초과할 Solidity 예외를 throw하 않는다.

이러한 점진적인 증가는 일반적인 연산이기 때문에 주의하지 않는다면 충분히 오류가 발생하는 지점이 있다.

 

 

 

 contract type{

     uint8 count = 0;

     mapping(address => uint) balances;

     function defect(address _addr) payable public{

         require(balance[_addr]!=0);

         for(;count<300;count++){

             msg.sender.call.value(balance[_addr]);

         }

     }

 }

 

 

위의 예시에서는 for문내의 조건의 범위가 300이하로 돌아가지만 count변수는 255까지 밖에 갈수 없기 때문에 오버플로우가 일어나고 해당 조건에서 탈출할 없다.

 

  • 문제 요인 해결

루프나 일정 조건에서 변수를 사용한다면 Uint나 uint256 사용하여  해당 오류가 발생하지 않도록 한다. 

 

 

4. Short address

솔리디티 컨트랙트 자체에서는 수행되지 않지만, 컨트랙트와 상호작용하는 3자의 애플리케이션에서 발생할 있는 공격

 

솔리디티로 작성된 코드 파일을 컴파일하면 컨트랙트 ABI파일을 생성하고 EVM에서는 파일을 이용해서 컨트랙트를 호출하고 트랜잭션에서 데이터를 읽는다.

 

지정된 파라미터 길이보다 짧게 인코딩된 파라미터가 전송되기도 하는데 이런 경우에 EVM 파라미터 끝에 0 추가해서 길이를 맞춘다.

이때 3 애플리케이션이 입력의 유효성을 검사하지 않을 경우 문제가 발생한다.

 

 

function transfer(address to, uint tokens) public returns (bool success);

 

Transfer함수가 인코딩 되면 파라미터인 to, tokens 순서대로 인코딩된다.

만약 입력 값이

주소 : 0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddead

토큰 : 100

인코딩 되면 => a905 9cbb / 0000 0000 0000 0000 0000 0000 dead dead dead dead dead dead dead dead dead dead / 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0005 6bc7 5e2d 6310 0000

같이 된다.

이는 주소 20바이트에 12개의 0바이트를 추가해서 32바이트 길이로 만드는 것이다.

 

 

4바이트 (a905 9cbb) transfer함수를 나타내고, 다음 32바이트는 주소, 다음 32바이트는 토큰 100개를 의미한다.

 

만약 주소를 보낼 1바이트가 누락된 주소(0xdeaddeaddeaddeaddeaddeaddeaddeaddeadde) 보낸다면 인코딩 되는 값이 달라진다.

 

이는 주소가 31바이트가 되게 되고 token값에서 1바이트가 당겨지게 된다. EVM 마지막에 데이터가 제대로 채워지지 않았음을 표시하고 누락된 바이트를 추가한다.

위에서는

 

a905 9cbb / 0000 0000 0000 0000 0000 0000 dead dead dead dead dead dead dead dead dead de00 / 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 056b c75e 2d63 1000 0000

되어 토큰이 100개가 아니라 256개가 되버린다.

 

 pragma solidity ^0.4.11;

 

 contract MyToken{

     mapping(address=> uint) balances;

 

     event Transfer(addressindexed_from, addressindexed_to, uint256_value);

 

     function MyToken() {

         balances[tx.origin] =10000;

     }

     // <yes> <report> SHORT_ADDRESSES

     function sendCoin(addressto, uintamount) returns(boolsufficient) {

         if(balances[msg.sender] <amount) returnfalse;

         balances[msg.sender] -=amount;

         balances[to] +=amount;

         emit Transfer(msg.sender, to, amount);

         returntrue;

     }

 

     function getBalance(addressaddr) constantreturns(uint) {

         return balances[addr];

     }

 }

 

 

  • 문제 요인 해결

외부 애플리케이션의 모든 입력 파라미터는 블록체인에 보내기 전에 유효성을 검사해야 한다. 또한 파라미터의 순서를 고려하는 것이 중요하다.

 

(compiler 0.4.26막힘)

 

 

 

 

'BlockChain' 카테고리의 다른 글

Solidity 취약점 #5  (0) 2022.01.17
Solidity 취약점 #4  (0) 2022.01.14
Solidity 취약점 #2  (0) 2022.01.12
Solidity 취약점 #1  (0) 2022.01.11
Solidity 취약점 카테고리화  (0) 2022.01.11