国外服务器

[区块链安全-Ethernaut]区块链智能合约安全实战-连载中

准备

随着区块链技术的逐渐推广,区块链安全也逐渐成为研究的热点。在其中,又以智能智能合约安全最为突出。Ethernaut正是入门研究区块链智能合约安全的好工具。

  • 首先,应确保安装Metamask,如果可以使用Google Extension可以直接安装,否则可以使用FireFox安装
  • 新建账号,并连接到RinkeBy Test Network(需要在Setting – Advanced里启用Show test networks,并在网络中进行切换)
  • 访问Faucet并获取测试币,每天都有0.1Eth的额度

现在就可以开始Ethernaut的探索之旅了!


0. Hello Ethernaut

本节比较简单,所以我将更关注整体过程,介绍Ethernaut的实例创建等等,自己也梳理一下,所以会更详细一些。

准备工作

进入Hello Ethernaut,会自动提示连接Metamask钱包,连接后,示意图如下:

按F12打开开发者工具,在console界面就可以进行智能合约的交互。

创建实例并分析

单击 Get New Instance 以创建新的合约实例。

可以看出我们实际上是通过与合约0xD991431D8b033ddCb84dAD257f4821E9d5b38C33交互以创建实例。在辅导参数中,调用0xdfc86b17方法,附带地址为0x4e73b858fd5d7a5fc1c3455061de52a53f35d966作为参数。实际上,所有关卡创建实例时都会向0xD991431D8b033ddCb84dAD257f4821E9d5b38C33,附带的地址则是用来表明所处的关卡,如本例URL地址也为
https://ethernaut.openzeppelin.com/level/0x4E73b858fD5D7A5fc1c3455061dE52a53F35d966


实例已经成功生成,主合约交易截图如下:


进入交易详情,查看内部交易,发现合约之间产生了调用。第一笔是由主合约调用关卡合约,第二笔是由关卡合约创建合约实例,其中实例地址为0x87DeA53b8cbF340FAa77C833B92612F49fE3B822


回到页面来看,可以确认生成实例的确为0x87DeA53b8cbF340FAa77C833B92612F49fE3B822

下面我们将进行合约的交互以完成本关卡。

合约交互

此时,在console界面可以通过playercontract分别查看用户当前账户和被创建合约实例。player代表用户钱包账户地址,而contract则包含合约实例abiaddress、以及方法信息。


按照提示要求输入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(),得到infoNum参数值为42(Word中的首位)。这就是下一步要调用的函数(info42)。

输入await contract.info42(),得到结果'theMethodName is the name of the next method.,即下一步应当调用theMethodName


输入await contract.theMethodName(),得到结果'The method name is method7123949.


输入await contract.method7123949(),得到结果'If you know the password, submit it to authenticate().

所以通过password()可以获取密码ethernaut0,并将其提交到authenticate(string)

注意当在进行authenticate()函数时,Metamask会弹出交易确认,这是因为该函数改变了合约内部的状态(以实现对关卡成功的检查工作),而其他先前调用的函数却没有(为View)。

此时,本关卡已经完成。可以选择Sumbit Instance进行提交,同样要签名完成交易


在此之后,Console页面弹出成功提示,本关卡完成!

总结

本题比较简单,更多的是要熟悉ethernaut的操作和原理。


1. Fallback

创建实例并分析

根据先前的步骤,创建合约实例,其合约地址为0xe0D053252d87F16F7f080E545ef2F3C157EA8d0E
本关卡要求获得合约的所有权并清空余额
观察其源代码,找到合约所有权变更的入口。找到两个,分别是contribute()receive(),其代码如下:

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

按照contribute()的逻辑,当用户随调用发送小于0.001 ether其总贡献额超过了owner,即可获得合约的所有权。这个过程看似简单,但是通过以下constructor()函数可以看出,在创建时,owner的创建额为1000 ether,所以这种方法不是很实用。

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

再考虑receive()函数,根据其逻辑,当用户发送任意ether,且在此之前已有贡献(已调用过contribute()函数),即可获得合约所有权。receive()类似于fallback(),当用户发送代币但没有指定函数对应时(如sendTransaction()),会调用该方法。
在获取所有权后,再调用withdraw函数既可以清空合约余额。

合约交互

使用contract命令,查看合约abi及对外函数情况。


调用await contract.contribute({value:1}),向合约发送1单位Wei。


此时,调用await contract.getContribution()查看用户贡献,发现贡献度为1,满足调用receiver()默认函数的最低要求。


使用await contract.sendTransaction({value:1})构造转账交易发送给合约,

调用await contract.owner() === player 确认合约所有者已经变更。

最后调用await contract.withdraw()取出余额。

提交实例,显示关卡成功!

总结

本关卡也算比较简单,主要需要分析代码内部的逻辑,理解fallback()receive的原理。


[温馨提示:高防服务器能助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。]

[图文来源于网络,不代表本站立场,如有侵权,请联系高防服务器网删除]