1. Strict balance equalitiy
Self_destruct 취약점, 예기치 않은 이더와 balance오류 취약점과 연관지을 수 있다.
Self_destruct API로 인한 이더의 전송은 Fallback function을 호출하지 않으며 Ether를 강제로 보낼 수 있고 피해자 계약은 거부할 수 없다.
따라서 의도하지 않은 동작이 발생할 수 있다.
pragma solidity ˆ0.4.25;
contract Gamble{
address owner ;
address [] members;
address[] participators;
uint participatorID = 0;
function constructor (){ // constructor Dependency
owner = 0xdCad . . . d1D3AD; /∗ Hard Code
}
function() payable{
ReceiveEth();
}
function ReceviceEth() payable{
if(msg.value != 1 ether){
revert();
}
members.push(msg.sender);
participators[participatorID] = msg.sender;
participatorID++;
if(this.balance ==10 ether){
getWinner();
}
}
위의 ReceiverEth함수에서는 1 ether단위로 받지만 self_destruct로 인해 일정 금액을 송금받아 (ex> 0.5eth) 10 ether단위가 만들어 질수가 없어 getwinner함수가 실행될 수가 없을 수 있다.
- 문제 요인 및 해결
Fallback 함수로 부터 ReceiveEth가 실행되는 로직에서 1ether단위가 아니면 들어오지 않는다는 생각이 취약점을 만들었다. Self_destruct라는 특수한 메소드로 인해 balance가 달라질수 있다는 것을 인지하고 if 조건을 range로 바꾼다면 도움이 될 것이다.
2. Delegate call
DelegateCall은 호출하는 컨트랙트의 맥락에서 타겟 주소의 코드가 실행된다.
이 경우 msg.sender와 msg.value의 값이 변경되지 않는다.
컨트랙트가 실행될 때 다른 주소로부터 코드를 동적으로 읽어들일 수 있다는 것을 의미한다.
저장 장소, 현재 주소, 잔액은 호출하는 주소를 참조하고, 실행하는 코드는 호출된 주소에서 읽어들인다.
단순하게 생각하면 A컨트랙트에서 B라는 컨트랙트에 있는 코드를 A에 있는 코드인 것처럼 사용할 수 있다.
이러한 특성으로 call과 delegatecall 연산코드는 이더리움 개발자가 코드를 모듈화할 수 있게 해준다.
//라이브러리 컨트랙트
contract FibonacciLib{
uint public start;
uint public calculatedFibNumber;
function setStart(uint _start) public {
start = _start;
}
function setFibonacci(uint n) public{
calculatedFibNumber = fibonacci(n);
}
function fibonacci(uint n) internal returns(uint){
if(n==0) return start;
else if(n==1) return start+1;
else return fibonacci(n-1) + fibonacci(n-2);
}
}
contract FibonacciBalance{
address public fibonacciLibrary;
uint public calculatedFibNumber;
uint public start = 3;
uint public withdrawalCounter;
bytes constant fibsig = bytes4(sha3("setFibonacci(uint256"));
constructor(address _fibonacciLibrary) public payable{
fibonacciLibrary = _fibonacciLibrary;
}
function withdraw(){
withdrawalCounter +=1;
require(fibonacciLibrary.delegatecall(fibsig,withdraw));
msg.sender.transfer(calculatedFibNumber * 1 ether);
}
function() public{
require(fibonacciLibrary.delegatecall(msg.data));
}
}
FibonacciBalance Contract에서 withdraw함수는 library의 setFibonnaci를 delegate Call로 호출한다.
그리고 라이브러리 내에서 fibonnaci함수가 실행되어 Fibnumber가 계산된다.
이때 상태 변수 혹은 스토리지 변수는 컨트랙트에 도입될 때 순차적으로 슬롯에 배치된다.
여기서 delegate Call을 사용하였으므로 FibonacciLib에서는 Start를 쓰려고 slot[0]으로 가면 fibonacciLibrary를 쓰게 된다.
그러므로 주소값인 fibonacciLibrary를 사용하게 되고 start값이 매우 커지게 된다.
이렇게 되면 이후 withdraw함수로 인해 FibonnaciLibrary로의 접근은 이루어지기 힘들어진다.
또한 만약 start값에 공격 contract 주소를 넣게 되면 악의적인 컨트랙트를 실행되게 할 수 있다.
(Check 필요)
3. 가시성
가시성이란 함수를 호출할 수 있는 범위를 지정하는 것이다.
=> external / public / internal / private
함수에 대한 기본 가시성(디폴트 값)은 public 이므로 가시성을 따로 지정하지 않으면 자동으로 그 함수는 외부에서 호출할 수 있는 public 함수가 된다.
pragma solidity >=0.4.0 <0.9.0;
contract HashforEther{
function withdrawWinning(){
require(uint32(msg.sender) == 0);
}
function _sendWinnings(){
msg.sender.transfer(this.balance);
}
}
0.4 이후로 막힘
- Freezing ether( =greedy contract)
Fronze Ether, Greedy Contract, Locked Money라는 명칭으로 불린다.
이는 이더를 받는 payable함수는 동작하지만 이를 내보내는(=withdraw)하는 로직이 제대로 구현이 되지 않아 이더를 뺄 수 없는 코드를 부른다.
이는 두 가지 경우가 있다.
- Withdraw기능 자체를 내포하지 않은 코드
이더를 받아 들이는 것에는 Fallback function과 같은 함수가 활용되고 내보내는 것에는 call, send, transfer과 같은 함수가 쓰인다. 이러한 함수가 빠진 경우에 해당한다.
contract Bitway{
function() payable external payable{
// None
}
}
- 외부 withdraw library에 의존하지만 이 스마트 컨트랙이 문제가 생기는 경우
라이브러리가 제공하는 withdraw를 delegate Call에 의해 사용하는 경우이다.
만약 이 라이브러리 스마트 컨트랙이 self_destruct에 의해 종료되거나 attacker에 의해 공격 당한다면 더이상 withdraw할 수 있는 방법이 없어진다.
contract Bitway{
address _n1 = 0xNewLibrary;
function withdrawM(){
_n1.delegatecall(msg.data);
}
}
4. Gasless send
Send는 2300 가스를 소모하는 메소드이기에 가벼운 Fallback Function을 수행시키는 것에 충분할수 있다.
하지만 Fallback Function이 타 스마트 컨트랙의 상태를 수정하는 등의 로직이 추가된다면 필요한 가스가 2300이상으로 증가할 수 있다.
이러한 값비싼 Fallback Function은 가스 부족 예외를 일으키고 이더를 전송하지 못하고 유지할 수가 있다.
contract Sender{
function pay(uint val, address payable _recv) public{
if(_recv.send(val)){
}
}
}
contract Receiver{
uint public TotalBal = 0;
function() external payable{
TotalBal = TotalBal + msg.value;
}
}
- Generating randomness
EVM bytecode의 실행은 deterministic 하다. 이러한 특징을 고려하지 않고 pseudo-random number의 생성을 컨트랙트의 정보를 통해 생성하게 된다면 이는 취약한 값이 된다.
그 예는 Timestamp, coinbase, difficulty와 같은 변수 값이 될 수 있다.
random = uint256(blockhash(block.number-1));
random = uint256(keccak256(abi.encodepacked(
block.timestamp,block.coinbase,block,difficulty)));
위와 같이 생성한 값들은 deterministic하며 채굴자에 의해서도 조정될수 있는 부분이 있기에 취약하며 외부 source로 부터 랜덤값을 만드는 것이 대응이 될 것이다.
'BlockChain' 카테고리의 다른 글
Solidity 취약점 #6 (0) | 2022.01.18 |
---|---|
Solidity 취약점 #5 (0) | 2022.01.17 |
Solidity 취약점 #3 (0) | 2022.01.13 |
Solidity 취약점 #2 (0) | 2022.01.12 |
Solidity 취약점 #1 (0) | 2022.01.11 |