智能合约权限管理错误造成的漏洞比比皆是,应遵循权限最小化开发原则,在设计函数时就规划好可见性。

原文标题:《成都链安智能合约安全实践(一)| 功能实现千万条,权限安全第一条》
撰文:成都链安

古时候,有一位杰出的领袖名叫唐尧。

他所治理的地区人们安居乐业,但是他听闻隐士许由十分有才干,便萌生了将领导权让给许由的想法。但是许由拒绝了,并说出了这样一段话「鹪鹩巢于深林,不过一枝。」

至此,后人也用越俎代庖一词来表达越权的含义。

成都链安智能合约安全实践(一)| 功能实现千万条,权限安全第一条「越俎代庖」

在智能合约的实现中存在着访问权限,如果权限设置不合理,很容易造成智能合约被攻击,严重的还会造成巨大的经济损失。

成都链安-安全实验室对于智能合约安全有着丰富的经验和积累,但随着区块链技术越来越受重视,智能合约的数量也越来越多,随之而来的智能合约被攻击事件也越来越多,也让我们感受到了「让区块链更安全」的企业使命是多么的重要,但是一己之力难于对抗所有的威胁。

接下来,我们将会把自己的安全经验积累通过与智能合约 CTF 靶场 ethernaut 相结合,通过技术连载的方式向广大智能合约开发者普及在开发过程中,如何实现更安全的代码。

现在我们就来聊一聊 ethernaut 靶场的第一题 Fallback,代码函数「越俎代庖」的事件。

权限漏洞简介

越权漏洞是指在智能合约中,因函数可见性设置不合理或函数缺乏有效的验证导致本不能调用某一函数的用户通过直接或者绕过验证的方式成功调用该函数。

该漏洞可被单独利用,也可能结合其他漏洞进行组合攻击,利用方式简单,漏洞影响视存在漏洞的函数而定,可能对合约造成毁灭性打击。

Fallback「越俎代庖」漏洞原理详细分析

合约中的「俎」与「庖」

如何理解合约中的「俎」与「庖」呢?先来看一段合约代码,如下图所示:

成都链安智能合约安全实践(一)| 功能实现千万条,权限安全第一条图 1

这一段合约代码出自 ethernaut 靶场的第一题 Fallback。针对于靶场中的问题,解题思路是通过调用回调函数 function() payable public 来触发 owner = msg.sender;,使得合约的所有者变成调用者。

题目非常的简单,只要向此合约发起一笔交易,且满足 require 的条件就可触发 fallback 函数。

正常情况下,在对一个合约调用中,如果没有其他函数与给定的函数标识符匹配,或者没有提供附加数据,那么 fallback 函数会被执行。一般是作为转入以太币的默认操作。所以智能合约开发时一般是不需要将 owner = msg.sender 写到 fallback 函数中的。

如下图所示:

成都链安智能合约安全实践(一)| 功能实现千万条,权限安全第一条图 2

然而,在这里本不该被用户调用的 owner=msg.sender 被调用了,导致权限控制不当,产生了越权,「俎」与「庖」就这样发生了接下来的故事。

相关安全事件

Bancor 合约事件

2020 年 6 月 18 日,
Bancor network 0x5f58058c0ec971492166763c8c22632b583f667f
被爆出存在漏洞。

漏洞产生的原因是合约中存在一个 public 的 safeTransferFrom 方法,使得攻击者可以直接调用此方法授权给 Bancor network 合约的代币转出到任意账户。

其关于转账和授权的三个函数权限均为 public,这使得任何用户都能对其进行调用。本次事件涉及资金 50W 余美元。

详细代码如下图所示:

成都链安智能合约安全实践(一)| 功能实现千万条,权限安全第一条图 3

权限为 public 的 safeTransferFrom 方法这个「奸臣」并没有得到 Bancor 合约「国王」的许可,直接夺走了「国家」的「财政大权」。

幸而 Bancor network 团队和白帽首先发现了此问题,并对资金进行了转移。在后续也对该漏洞进行了修复,才得以避免损失。(详细分析见)

而同样的事件也在另外一个合约中上演,接下来我们将介绍 6 月底的 VETH 合约漏洞事件。

VETH 项目事件

2020 年 6 月 30,VETH 项目被爆出漏洞。本次事件中「越俎代庖」的主角则是合约中的 changeExcluded 函数的 external 修饰符。

external 修饰符使得任何人都可以调用 changeExcluded 函数来绕过 transferFrom 函数内部的授权转账额度检查,将合约的 VETH 代币盗走。

此次事件,攻击者利用此漏洞盗走 919299 个 VETH 后大量抛售,导致 VETH 代币价值瞬间流失。

详细见,涉及到的合约代码如下图所示:

成都链安智能合约安全实践(一)| 功能实现千万条,权限安全第一条图 4

通过以上两个案例,相信大家已经意识到了合约中「越俎代庖」事件的严重影响,那么如何在合约代码编写的过程中有效的区分「俎」与「庖」呢?

「俎」、「庖」信息大揭秘

针对越权事件,首先需要合约开发人员了解函数可见性。

函数的可见性,一共有 external、public、internal 和 private 四种:

External

外部函数作为合约接口的一部分,意味着我们可以从其他合约和交易中调用。一个外部函数 f 不能从内部调用(即 f 不起作用,但 this.f() 可以)。当收到大量数据的时候,外部函数有时候会更有效率,因为数据不会从 calldata 复制到内存 .

Public

public 函数是合约接口的一部分,可以在内部或通过消息调用。对于 public 状态变量, 会自动生成一个 getter 函数(见下面)。

Internal

这些函数和状态变量只能是内部访问(即从当前合约内部或从它派生的合约访问),不使用 this 调用。

Private

private 函数和状态变量仅在当前定义它们的合约中使用,并且不能被派生合约使用。

开发人员在构造一个函数时,应当遵循这些可见性进行开发,要明确哪些函数是可以由用户调用的「俎」,而哪些又是合约中不能任意替代的「庖」,以最小原则进行分配。

比如一个函数 safeTransfer 在设计时是用于转账操作的,用户可以通过调用此函数,转账此合约发行的代币。我们使用 public 和 external 都可以满足需求,但就安全的角度,我们应当使用 external,避免合约内对此函数进行调用,造成不可预期的风险。

如存在有一个 safeTransferFrom 函数,用户可以通过授权给此合约其他代币,将其他代币转移到一个指定地址的,当_token 等于合约本身时,就会以合约本身的身份调用 sadeTransfer 函数,即而将合约内的钱转到其他地址。

成都链安智能合约安全实践(一)| 功能实现千万条,权限安全第一条图 5

然而只是遵循函数可见性是远远不够的,函数的可见性,只是区分了合约内部、继承合约和外部这三个界限,远远不能满足我们的需求。

想要达到较为完善的权限管理,我们应当引入「角色」的概念,如:管理员、普通用户、特权用户等。在合约中存储这些角色的地址,通过判断地址或标志变量来进行权限的控制。

如下图所示:

成都链安智能合约安全实践(一)| 功能实现千万条,权限安全第一条图 6

通过修饰器对这些不同的「角色」进行管理,如使用 onlyOwner 修饰器,限制特定的地址才可调用此函数。对应「角色」的权限管理。使用修饰器的方式,可以更加清晰的判断出是否存在纰漏。

安全总结

就链上现状来看,智能合约权限管理错误造成的漏洞比比皆是,其中不乏很多「著名项目」,而此类漏洞造成的损失也是巨大的。

成都链安安全团队依据多年合约审计和链上分析安全经验给出以下几点建议:

  1. 遵循权限最小化开发原则,在设计函数时应当就规划好可见性。
  2. 建立角色机制,使用修饰器对各函数进行权限管理,避免纰漏。
  3. 上线前一定找专业机构做好代码审计,正所谓「一人一个脑,做事没商讨;十人十个脑,办法一大套」。

来源链接:mp.weixin.qq.com