챕터9:스마트 컨트랙트 보안
다른 프로그램들과 마찬가지로 스마트 컨트랙트도 작성된 그대로 실행되는데, 프로그래머가 의도한 내용과 반드시 일치하지 않을 때가 있다. 또한 모든 스마트 컨트랙트는 공개되어 있으며, 모든 사용자는 트랜잭션을 생성하여 간단하게 상호작용할 수 있다. 어떤 취약점이라도 악용될 수 있으며, 손실은 거의 항상 복구할 수 없다. 따라서 모범 사례를 따르고 잘 테스트된 디자인 패턴을 사용하는 것이 가장 중요하다.
보안 모범 사례
방어적 프로그래밍은 스마트컨트랙트에 특히 적합하다.
미니멀리즘/단순성
- 코드는 간단할수록 안전하다.
코드 재사용
- 같은 코드를 반복하지마라(함수, 라이브러리로 정리), 개발된 라이브러리를 사용하는 것이 디자인이나 기능은 몰라도 일단 안전하다.
코드 품질
- 스마트 컨트랙트의 버그는 금전적 손실을 발생시킬 수 있고, 배포되면 문제를 해결할 수 있는 방법이 거의 없다. 엄격해야한다.
가독성/감사 용이성
- 코드는 명확하고 이해하기 쉬워야 한다. 읽기 쉬울수록 감사하기도 쉽다. 커뮤니티의 지혜와 혜택을 받자!
테스트 범위
- 할 수 있는 모든 것을 테스트하라. 모든 인수가 예상 범위 내에 있는지, 올바르게 형식이 지정되었는지를 확인하자!
보안 위험 및 안티 패턴
스마트 컨트랙트 프로그래머라면 컨트랙트를 위험에 노출시키는 프로그래밍 패턴을 감지하고 피할 수 있도록 가장 공통적인 보안 위험에 익숙해야한다.
재진입성
컨트랙트는 일반적으로 이더를 처리하기 때문에 종종 다양한 외부 사용자 주소로 이더를 전송한다.
이런 작업을 수행 하려면 컨트랙트는 외부 호출을 요청해야 하지만 이러한 외부 호출은 공격자가 악용할 수 있다.
공격자는 컨트랙트에 콜백을 포함하여 대체코드를 실행하도록 강제할 수 있다(폴백 함수르 통해). 이런 종류의 공격은 악명 높은 DAO해킹(https://en.wikipedia.org/wiki/The_DAO_(organization))에 사용되었다.
취약성
이런 유형의 공격은 컨트랙트가 알 수 없는 주소로 이더를 전송할 때 발생할 수 있다. 공격자는 폴백 함수에 악성 코드를 갖고 있는 컨트랙트를 외부 주소에 조심스럽게 만들어놓을 수 있다. 따라서 어떤 컨트랙트에서 이 조수로 이더를 보내면 악의적인 코드가 호출된다. 일반적으로 악성 코드는 취약한 컨트랙트에서 개발자가 예상하지 않았던 작업을 실행한다. '재진입(reentrancy)'이라는 용어는 외부의 악의적인 컨트랙트가 취약한 컨트랙트의 함수를 호출하고 코드 실행 경로가 그 안으로 '재진입한다(reenters)'는 사실에서 비롯된 것이다.
쉽게 말하면 기존의 컨트랙트를 재귀적인(recursive한) 컨트랙트로 호출해 특정 기능을 계속 실행시키는 동작 방식이다.
contract EtherStore {
대충 입금 함수
대충 출금 함수
}
EtherStore라는 컨트랙트가 있다고 가정했을 때,
이를 공격하는 컨트랙트를 만든다.
contract Attact {
대충 EtherStore 컨트랙트 import 하는 내용
어택함수
폴백함수{
if(공격 컨트랙트 잔고가 있다면){
어택함수
}
}
}
어택함수에서는 EtherStore에서 출금을 진행하면서 에러가 날 만한 상황을 발생시킨다.
따라서 출금이 되고, 에러가 났기 때문에 Attact 함수의 폴백함수를 실행하게 된다.
폴백함수는 다시 어택함수를 실행시켜, 공격을 당하는 컨트랙트의 잔고가 빌때까지 어택함수를 실행시키게 되고
공격을 당하는 컨트랙트의 잔고가 사라지면, 컨트랙트의 실행을 마치고 함수가 끝난다.
재진입 공격에 대한 내용이 담긴 블로그
이더리움 스마트 컨트랙트 모범 사례
예방 기법
1. (가능한) 이더를 외부의 컨트랙트에 보낼 때 내장된 transfer(http://bit.ly/20gvnng) 함수를 사용
-> transfer 함수는 외부 호출에 대해 2300개의 가스만을 보내는데, 이 정도의 가스양으로는 목적지 주소/컨트랙트가 다른 컨트랙트를 호출하기에는(즉, 송신 컨트랙트에 재진입하는 것) 충분하지 않다.
2. 이더가 컨트랙트(또는 외부 호출)에서 전송되기 전에 상태 변수를 변경하는 모든 로직이 발생하도록 코드 작성
-> 알 수 없는 주소로 보내는 외부 호출을 수행하는 코드는 지역 함수나 코드 실행 부분에 있어서 가장 마지막 작업이 되도록 하는 것이 바람직. 이를 체크 효과 상호작용 패턴이라고 한다.(http://bit.ly/2EVo70v)
3. 뮤텍스 도입
코드 실행 중에 컨트랙트를 잠그는 상태 변수를 추가하여 재진입 호출 방지
실제 사례: DAO
DAO(Decentralized Autonomous Organization, 탈중앙화 자율 조직) 공격은 이더리움의 초기 개발에서 발생한 주요 해킹 중 하나였다. 당시 기준으로 컨트랙트는 1억 5천만 달러 이상을 보유하고 있었다. 재진입은 그 공격에서 중요한 역할을 했으며, 궁극적으로는 이더리움 클래식(Ethereum Classic, ETC)을 만든 하드 포크로 이어졌다. DAO 공격에 대한 상세한 분석은 (http://bit.ly/2EQaLCI)를 참고하라. 이더리움의 포크 내역, DAO 해킹 타임라인, 하드 포크에서 ETC의 탄생에 대한 자세한 내용은 부록 B에서 확인할 수 있다.
산술 오버플로/언더플로
이더리움 가상 머신은 정수들에 대해 고정된 크기의 데이터 타입을 지정한다. 즉, 정수 변수는 특정 범위의 숫자만 나타낼 수 있다. 예를 들어, uint8은 [0,255] 범위의 숫자만 저장할 수 있다. uint8에 256을 저장하려고 하면 결과는 0이 나온다. 사용자 입력을 점검하지 않고 계산을 수행하면 저장하는 데이터의 유형이 범위를 벗어나는 숫자가 될 수 있어 솔리디티 변수를 악용할 수 있다.
산술 오버플로/언더플로에 대한 추가 정보는 '스마트 컨트랙트 체결 방법'(https://bit.ly/2nNLuOr), 이더리움 스마트 컨트랙트 모범 사례(https://bit.ly/2MOfBPv), '이더리움, 솔리디티, 정수 오버플로: 1970년과 같이 블록체인 프로그래밍하기'(https://bit.ly/2xvbx1M)를 참고하라.
//일반적으로 알고 있는 오버,언더플로와 같다.
취약점
값이 의도치 않은 값으로 바뀔 수 있음을 주의하자.
예방 기법
수학 라이브러리를 사용하거나 만드는 것(나누기는 오버플로/언더플로를 발생시키지 않는다, EVM은 0으로 나누면 원 상태로 되돌린다)
-> 오픈 제플린의 SafeMath 라이브러리는 언더플로/오버플로 취약점을 방지하는 데 사용할 수 있다.
실제 사례 : PoWHC 및 일괄 전송 오버플로9CVE-2018-10299)
위크 핸즈 증명 코인(Proof of Weak Hands Coin, PoWHC)은 원래 일종의 농담으로 고안되었지만, 한 인터넷 단체에 의해 계획된 폰지 스킴(Ponzi scheme)이었다. 불행하게도 계약자는 이전에 오버플로/언더플로를 보지 못했고, 결과적으로 866이더가 컨트랙트에서 유출되었다. 에릭 바니사드르(Eric Banisadr)는 이 사건에 대한 그의 블로그 게시물(https://bit.ly/2wrxIFJ)에서 언더플로가 어떻게 발생했는지(앞에서 설명한 이더넷 과제와 크게 다르지 않은) 좋은 개요를 제공한다.
또 다른 예(http://bit.ly/2CUf7WG)는 batchTransfer() 함수를 여러 ERC20 토큰 컨트랙트에 구현한 경우다. 이 구현은 오버플로 취약점이 있었는데, 펙실드(PectShield)의 계정(https://bit.ly/2HDlls8)에서 자세한 내용을 읽을 수 있다.
예기치 않은 이더
일반적으로 이더가 컨트랙트에 전달될 때는 폴백 함수나 컨트랙트에 정의된 또 다른 함수를 실행해야 한다. 이것에 대해 두가지 예외가 있는데, 어떤 코드를 실행하지 않고 컨트랙트 내에 이더가 존재할 수 있는 경우다. 전송되는 모든 이더에 대해 코드 실행에 의존하는 컨트랙트는 이더가 가제로 전송되는 공격에 취약할 수 있다. 이에 대한 더 자세한 내용은 '스마트 컨트랙트를 안전하게 만드는 방법'(https://bit.ly/2MR8Gp0)과 '솔리디티 보안 패턴: 이더를 컨트랙트로 강제 전송하기'(https://bit.ly/2SMs0KM)를 참고하라
이 문장에서 핵심은 ' 전송되는 모든 이더에 대해 코드 실행에 의존하는 컨트랙트는 이더가 가제로 전송되는 공격에 취약할 수 있다.'라는 부분이다.
취약점
불변 검사(invariant checking) : 올바른 상태 전이 or 유효성 검사를 강제하는 데 유용한 방어 프로그래밍 기법
연산 후에 변경되지 않는 불변량(변경되지 말아야 할 매트릭스 or 파라미터들 ex. erc20_totalSupply)을 정의하고 변경되지 않았는지 확인하는 것이다.
컨트랙트에 저장된 현재 이더양은 불변량처럼 보이지만 외부 사용자들에 의해 조작될 수 있다.
개발자들은 이더가 payable 함수를 통해서만 얻을 수 있다고 오해를 하는데,(이로 인해 this.balance를 잘못된 방법으로 사용할 수 있다)
이를 통하지 않고 심지어 컨트랙트에서 코드를 실행하지 않고도 강제적으로 이더를 보낼 수 있는 두가지 방법이 있다.
1. 자기파괴(self_destruct/suicide)
-> 컨트랙트는 selfdestruct 함수를 통해 컨트랙트를 파괴하고 해당 컨트랙트에 있던 모든 이더를 지정한 주소로 보낼 수 있는데, 이 지정된 주소가 컨트랙트인 경우 어떤 함수(폴백 포함)도 호출되지 않는다. (Http://bit.ly/2OfLukM)
2. 미리 보내진 이더
-> 컨트랙트 주소는 결정론적이다. 생성될 컨트랙트의 주소를 미리 알 수 있기 떄문에 미리 계산된 주소로 컨트랙트가 생성되기 전에 이더를 보낸다면, 이 컨트랙트 생성시에 해당 이더를 가지고 생성하게 된다. (http://bit.ly/2EPj5Tq)
this.balance의 잘못된 사용의 예를 책에서 들어준다.
책에서의 예시는 플레이어가 0.5이더를 단위로 이더를 보내어 3,5,10에 해당하는 마일스톤에 도달하는 사람에게 모든 이더를 제공하는 코드를 보여주는데, 여기에서 0.1이더를 공격자가 보낸다면, 조건이 모두 거짓이 되어 해당 마일스톤에 도달할 수 없게 된다.
예방 기법
보통은 This.balance의 공격으로 발생하고, 해당 값은 조작될 수 있기 때문에 가능하다면 컨트랙트 잔액의 정확한 값에 의존하지 않아야 한다. this.balance에 근거한 로직을 적용할 경우 예기치 않은 잔액에 대해 대처해야 한다.
만약 입금된 이더의 정확한 값이 필요하다면, 입금된 이더를 안전하게 추적할 수 있도록 함수 내에서 증가하는 자체 정의된 변수를 사용해야 한다. 이 변수는 selfdestruct 호출을 통해 강제로 보내진 의더에 의해 영향을 받지 않는다.
추가 예제
악용 가능한 컨트랙트의 몇 가지 예는 언더핸드 솔리디티 코딩 컨테스트(Underhanded Solidity Coding Contest, https://bit.ly/2ThWdP3)에서 찾아볼 수 있으며, 이 절에서 제기한 여러가지 위험이 확장된 예들을 살펴볼 수 있다.
DELEGATECALL
CALL과 DELEGATECALL 연산 코드는 이더리움 개발자가 코드를 모듈화할 수 있게 하는 데 유용하다. 컨트랙트에 대한 표준 외부 메세지 호출은 CALL 연산코드에 의해 처리되므로 코드가 외부 컨트랙트/함수의 컨텍스트에서 실행된다. 대상 주소에서 실행 코드가 호출 컨트랙트의 컨텍스트에서 실행되는 것을 제외하고 DELEGATECALL 연산코드는 거의 같으며, msg.sender와 msg.value는 변경되지 않는다. 이 특성을 사용하면 라이브러리(library)를 구현할 수 있으므로 개발자는 재사용 가능한 코드를 일단 배포하고 향후 컨트랙트에서 호출할 수 있다. 이 두 연산코드의 차이점은 간단하고 직관적이지만, DELEGATECALL을 사용하면 예기치 않은 코드가 실행될 수 있다. 자세한 내용은 로이루의 이더리움 스택 교환 질문(http://bit.ly/2AAElb8)과 솔리디티 문서(http://bit.ly/2Oi7UIH)를 참고하라.
취약점
라이브러리 자체의 코드는 안전하고 취약점이 없을 수 있음
그러나 다른 어플리케이션의 컨텍스트에서 실행될 때 새로운 취약점이 발생할 수 있음.
피보나치 수를 사용하여 예를 살펴보자
이 라이브러리는 수열에서 n번째 피보나치 수를 생성할 수 있는 함수를 제공한다.
시작하는 수를 setStart함수로 지정해주고, setFibonacci 함수에 n을 입력하면,
n번째 피보나치 수를 알 수 있다.
이 라이브러리를 사용하는 컨트랙트를 살펴보자.
이 컨트랙트를 이용해서 참가자는 컨트랙트에서 이더를 출금할 수 있다.
디폴트 가시성
public private을 잘 해둬야 함
엔트로피 환상
이더리움 생태계는 결정론적 상태 -> 무작위성이란 없다.
외부 컨트랙트 참고
외부 컨트랙트를 참고할 때 잘 확인해봐라.
이더리움 DAO 해킹 사건 자료
https://steemit.com/kr/@keepit/3eq17d-keepit-2
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=mage7th&logNo=221168044478
재진입공격에 대한 자료
https://ggs134.gitbooks.io/solidityguide/content/512-re-entrancy.html
'블록체인 > 책' 카테고리의 다른 글
스마트 컨트랙트와 바이퍼 | 마스터링 이더리움 (0) | 2021.09.28 |
---|---|
스마트 컨트랙트와 솔리디티 | 마스터링 이더리움 (0) | 2021.08.29 |
트랜잭션 | 마스터링 이더리움 (0) | 2021.08.13 |
지갑 | 마스터링 이더리움 (0) | 2021.07.25 |
암호학 | 마스터링 이더리움 (0) | 2021.05.10 |