1. Transfer forwards all gas
기본적으로 가스는 트랜잭션을 호출한 sender가 지불하도록 되어 있는데 이는 두 가지 공격 가능성을 열어둔다.
- 공격자가 트랜잭션을 보내도록 유도한다.
- 트랜잭션이 더 많은 가스를 소모하도록 유도한다.
Call 함수는 Caller 컨트랙트의 Fallback Function을 invoke하고 재진입 공격이 가능하다. 이는 첫 번째 공격이 가능하게 하며 두 번째는 공격자의 트랜잭션에 포함되는 매개변수 또는 개발자의 잘못된 로직으로 인한 가스를 요하는 잦은 실행이 유도된다.
즉 재진입 공격과 유사하게 진행되며 예방 방법 또한 같은 선상에 있다.
+ (Supplement)
2. DOS
사용자가 일정 기간 또는 영구적으로 컨트랙트를 실행할 수 없게 만드는 공격이다.
기본적으로 EVM은 강제 종료를 통해 DOS공격으로부터 프로그램을 보호한다. EVM은 실행 시작 시 가스를 할당하고 각 실행 단계에서 가스의 소모가 이루어지는데 남은 가스가 실행에 필요한 가스보다 적으면 스마트 컨트랙의 실행을 종료하여 부분 또는 전체 Rollback을 유발한다
- Dos costly patterns and loops
인위적으로 부풀려질 수 있는 배열을 사용해서 배열 크기를 크게 만들면 for루프를 실행하는데 요구되는 가스가 가스 한도를 초과하게 된다. 그렇게 되면 함수가 원하는 대로 동작하지 않게 만들 수 있다.
1-1) arr를 manipulate하여 addr를 추가하고 이를 통해 reverting 을 일으켜 DOS 유발
(Code Check 필요)
1-2) 가변의 arr길이의 length로 for문이 돌기 때문에 매우 큰 값으로 인한 gas cost가 초과되고 이로 인해 DOS 유발
// SPDX-License-Identifier:GPL-30
pragma solidity >=0.5.0 <0.9.0;
contract testLL{
uint constant LARGEGAS = 100000;
address payable addrArr;
function LongList(uint256 memory nextV, uint[] memroy arr, address payable _addr) public{
uint256 i = nextV;
addrArr = _addr;
for(;i<arr.length && gasleft() > LARGEGAS; i++){
addrArr.send(arr[i]);
}
}
}
=> Arr라는 배열 변수를 외부로부터 입력받는다.
- 문제 요인 및 해결
루프의 조건에 외부 사용자가 인위적으로 조작할수 있는 변수를 사용하였다.
때문에 외부로 부터 가변 길이의 변수를 받지 않는 쪽과 중요 로직에 외부 변수를 사용하지 않도록 점검과 수정이 필요하다.
- Dos by external contract
외부 호출을 기반으로 한 진행상태를 갖는 컨트랙트에서 발생할 수 있다.
// SPDX-License-Identifier:GPL-30
pragma solidity >=0.5.0 <0.9.0;
contract sender{
address payable emperor;
uint public rewardPrice = 500;
function set_empr(address _addr) public{
emperor = _addr;
}
function() external payable{
require(msg.value >= rewardPrice);
uint MCrownPrice = MfindCrownPrice();
emperor.transfer(MCrownPrice);
(bool success, ) = emperor.call.value(MCrownPrice)("");
require(success);
// After logic
}
}
contract Receiver(){
function() external payable{
revert();
}
}
Transfer또는 send를 통해 타 컨트랙트에게 일정 금액을 전송하는 코드를 생각해 볼 수 있다.
Transfer는 실패 시 자체적으로 에러를 일으키고 call의 경우 True,False를 입력 받아 exception을 Control한다.
하지만 Receiver입장에서 이를 모두 revert시켜 버리면 Control Flow가 틀어지지는 않지만 더이상 실행이 되지 않는 DOS공격으로 되돌아 온다.
- 문제 요인 및 해결
외부 호출에 대한 의존성이 문제점이라 볼 수 있다.
하지만 이러한 호출이 불가피한 경우도 존재할 것이기에 호출 이후의 검증 로직이 충분하지 못한 것이 원인이라 여겨진다.
Call의 경우 false의 경우 lock time을 걸어두거나 다른 로직을 실행하도록 수정한다면 좋을 것이다.
3. TimeStamp Dependency
BlockChain내의 각 블록에는 1) Timestamp, 2) Cryptographic hash, 3) Transaction Data의 세 가지 정보가 포함되어 있다.
TimeStamp는 채굴자가 작업 증명 퍼즐을 계산한 후 블록 내 모든 거래를 검증하는 시간을 나타낸다.
채굴자는 이러한 Block TimeStamp를 조작할 수 있다.(= 900초 이내 검증완료)
암호화 해시는 블록 데이터의 무결성을 확인하기 위한 해시값이다. 이 암호화 해시는 두 블록을 연결하는 종속적 의미를 가진다.
거래 데이터는 거래에 따라 다를 수 있지만 간단한 트랜잭션의 경우 발신자와 수신자의 SC계정 주소와 이더가 될 수 있다.
Ex>
// SPDX-License-Identifier:GPL-30
pragma solidity >=0.5.0 <0.9.0;
contract Lotto{
uint pubic pastBlockTime;
constructor() pubic payable{}
function() public payable{
require(msg.value == 10 ether);
require(now != pastBlockTime); // 블록당 오직 하나의 트랜잭션만 가능
pastBlockTime = now;
if(now %15 ==0){
msg.sender.transfer(address(this).balance);
}
}
}
위 코드는 복권을 스마트 컨트랙트로 만든 예시이다.
채굴자에 의해 생성된 pastBlockTime을 통해 당첨여부를 판단하게 되며 이는 하나의 트랜잭션만 허용한다.
이를 채굴하는 채굴자는 Timestamp를 조정해 당첨을 유도할수 있다.
- 문제 요인 및 해결
스마트 컨트랙트는 난수 생성을 위해 타임스탬프를 사용한다. 그렇다면 타임스탬프의 값을 설정하는 데 채굴자가 관여하기 때문에 타임스탬프는 소위 결정적 임의 값이 된다.
만약 복권 구현과 같은 컨트랙트에서 타임스탬프를 임의 값으로 사용한다면 이는 취약한 코드가 될 수 밖에 없다.
이에 대한 코드는 block.timestamp나 now와 같이 사용된다.
이를 막기 위해선 블록 Timestamp를 특정 조건 체크를 위한 요소로 사용하면 안된다.
하지만 시간과 관련된 조건이 필요한 경우가 있다. 예를 들어 컨트랙트가 해지되는 시간, 만기일 적용과 같은 시간 관련 조건이 있을 경우에는 블록의 타임스탬프를 사용하지 말고 block.number과 평균 블록 시간을 사용해서 설정하는 것이 좋다.
'BlockChain' 카테고리의 다른 글
Solidity 취약점 #4 (0) | 2022.01.14 |
---|---|
Solidity 취약점 #3 (0) | 2022.01.13 |
Solidity 취약점 #1 (0) | 2022.01.11 |
Solidity 취약점 카테고리화 (0) | 2022.01.11 |
Solidity #5 (0) | 2022.01.10 |