본문 바로가기

자주나오는패턴

[Common Vulnerabilities] Insufficient Gas Griefing

부족한 가스 그리핑 공격은 sub-call을 수행할 때 발생할 수 있다. 예를 들어 Acontract가 Bcontract의 어떤 함수를 실행할 때 공격자는 Acontract에 대한 충분하지 않은 가스를 제공한다. 그랬을 경우에 1. 트랜잭션의 실패 2. Acontract에서 실패했지만 계속해서 실행할 수 있다. 그것으로 특정한 컨트랙트에 대해서 계속해서 방해를 할 수 있다. 그러니까 공격자에게 '직접적'인 이득은 없지만 프로토콜의 시스템을 방해하는 것이다. 

 

우선 griefing attack은 가스문제 전에 악의적인 접근을 지속하는 것을 말한다. 

예시 컨트랙트로 설명하겠다. 

contract DelayedWithdrawal {
    address beneficiary;
    uint256 delay;
    uint256 lastDeposit;

    constructor(uint256 _delay) {
        beneficiary = msg.sender;
        lastDeposit = block.timestamp;
        delay = _delay;
    }

    modifier checkDelay() {
        require(block.timestamp >= lastDeposit + delay, "Keep waiting");
        _;
    }

    function deposit() public payable {
        require(msg.value != 0);
        lastDeposit = block.timestamp;
    }

    function withdraw() public checkDelay {
        (bool success, ) = beneficiary.call{value: address(this).balance}("");
        require(success, "Transfer failed");
    }
}

여기서 보면 문제가 없어보이지만 누구든 deposit함수를 실행할 수 있기 때문에 griefing attacker는 최소한의 value ( = 1 wei)를 넣고 계속해서 컨트랙트를 실행하면 lastDeposit이 계속해서 최신 block.timestamp로 변경되기 때문에 누구도 withdraw를 하지 못하는 경우가 생긴다. 

 

이어서 Insufficient Gas Griefing은 주로 외부 호출을 실행하는 컨트랙트에 영향을 미치는 공격이다. 이 공격에서 공격자는 최상위 함수의 성공을 보장하기 위해 충분한 가스를 제공하는 동시에 가스 소진으로 인해 외부 호출의 실패를 야기시킬 수 있다. 그러니까 최상위 함수는 실행하지만 스테이트의 변경은 발생하지 않는다. 

contract Relayer {
    mapping (bytes => bool) executed;
    address target;

    function forward(bytes memory _data) public {
        require(!executed[_data], "Replay protection");
        // more code for signature validation in between
        executed[_data] = true;
        target.call(abi.encodeWithSignature("execute(bytes)", _data));
    }
}

 

위 컨트랙트에서 forward함수를 실행하면 target.call함수의 실행결과를 리턴하지 않기 때문에 자연스럽게 실행될 것이다. 그래서 한번 체크가 된 _data에 대해서 execute함수가 실행되지 않았지만 다른 사람이 동일한 _data로 forward함수를 실행하면 실패할 것이다. 

 

 

insufficient gas gridfing을 막는 두가지 방법이 있다.

1. relay tx에 허가된 유저만 가능하게 한다.

2. 충분한 가스를 제공하도록 요구한다. 

 

2번 가스 체크 방법

contract GasCalc {
    uint public  gasUsed; 
    uint64[] public  numArr;

    function updateArr(uint64 _num) external  returns(uint, uint){
        uint initGas = gasleft();
        numArr.push(_num);
        uint finalGas = gasleft();
        gasUsed = initGas - finalGas;
        return (initGas , finalGas);
    }
}

사용한 가스 : 44422

 

 

 

 

[ 참고 및 추가자료 ]

https://swcregistry.io/docs/SWC-126/

https://scsfg.io/hackers/griefing/

https://www.rareskills.io/post/solidity-gasleft