CVE-2018-17405漏洞预警及OpenZepplin详解

2018-10-11  本文已影响36人  看雪学院

如何获得OpenZepplin?

OpenZeppelin 可以使用 npm install --save-exact openzeppelin-solidity 直接安装到现有的 node.js 项目中;

OpenZeppelin也可以直接与以太网开发环境 Truffle(https://github.com/trufflesuite/truffle) 集成,我们将从这里开始。

安装 Truffle 并初始化工程:

CVE-2018-17405

A smart contract implementation for Bitron Coin (BTO),

an Ethereum token, uses a raw form of math operations. The minus operation could trigger a integer overflow on the contract owner’s balance, leading to a very large balance, as exploited in the wild in September 2018.

然后在 Solidity 项目的根目录中运行以下命令:

$ npm init -y

$ npm install --save-exact openzeppelin-solidity

之后就可以得到 node_modules / openzeppelin-solidity / contracts文件夹中的所有库合约了。由于Truffle和其他以太坊开发工具包都兼容 node_modules,所以可以直接使用库中的合约,如下所示:

import 'openzeppelin-solidity/contracts/ownership/Ownable.sol';

contract MyContract is Ownable {

...

}

安装完成之后,我们从哪里入手开始学习OpenZepplin呢?

我们接下来按照顺利聊聊:

了解访问控制所有者权限

了解众筹

了解代币

了解一下其他工具

当然,最好的学习方式肯定是去看官方guide,链接是这个: 

https://blog.zeppelin.solutions/guides/home

官方指南里有一篇《The Hitchhiker’s Guide to Smart Contracts in Ethereum》

(https://blog.zeppelin.solutions/the-hitchhikers-guide-to-smart-contracts-in-ethereum-848f08001f05)

里面会介绍可以用于智能合约开发的各种工具,一直到环境搭好。

《A Gentle Introduction to Ethereum Programming》 Part 1和part 2(https://blog.zeppelin.solutions/a-gentle-introduction-to-ethereum-programming-part-1-783cc7796094)介绍了很多以太坊平台的许多基本概念,作为入门很不错。

如果想要更深入的了解,还可以阅读《Designing the architecture for your Ethereum application》指南(https://blog.zeppelin.solutions/designing-the-architecture-for-your-ethereum-application-9cec086f8317),该指南讨论如何更好地将Dapp和现有互联网架构融合到一起。

当然最后也可以寻求帮助或follow社区 Slack 中 OpenZeppelin (https://slack.openzeppelin.org/)的进展,多看看官网总没错。

控制所有者权限,什么是访问控制呢?

访问控制的意思就是“谁被允许做这件事” ——在智能合约领域非常重要。

合约的访问控制,管理谁可以发行代币、谁可以对合约进行投票、谁可以自行修改合约等等,因此了解如何进行合约的访问控制非常重要。

那我们为什么要用到访问控制这个功能呢?

智能合约一旦发布到以太坊上,发布者就对该合约失去了控制,这是由solidity的语言特性决定的,solidity没有实现这个持续控制的功能;

由于语言缺乏该功能,所以有些程序猿在自己实现该功能的时候,写的不对甚至错误,闹出来很多问题。

比如韩国国家级区块链项目ICON(ICX)智能合约代码2018年6月被爆代码存在安全漏洞,使transfer功能失效。虽然黑客无法利用这次漏洞盗币,但该漏洞会导致包括转账、交易等重要功能无法正常使用。

在ICX的合约中,有一个功能可以开启/关闭合约的转账功能。最初,此功能的设计是只有ICX合约的所有者拥有调用它的权限;由于代码写错了逻辑运算符号,导致除了合约所有者之外的任何人都能随意开启和关闭该合约的转账功能。其错误的代码语句为require(msg.sender != walletAddress);,其中本该为“==”符合却打成了“!=”,使得功能恰恰相反。

通过在合约的函数尾部加上onlyOwner的修饰符,合约的发布者可以持续控制该合约的所有权、转让权等等;这就是访问控制最基本的功能:Ownership 和Ownable.sol

OpenZeppelin 提供 contracts/ownership/Ownable.sol 以实现合约中的所有权。

import "openzeppelin-solidity/contracts/ownership/Ownable.sol";

contract MyContract is Ownable {

function normalThing()

public

{

//任何人可以调用这个normalThing()函数

}

function specialThing()

public

onlyOwner

{

// 只有权限的所有者(一般是创建人msg.sender)可以调用这个函数!

}

}

Ownable 还允许:

transferOwnership(地址newOwner)将所有权从一个帐户转移到另一个帐户

renounceOwnership()完全删除所有者,对分散控制合约很有用。

警告! 完全删除所有者意味着受 onlyOwner 保护的管理任务将不再可调用!

请注意,任何支持发送交易的合约可以是合约的所有者; 唯一的要求是所有者拥有以太坊地址,因此它可能是 Gnosis Multisig 或 Gnosis Safe,Aragon DAO,ERC725 / uPort 身份合约或您创建的完全自定义的任何合约。

通过这种方式,可以自由发挥的方式就很多了。 一般可以用项目导向的多重签名钱包来作为Owner,而不是将单个以太坊离线账户(EOA)作为所有者。

不过好像看到OpenZepplin库里面的合约,都不怎么用这个Ownable。

蒽蒽,据官网介绍,没有一个 OpenZeppelin 合约使用 Ownable ! 这是因为提供更灵活的访问控制方式才符合OpenZepplin可重用合约理念。对于大多数合约,OpenZepplin使用角色来管理谁可以做什么。在某些情况下,哪怕存在着主从关系,OpenZepplin也会使用Secondary.sol来创建一个“辅助”合约,允许“主要”合约来管理它。

我来稍微介绍一下OpenZepplin独创的角色的访问控制吧Role-Based Access Control(RBAC)吧

单一Ownable的替代方案是基于角色的访问控制(RBAC),它并不记录具有管理级别权限的单个实体,而是记录具有各种角色的多个不同实体,合约会基于角色的权限来判断行为。

这就类似于 Web 开发过程中的,绝大多数访问控制系统都是基于角色的:一些用户是普通用户,一些是管理员,一些用户可以是公司员工管理员。

例如,MintableToken 可以有一个 minter 角色,决定谁可以发行代币(可以分配给 Crowdsale )。它还可以具有名称角色,允许更改代币的名称或符号(无论出于何种原因)。RBAC 为您提供了更多的灵活性,而不是谁可以做什么,并且通常建议用于需要更多可配置性的应用程序。

OpenZeppelin提供了contract/access/Roles.sol,用于实现基于角色的访问控制。

以下是在上面的代币示例中使用 Roles 的示例,我们将使用它来实现可由 Minters 创建并由 Namers 重命名的代币:

import "openzeppelin-solidity/contracts/access/rbac/Roles.sol";

contract MyToken is DetailedERC20, StandardToken {

using Roles for Roles.Role;

Role private minters;

Role private namers;

constructor(

string name,

string symbol,

uint8 decimals,

address[] minters,

address[] namers,

)

DetailedERC20(name, symbol, decimals)

Standardtoken()

public

{

namers.addMany(namers);

minters.addMany(minters);

}

function mint(address to, uint256 amount)

public

{

// only allow minters to mint

require(minters.has(msg.sender), "DOES_NOT_HAVE_MINTER_ROLE");

_mint(to, amount);

}

function rename(string name, string symbol)

public

{

// only allow namers to name

require(namers.has(msg.sender), "DOES_NOT_HAVE_NAMER_ROLE");

name = name;

symbol = symbol;

}

}

那我们按照官方教程,先从众筹说起~~

众筹大家都知道,也就是ICO,央行发文要求取缔的就是这个。在上一节课中,我们已经讲了众筹的核心就是项目方发行自己的代币,来换取参与者手中的以太坊。代币值不值钱不知道,以太坊可是真金白银。

众筹的形式千差万别,但是大体上都可以归类为以下几种:

1. 价格和汇率

    是否以固定价格出售代币?

     价格会随着时间的推移或需求的变化而变化吗?

2.分发

     代币如何分发给参与者?

3.客户验证 - 谁可以购买代币?

     是否有KYC / AML检查?

     代币有最大限额吗?

     每个参与者的最多可以买多少?

     是否有开始和结束的大概时间?

4.众筹结果分配

     结果分配是实时发生还是在众筹之后发生?

     如果没有达到目标,参与者可以获得退款吗?

为了管理所有不同的组合,OpenZeppelin提供了一个高度可配置的Crowdsale.sol基本合约,可以与各种其他功能比如代币相结合,项目方可以按照要求来随机搭配组合。

我们分别来看看其中的几个注意点:

首先是汇率:

        代币的汇率是非常重要的,维持汇率的稳定非常重要;

        谁也不会参与一个汇率不稳的众筹项目;

        然后是代币的分发,代币的分发有以下几种形式:

        默认形式:众筹合约拥有代币,谁给合约汇钱,合约就把代币发给谁;

        MintedCrowdsale :众筹合约先收到钱,再发行相应数量的代币;

        AllowanceCrowdsale :众筹合约收到钱后,第三方地址会把代币发给参与者;

在默认形式下,众筹合约需要拥有代币,然后可以通过下面的代码将代币发送给参与者:

IERC20(tokenAddress).transfer(CROWDSALE_ADDRESS, SOME_TOKEN_AMOUNT);

当然,别忘了填上自己的收款钱包,否则ether进不了你的账户:

new Crowdsale(

1, // rate in TKNbits

MY_WALLET, // 自己的钱包

TOKEN_ADDRESS// 代币合约的地址

);

在MintedCrowdsale情况下,要使用 MintedCrowdsale,您的代币也必须是 ERC20Mintable 的一种,然后众筹合约还要有权限来Mint才行,比如来看下面的代码:

contract MyToken is ERC20, ERC20Mintable {

// ... see "Learn About Tokens" for more info

}

contract MyCrowdsale is MintedCrowdsale, Crowdsale {

constructor(

uint256 rate,// rate in TKNbits

address wallet,

ERC20 token

)

MintedCrowdsale()

Crowdsale(rate, wallet, token)

public

{

}

}

constract MyCrowdsaleDeployer {

constructor()

public

{

// create a mintable token

ERC20Mintable token = new MyToken();

// create the crowdsale and tell it about the token

Crowdsale crowdsale = new Crowdsale(

1, // rate, still in TKNbits

msg.sender,// send Ether to the deployer

address(token),// the token

);

// transfer the minter role from this contract (the default)

// to the crowdsale, so it can mint tokens

token.transferMinterRole(address(crowdsale));

}

}

AllowanceCrowdsale的情况下,代币会从第三方钱包发送给众筹的参与者。为了实现这一点,第三方钱包必须通过 ERC20 approve(...)方法来批准这个操作。

contract MyCrowdsale is AllowanceCrowdsale, Crowdsale {

constructor(

uint256 rate,

address wallet,

ERC20 token,

address tokenWallet// <- new argument

)

AllowanceCrowdsale(tokenWallet)  // <- used here

Crowdsale(rate, wallet, token)

public

{

}

}

然后在创建众筹之后,代币合约要进行approve操作。

IERC20(tokenAddress).approve(CROWDALE_ADDRESS, SOME_TOKEN_AMOUNT);

接下来是客户验证,分为以下几种情况:

CappedCrowdsale - 有各种限制的众筹,任何超过该限制的购买无效

IndividualuallyCappedCrowdsale - 限制个人的购买数量

WhitelistedCrowdsale - 仅允许列入白名单的参与者购买代币。这对于将KYC / AML白名单放在链上非常有用!

TimedCrowdsale - 添加一个 openingTime 和 closingTime 到你的crowdsale

以上这几种形式是可以搭配组合的:

contract MyCrowdsale is CappedCrowdsale, TimedCrowdsale, Crowdsale {

constructor(

uint256 rate, // rate, in TKNbits

address wallet, // wallet to send Ether

ERC20 token,// the token

uint256 cap,// total cap, in wei

uint256 openingTime,// opening time in unix epoch seconds

uint256 closingTime // closing time in unix epoch seconds

)

CappedCrowdsale(cap)

TimedCrowdsale(openingTime, closingTime)

Crowdsale(rate, wallet, token)

public

{

// nice, we just created a crowdsale that's only open

// for a certain amount of time

// and stops accepting contributions once it reaches `cap`

}

}

最后就是众筹结果的分配了!每一场众筹最后肯定都要分配结果,不知道大家有没有参与过EOS的众筹,它就是在众筹结束之后,才进行代币的分配。

当然默认的众筹是参与者会立即获得代币,但是有时候用户万一会要退款怎么办呢?或者如果众筹没有达到预定的金额,想要退款给用户的情况呢?再或者我们想要等到众筹结束再一次性把代币发给用户呢?

OpenZepplin考考虑到了这些所有的情况,我们以以下两种情况举例:

PostDeliveryCrowdsale

PostDeliveryCrowdsale,正如它字面的意思,分配代币 crowdsale 完成后,让用户调用withdrawTokens()来获得他们所购买的代币。

contract MyCrowdsale is PostDeliveryCrowdsale, TimedCrowdsale, Crowdsale {

constructor(

uint256 rate, // rate, in TKNbits

address wallet, // wallet to send Ether

ERC20 token,// the token

uint256 openingTime,// opening time in unix epoch seconds

uint256 closingTime // closing time in unix epoch seconds

)

PostDeliveryCrowdsale()

TimedCrowdsale(startTime, closingTime)

Crowdsale(rate, wallet, token)

public

{

// nice! this Crowdsale will keep all of the tokens until the end of the crowdsale

// and then users can `withdrawTokens()` to get the tokens they're owed

}

}

RefundableCrowdsale

这种众筹方式允许,在众筹没有达到最低目标时,用户可以通过claimRefund()来获得退款。

contract MyCrowdsale is RefundableCrowdsale, Crowdsale {

constructor(

uint256 rate, // rate, in TKNbits

address wallet, // wallet to send Ether

ERC20 token,// the token

uint256 goal// the minimum goal, in wei

)

RefundableCrowdsale(goal)

Crowdsale(rate, wallet, token)

public

{

// nice! this crowdsale will, if it doesn't hit `goal`, allow everyone to get their money back

// by calling claimRefund(...)

}

}

由于时间的原因,今天就到这里,我们会有下一期的哈。

原文作者:roysue

原文链接:https://bbs.pediy.com/thread-247203.htm

转载请注明:转自看雪学院

更多阅读:

1、[原创]Flare-on5 12题 suspicious_floppy writeup

2、[原创] ratel,让xposed模块在免root的环境下跑起来

3、[原创]反编译原理(2)-中间表示

4、[原创]代码混淆之我见(一)

上一篇下一篇

猜你喜欢

热点阅读