[마스터링 이더리움] 스마트 컨트랙트 보안 - 패턴16 Tx.Origin 인증 (마지막)
Tx.Origin 인증
- 솔리디티는 tx.origin이라는 전역변수가 있다. 이것은 call을 호출한 계정의 주소를 저장한다.
- 컨트랙트에서 이 값을 이용해서 인증과 같은 작업을 하는 것은 위험하다
취약점
- Phishable 컨트랙트 (피싱 위험 있는 컨트랙트)
1 contract Phishable {
2 address public owner;
3
4 constructor (address _owner) {
5 owner = _owner;
6 }
7
8 function () public payable {} // collect ether
9
10 function withdrawAll(address _recipient) public {
11 require(tx.origin == owner);
12 _recipient.transfer(this.balance);
13 }
14 }
11행에서 tx.origin을 아용하여 withdrawAll 함수를 실행할 수 있다.
AttackContract 컨트랙트
1 import "Phishable.sol";
2
3 contract AttackContract {
4
5 Phishable phishableContract;
6 address attacker; // The attacker's address to receive funds
7
8 constructor (Phishable _phishableContract, address _attackerAddress) {
9 phishableContract = _phishableContract;
10 attacker = _attackerAddress;
11 }
12
13 function () payable {
14 phishableContract.withdrawAll(attacker);
15 }
16 }
- 일반 사용자는 해커의 컨트랙트가 일반 계정인지 알고 금액을 입금할 수 있다.
- 이렇게 되었을 때, Phishable 컨트랙트를 생성한 일반 사용자가 AttackContract에 이더를 보내면 AttackContract의 13행 폴백함수가 실행된다.
- 여기서 다시 해커는 Phishable 컨트랙트의 10행의 withdrawAll함수를 호출하는데, 인자로 해커의 계정 주소를 넣는다.
- AttackContract의 11행에서 tx.origin은 Phishable 컨트랙트를 생성한 일반 사용자이다. 왜냐하면, 이 사용자가 애초에 AttackContract의 폴백함수를 호출했고, 거기서 다시 Phishable의 withdrawAll함수를 호출했기 때문이다. 이처럼 tx.origin은 애초에 call을 시작한 계정 정보를 계속 유지하고 있다.
- 그 다음은 다 털리는 일만 남았다!
예방기법
- tx.orgin을 어떤 권한 설정으로 사용하지 말라
- 다만, 정당한 사례도 있다. 외부 컨트랙트가 현재 컨트랙트를 호출하지 못하게 막는데 require(tx.origin == msg.sender)처럼 사용할 수 있다.
이것으로 길고 긴 스마트 컨트랙트 보안의 16가지 패턴을 알아봤습니다. 번역서로는 내용 파악이 어려워서 원서와 인터넷 검색을 통해 이해한 내용을 바탕으로 적었습니다.
책의 저자는 스마트 컨트랙트 보안 개발을 할 때, 다음 3가지를 강조했습니다!
- 잘 구축되고 검증된 라이브러리를 사용하라.
- OpenZeppelin이 좋다
- 혼자 개발하지 말고, 검증된 라이브러리를 사용하라!