create account

[Ethereum] Ethernaut 풀이 - 1.Fallback by modolee

View this thread on: hive.blogpeakd.comecency.com
· @modolee · (edited)
$0.13
[Ethereum] Ethernaut 풀이 - 1.Fallback
![modolee_logo](https://steemitimages.com/DQmWQDjyP5d1RNW3mkCWUkDQ6yh3uDJFUZErPpaKBwKi4gM/iron_modolee.png)
안녕하세요. 개발자 모도리입니다.
Ethernaut 문제 풀이 시리즈를 계속 진행해 보려고 합니다. 이번에는 Level 1 문제를 풀어보겠습니다. 지난 게시물은 아래를 확인해 주세요.
* [Ethernaut 소개](https://steemit.com/kr-dev/@modolee/ethereum-ethernaut)
* [Ethernaut 풀이 - 0.Hello Ethenaut](https://steemit.com/kr-dev/@modolee/ethereum-ethernaut-0-hello-ethenaut)

---

# 1.Fallback

### 임무 확인
총 2가지 조건을 만족 시켜야 임무를 완수할 수 있습니다.
![00_mission.png](https://cdn.steemitimages.com/DQmdz6JkGkPMVfNEzysymiyfNbaVk2CZi1ad2GkxMUiopsv/00_mission.png)
* contract의 ownership을 내 것으로 만들어라.
* contract의 balance를 0으로 만들어라.

---

### 새로운 인스턴스 생성
우선 기본적으로 콘솔을 띄운 상태에서, **Get new instance** 버튼을 누르면, MetaMask 창이 뜨는데 거기에서 submit 버튼을 눌러 트랜잭션을 발생시킵니다.
![01_get_new_instance.png](https://cdn.steemitimages.com/DQmSLhmM9W4STcWD9AJi7w56kivTbZTwWyGEYvnD4nq1XvH/01_get_new_instance.png)
채굴까지 완료가 되면 인스턴스의 주소가 나옵니다.

---

### 여러 주소(계정)들의 관계 분석
콘솔 창에 여러가지 주소들이 나오는데 관계를 한번 분석해 보겠습니다.
![02_accounts_relation.png](https://cdn.steemitimages.com/DQmbwJqGBPnzuM2v5C1UUhu9jqkoxYqjBZS5nU5xuTxFbyh/02_accounts_relation.png)
* Ethenaut - Contract Account (CA)
  * Ethernaut 라는 해당 서비스 전체를 관장하는 컨트랙트입니다.
  * 각 Level 별 컨트랙트를 생성하고 임무 완수 여부 상태를 저장합니다.
  * 콘솔 명령 : `ethernaut`
* Level - Contract  Account (CA)
  * Level 컨트랙트는 사용자가 요청하면 Instance 컨트랙트를 생성합니다.
  * 콘솔 명령 : `level`
* Instance - Contrac tAccount (CA)
  * Level의 임무를 담고 있는 컨트랙트입니다.
  * 콘솔 명령 : `instance`
* Contract - Javascript 변수
  * Instance 컨트랙트를 web3와 ABI를 이용해서 console에서 접근할 수 있게 한 것 입니다.
  * 콘솔 명령 : `contract`
* Player - Externally Owned Account (EOA)
  * MetaMask에 있는 계정입니다.
  * 콘솔 명령 : `player`

---

### Solidity 코드 분석
아래 쪽에 보시면 현재 Level의 instance 배포에 사용된 Solidity 코드가 있습니다. 이 코드를 분석해서 임무를 완수해야 됩니다.
![03_contract_all.png](https://cdn.steemitimages.com/DQmaKuGCopBaV5JPpxZbwBcZGuZsWCc7FoW549cKYn4EYZF/03_contract_all.png)

* **버전 선언, 파일 import, 컨트랙트 선언**
![04_ownable.png](https://cdn.steemitimages.com/DQmatNW5QTCWT4gmLP2xdZnk2vci7WUVq8mHaC7BJD2mwia/04_ownable.png)
  * Solidity 0.4.18 버전을 기준으로 작성되었습니다.(0.4.23 버전 이전에는 생성자를 컨트랙트 이름과 동일하게 선언했었는데, 0.4.23 버전부터는 constructor라는 이름으로 선언합니다.)
  * [Ownable.sol](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/ownership/Ownable.sol) 파일을 import 합니다.
  * Ownable은 OpenZeppelin에서 제공하는 표준 컨트랙트로 컨트랙트의 owner를 지정하고, 컨트랙트 함수의 실행 권한을 제한할 수 있습니다.
  * ![04_01_ownable_openzeppeline.png](https://cdn.steemitimages.com/DQmP9StU7CGvvBpqH6YedoHmc98xzuWi85Kga5exoFQxDyx/04_01_ownable_openzeppeline.png)
  * 컨트랙트 생성 시 owner를 msg.sender로 지정하고, onlyOwner modifier를 이용하는 함수는 owner만 실행 가능하게 만들 수 있습니다.
  * Fallback 컨트랙트는 Ownable 컨트랙트를 상속 받아서 선언합니다.
  * address를 uint로 mapping 하는 contributions 상태 변수를 선언합니다.
* **생성자**
  * ![05_contructor.png](https://cdn.steemitimages.com/DQmVZvwyeXKUPdraiRzS2UXpf2LxWhxJ9TDT1yeaBWxM9ok/05_contructor.png)
  * msg.sender(컨트랙트 배포자)의 contributions 에 1000 ether를 저장합니다.
  * 그리고 Ownable을 상속 받았기 때문에, owner가 msg.sender가 됩니다.
  * `await contract.owner()` 명령으로 owner를 확인해 봅니다.
  * ![10_contract_owner.png](https://cdn.steemitimages.com/DQmcyrVm5bsGJgcsT8t6uxViPykPcGUBmR8LuUTFYcNTRKj/10_contract_owner.png)
  * Level contract가 instance를 배포 했으므로, owner로 설정되어 있습니다.
* **contribute 함수**
  * payable로 선언되어 ether를 전송받을 수 있습니다.
  * msg.value(보낸 ether)가 0.001 ether 미만이어야 아래 코드를 실행합니다.
  * 전송 받은 ether 만큼을 msg.sender(보낸 계정)의 contributions 값에 더합니다.
  * 만약 msg.sender의 contributions 값이 owner의 contributions 값 보다 클 경우 ***owner를 msg.sender에게 넘겨줍니다.***
* **getContribution 함수**
  * gas fee가 소모되지 않는, 단순 상태변수 읽기 함수이기 때문에 view로 선언되었습니다.
  * msg.sender의 contributions 값을 반환합니다.
* **withdraw 함수**
  * onlyOwner modifier가 붙어 있으므로, 컨트랙트 owner만 실행할 수 있습니다.
  * 현재 컨트랙트가 가지고 있는 ***모든 ether 잔고를 owner에게 전송합니다.***
* **fallback 함수**
  * payable로 선언되어 ether를 전송받을 수 있습니다.
  * msg.value(보낸 ether)가 0 초과이고, msg.sender(보낸 계정)의 contributions 값이 0 초과 일 때 ***owner를 msg.sender로 변경합니다.***

---

### 문제 풀이
#### 임무 완료 조건 다시 확인
  1. owner가 되어라. -> owner를 변경하는 코드가 있는 contribute, fallback을 살펴봐야 합니다.
  2. 컨트랙트의 ether 잔고를 0으로 만들어라. -> owner가 된 후 withdraw를 호출하면 잔고를 0으로 만들 수 있습니다.

#### owner가 되어 보겠습니다.
  * contribute 함수를 통해서 owner가 되려고 한다면, 엄청난 노가다가 필요합니다.
  * contribute에서 owner가 될 수 있는 조건은 현재 owner보다 contributions 값이 커져야 하는데, 생성자에서 최초 owner의 contributions을 1000 ether로 설정했습니다.
  * 그리고 contribute의 코드 실행 조건은 0.001 ether 미만을 전송해야 하기 때문에, 0.0009 ether를 1,120,000번 전송해야 contributions이 1,008이 되어  owner가 될 수 있습니다. 설마 이 방법은 아니겠죠...
  * 그러면 fallback 함수를 보겠습니다.
  * owner가 될 수 있는 조건은 0 초과 ether를 전송하고, contributions도 0 초과이면 가능합니다.
  * msg.value는 ether 전송시에 지정해 주면 되는데, contributions는 어떻게 해야 될까요?
  * contribute 함수를 다시 보면 owner를 얻지는 못하더라도 전송한 ether 만큼을 contributions에 더해줍니다.
  * 방법은! contribute 함수를 호출해서 contributions를 0 초과 값으로 만들어 놓은 상태에서, fallback 함수를 호출하면서 0 초과하는 ether를 전송하면 owner가 될 수 있습니다.
  * **contribute 함수 호출**
    * `contract.contribute.sendTransaction({value:toWei(0.0009)})` 를 콘솔창에서 실행하면, contribute 함수를 호출하면서 0.0009 ether를 전송합니다.
    * MetaMask 창이 뜨면 Submit을 눌러주면 됩니다. 채굴이 빨리 되게 하려면 gas price를 넉넉하게 줍니다.
    * ![11_call_contribute.png](https://cdn.steemitimages.com/DQmZ8pKrZUFQwWgGgwPTVa4AHrYTk2VE5dUnUTduwmC5nyd/11_call_contribute.png)
    * 가끔 채굴이 되었는데, Mined transation 이라는 메세지가 안나오는 경우가 있습니다. 그때에는 MetaMask에서 transation이 ...이 아니라 여러 색상이 칠해져 있는 동그라미가 나온다면 transaction이 처리 된 것입니다.
    * ![12_metamask_pending.png](https://cdn.steemitimages.com/DQmcCeGRvS3HXQHNaCu6Mo6rPmG6mK4e2P1fbwZ7MPMJU9M/12_metamask_pending.png)
    * 그러면 getContribution, getBalance 함수를 이용해서 정상적으로 ether가 전송되었는지 확인해 보겠습니다.
    * player의 contributions를 확인하기 위해 `fromWei(await contract.getContribution())`명령을 입력합니다.
    * getContribution은 기본적으로 wei 값을 반환하기 때문에 큰 숫자가 나옵니다. 그래서 ether로 표현하기 위해서 fromWei 함수를 이용했습니다. (help() 명령을 통해 확인할 수 있습니다.) contributions의 값이 0을 초과했습니다.
    * instance의 ether 잔고를 확인하기 위해 `await getBalance(instance)` 명령을 입력합니다.
    * getBalance를 호출해서 컨트랙트의 ether 잔고를 확인하면 contribute를 통해서 전송 받은 ether 만큼이 있습니다.
    * ![13_call_getContribution.png](https://cdn.steemitimages.com/DQmPkXrV8R1uGpf6hhrkEnsC29cFazBQKZrhC917fTeKYAg/13_call_getContribution.png)
  * **fallback 함수 호출**
    * fallback 함수 호출 조건을 만족 시키려면 0 초과의 ether를 전송해야 합니다.
    * fallback 함수는 어떻게 호출할 수 있을까요?
    * 가장 쉬운 방법은 그냥 MetaMask를 이용해서 instance address로 ether 전송 트랜잭션을 발생시키는 것입니다. (0 ether 전송도 가능합니다.)
    * MetaMask 창을 열어서 SEND 버튼을 누르면 Recipient Address, Amount를 넣을 수 있는 칸이 있습니다. 
    * Recipient Address에는 instance의 address를 Amount에는 전송하고자 하는 ether 량(wei가 아닌 ether 단위)을 적고 NEXT를 누릅니다. 저는 contribute에 전송량과 동일하게 0.0009 ether를 전송했습니다.
    * MetaMask에서 트랜잭션 처리가 완료 되었는지 확인한 후, owner를 확인해 보겠습니다.
    * `await contract.owner()`와 `player` 명령의 결과 값이 같다면, owner가 된 것입니다. (맨 처음 나왔던 player 주소와 다른 것은... 제가 여러 군데에서 작업을 해서 MetaMask 주소가 바껴서 그런 것입니다. 오해 없으시길 ^^;)
    * ![16_be_owner.png](https://cdn.steemitimages.com/DQmfNSpwiPL8SgbvckN8Eh3SPeBpvX776dd4DuSpVWH3D3v/16_be_owner.png)

#### instance의 ether 잔고를 0으로 만들겠습니다.
* 우선 `await getBalance(instance)` 명령으로 잔고가 얼마 있는지 확인해 보겠습니다.
* ![17_get_balance.png](https://cdn.steemitimages.com/DQmcNBUaFqvghExr6J8qrkDBPzeqHgEhPXtcs5thSCBR2FZ/17_get_balance.png)
* contribute 호출 시 0.0009 ether, fallback 호출 시 0.0009 ether를 전송 했으니 0.0018 ether가 있으면 정상입니다.
* owner가 되었으니 withdraw 함수를 호출할 수 있습니다.
* `contract.withdraw()` 명령을 실행하고, MetaMask로 트랜잭션을 발생시킵니다. 그리고 다시 한번 `await getBalance(instance)`명령을 통해서 잔고를 확인합니다.
* ![18_withdraw.png](https://cdn.steemitimages.com/DQmQgidhrJq21CKiMJijEvoJrHDtHv5cSeYttQUsHRf9Cwv/18_withdraw.png)

### 답안 제출
* 2가지 임무를 모두 완수 했으니 Submit instance 버튼을 눌러서 답안을 제출합니다.
* ![submit_instance](https://steemitimages.com/0x0/https://cdn.steemitimages.com/DQme3DtJ5HWZD8KAeDQ3hpWwoWJi5XGK7KQAvSzzhGDrZrQ/16_submit_instance.png)
* 트랜잭션을 발생시키고, 채굴이 완료되면 임무 완수 메세지가 나옵니다.
* ![19_complete.png](https://cdn.steemitimages.com/DQmTKm67qoCYK6qc2akAyD9k4LYuE6g5ybc7EwZGDXVwQLi/19_complete.png)

### 말하고자 하는 취약점
* contribute 함수 작성하면서 owner 권한 얻는 것을 굉장히 어렵고, 많은 비용이 들게 작성했다고 생각할 수 있습니다.
* 하지만 fallback 함수의 잘못 된 조건 때문에 적은 비용으로도 owner 권한을 얻을 수 있게 되어버려, 처음 의도했던 방향과 다르게 되어버렸습니다.
* 그래서 auditing이 필요하다~ 가 결론일 것 같네요.^^
* 최근에 문제가 되었던 [ICON의 ICX ERC-20 토큰 컨트랙트 코드](https://etherscan.io/address/0xb5a5f22694352c15b00323844ad545abb2b11028#code)에서도 사소한 부호 실수로 엄청난 불편을 일으키고 있습니다.
```
    modifier onlyFromWallet {
        require(msg.sender != walletAddress);
        _;
    }
```
* msg.sender가 walletAddress인 경우에만 실행할 수 있게 modifier를 만드려고 했는데, 부호를 잘못 사용하여 walletAddress을 제외한 모두(아무나) 접근할 수 있게 되어버렸습니다. 


문제를 풀 때랑 이걸 포스팅으로 작성할 때 들어가는 시간의 차이가 많이 나네요 ㅠㅠ
하루 한 문제를 예상하고 있었는데, 문제가 어려워지면 기간이 더 걸릴 수도 있을 것 같습니다.
어차피 공부 목적으로 작성 중이니 기간에 압박 받지 않고 열심히 올려보겠습니다.

감사합니다.
👍  , , , ,
properties (23)
authormodolee
permlinkethereum-ethernaut-1-fallback
categorykr-dev
json_metadata{"tags":["kr-dev","kr","ethereum","solidity","ethernaut"],"image":["https://steemitimages.com/DQmWQDjyP5d1RNW3mkCWUkDQ6yh3uDJFUZErPpaKBwKi4gM/iron_modolee.png","https://cdn.steemitimages.com/DQmdz6JkGkPMVfNEzysymiyfNbaVk2CZi1ad2GkxMUiopsv/00_mission.png","https://cdn.steemitimages.com/DQmSLhmM9W4STcWD9AJi7w56kivTbZTwWyGEYvnD4nq1XvH/01_get_new_instance.png","https://cdn.steemitimages.com/DQmbwJqGBPnzuM2v5C1UUhu9jqkoxYqjBZS5nU5xuTxFbyh/02_accounts_relation.png","https://cdn.steemitimages.com/DQmaKuGCopBaV5JPpxZbwBcZGuZsWCc7FoW549cKYn4EYZF/03_contract_all.png","https://cdn.steemitimages.com/DQmatNW5QTCWT4gmLP2xdZnk2vci7WUVq8mHaC7BJD2mwia/04_ownable.png","https://cdn.steemitimages.com/DQmP9StU7CGvvBpqH6YedoHmc98xzuWi85Kga5exoFQxDyx/04_01_ownable_openzeppeline.png","https://cdn.steemitimages.com/DQmVZvwyeXKUPdraiRzS2UXpf2LxWhxJ9TDT1yeaBWxM9ok/05_contructor.png","https://cdn.steemitimages.com/DQmcyrVm5bsGJgcsT8t6uxViPykPcGUBmR8LuUTFYcNTRKj/10_contract_owner.png","https://cdn.steemitimages.com/DQmZ8pKrZUFQwWgGgwPTVa4AHrYTk2VE5dUnUTduwmC5nyd/11_call_contribute.png","https://cdn.steemitimages.com/DQmcCeGRvS3HXQHNaCu6Mo6rPmG6mK4e2P1fbwZ7MPMJU9M/12_metamask_pending.png","https://cdn.steemitimages.com/DQmPkXrV8R1uGpf6hhrkEnsC29cFazBQKZrhC917fTeKYAg/13_call_getContribution.png","https://cdn.steemitimages.com/DQmfNSpwiPL8SgbvckN8Eh3SPeBpvX776dd4DuSpVWH3D3v/16_be_owner.png","https://cdn.steemitimages.com/DQmcNBUaFqvghExr6J8qrkDBPzeqHgEhPXtcs5thSCBR2FZ/17_get_balance.png","https://cdn.steemitimages.com/DQmQgidhrJq21CKiMJijEvoJrHDtHv5cSeYttQUsHRf9Cwv/18_withdraw.png","https://steemitimages.com/0x0/https://cdn.steemitimages.com/DQme3DtJ5HWZD8KAeDQ3hpWwoWJi5XGK7KQAvSzzhGDrZrQ/16_submit_instance.png","https://cdn.steemitimages.com/DQmTKm67qoCYK6qc2akAyD9k4LYuE6g5ybc7EwZGDXVwQLi/19_complete.png"],"links":["https://steemit.com/kr-dev/@modolee/ethereum-ethernaut","https://steemit.com/kr-dev/@modolee/ethereum-ethernaut-0-hello-ethenaut","https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/ownership/Ownable.sol","https://etherscan.io/address/0xb5a5f22694352c15b00323844ad545abb2b11028#code"],"app":"steemit/0.1","format":"markdown"}
created2018-06-19 02:04:42
last_update2018-06-19 02:09:42
depth0
children0
last_payout2018-06-26 02:04:42
cashout_time1969-12-31 23:59:59
total_payout_value0.098 HBD
curator_payout_value0.028 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length8,538
author_reputation285,192,678,959
root_title"[Ethereum] Ethernaut 풀이 - 1.Fallback"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id61,298,551
net_rshares60,320,936,722
author_curate_reward""
vote details (5)