首页
社区
课程
招聘
CVE-2018-17405漏洞预警及OpenZepplin详解(上) ​​​​
发表于: 2018-10-10 10:59 5510

CVE-2018-17405漏洞预警及OpenZepplin详解(上) ​​​​

2018-10-10 10:59
5510

问01:首先介绍一下您发现的这个漏洞吧?

近日看雪区块链小组成员roysue@kanxue在对Bitron Coin智能合约的代码进行审计的过程中,发现其存在着整数溢出型漏洞,看雪学院第一时间上报漏洞并发布预警威胁情报。

 

漏洞详情如下:

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.

 

这其实就是一个整数溢出漏洞,一个小朋友,可以数着手指,计算10以内的加法。如果问小朋友,1+1等于几,他会扳手指算出来2;如果问他6+5,他扳完10个手指头后发现不够了,又扳回来说是1。因为对小朋友来说,问题已经超纲“溢出”了。

问02:这个漏洞会造成多大的影响?

漏洞造成的影响很大,曾经美链就是因为这个洞,价值一夜归零

 

https://baijiahao.baidu.com/s?id=1598546560560765681&wfr=spider&for=pc

 

大家可以看这个,BEC(美链币):一行代码蒸发60亿,原理跟本次漏洞是一模一样的

问03:看来这个问题不容小觑,厂商应该怎样修补这个漏洞,以及如何避免该漏洞再次发生?

https://bbs.pediy.com/thread-247145.htm 就像文章中介绍的一样,所有的加减乘除操作都应该使用 SafeMath库来进行

 

漏洞的修补方式:规避整型溢出的神器——SafeMath库
由于目前Solidity还未解决此问题,所以只能由各个合约自行完成整型溢出的判断,这里给出一个规避的建议:无论在任何时候都不要直接使用”+”、”-“、”*”、”/“数学运算符,而改用使用SafeMath库来进行数学运算。此库对所有数学运算都做了防溢出判断,可有效杜绝整型溢出的问题。

 

SafeMath 在各个运算的函数开头使用来 require 来进行判断,从而有效地规避了整型溢出问题。实际上,正常编译合约的时候,使用加减乘除都应该进行溢出判断,而openzeppelin的这个SafeMath帮我们实现了这个功能,从而我们直接使用此库的所有数学运算即可,省去了繁琐的溢出判断。

问04:OpenZepplin是一家公司麽?

OpenZepplin其实不是一家公司,它是由Zepplin公司开源的一套solidity基础库,Zepplin公司是专门做审计的 可以说是开发智能合约的老司机,他们见过太多人 犯过太多的低级错误,当然这里面也有solidity的坑 ,比如说这个整数溢出,这其实应该在语言层面就解决掉的问题 ,大家一般都会有bigmath的库,但是solidity就是没有,所以Zepplin推出了一个基础库,叫做OpenZepplin,提供一些基础的组件,供大家搭配使用.

问05:那我们如何获得OpenZepplin呢?

  • OpenZeppelin 可以使用 npm install --save-exact openzeppelin-solidity 直接安装到现有的 node.js 项目中;
  • OpenZeppelin 也可以直接与以太网开发环境 Truffle(https://github.com/trufflesuite/truffle) 集成,我们将从这里开始。

安装 Truffle 并初始化工程:

$ npm install -g truffle
$ mkdir myproject && cd myproject
$ truffle init

然后在 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 {
  ...
}

问06:安装完成之后,我们从哪里入手开始学习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/)的进展,多看看官网总没错。

<font color=red> 问07:好的,那我们来看下访问控制所有者权限吧,什么是访问控制呢?</font>

  • 访问控制的意思就是“谁被允许做这件事” ——在智能合约领域非常重要。
  • 合约的访问控制,管理谁可以发行代币、谁可以对合约进行投票、谁可以自行修改合约等等,因此了解如何进行合约的访问控制非常重要。

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

  • 智能合约一旦发布到以太坊上,发布者就对该合约失去了控制,这是由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)作为所有者。

问09:不过好像看到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;
    }
}

问10:说了这么多我听不懂的,其实我只是想了解一下发币和众筹...

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

 

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

 

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

  • 价格和汇率
    • 是否以固定价格出售代币?
    • 价格会随着时间的推移或需求的变化而变化吗?
  • 分发
    • 代币如何分发给参与者?
  • 客户验证 - 谁可以购买代币?
    • 是否有KYC / AML检查?
    • 代币有最大限额吗?
    • 每个参与者的最多可以买多少?
    • 是否有开始和结束的大概时间?
  • 众筹结果分配
    • 结果分配是实时发生还是在众筹之后发生?
    • 如果没有达到目标,参与者可以获得退款吗?

为了管理所有不同的组合,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(...)
    }
}


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


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//