构造函数负责智能合约所有者 owner 等数据的初始化,正确使用构造函数对智能合约安全至关重要。

原文标题:《成都链安智能合约安全实践(二)| 练就「火眼金睛」,真假构造函数一眼看清》
撰文:成都链安

在《西游记》中,六耳猕猴冒充猴王孙悟空,以假乱真,骗过了唐僧,骗过了众神,纵使是照妖镜也分不出真假。现在,智能合约遇上了「六耳猕猴」,又会擦出怎样的火花?

智能合约安全实践:开发者如何正确使用构造函数?

在智能合约中,构造函数负责一些数据的初始化工作,owner 值一般也会放在构造函数中进行初始化。owner 是智能合约拥有者的称呼,也常被用来作为该合约的超级管理员。对代币合约来说,owner 可能被分配的权限有:铸造 / 销毁代币、冻结代币等。如果开发者以错误的语法创建「构造函数」,造成构造函数缺失,致使「六耳猕猴」以假乱真,瞒过了开发者,最后使得攻击者成为合约的拥有者(owner),那么攻击者便可依赖 owner 的权限,对代币进行增发或销毁等操作,进而可能造成整个代币的崩盘。

构造函数简介

在 Solidity 语言中,当函数名和合约名相同时,此函数就是合约的构造函数,在合约对象创建时,会先调用构造函数对相关的数据进行初始化。

以太坊 Solidity 0.4.22 版本中引入了关键字 constructor,新的构造函数声明形式:constructor() public { },引入的目的是用以替代低版本中将合约名作为构造函数名的语法形式,从而避免开发者笔误造成构造函数命名错误的问题。引入的这个关键字看似平淡无奇,实则意蕴深刻,且听我慢慢道来。

Fal1out「以假乱真?」– 漏洞分析

下面以 ethernaut 靶场的 Fallout 题目为例进行分析。一眼看去,这似乎是一个正常没有漏洞的合约代码,但经过仔细观察发现,该合约存在一个致命错误——构造函数名称与合约名称不一致,Fallout 合约的构造函数被写错成了 Fal1out (字母 l 和数字 1 的差异),这样的错误使其成为了一个被 public 修饰的普通函数,失去了构造函数仅在合约部署时被调用的特性,使得任何人都可以调用。该题目源码如下图所示:

智能合约安全实践:开发者如何正确使用构造函数?图 1

在 Fal1out 函数中直接指定了函数调用者的地址即为 owner,所以只需要调用 Fal1out 函数即可实现对合约 owner 的更改。如下图所示:

智能合约安全实践:开发者如何正确使用构造函数?图 2

「假猴王」Fal1out 想借着一些字体类型的相似字符的视觉差异混淆视听,可最终还是没能逃过我们的「火眼金睛」。

前车之覆:MorphToken 事件分析

在过去也曾发生过类似的安全事件,包含着假构造函数的合约被成功发布到主链上,其中比较出名的是「MorphToken 事件」,其因为一个看似很小的问题而造成了数千万市值的代币被增发。(合约代码地址)

在 Owned 合约中,由于首字母大小写的错误,导致本该成为构造函数的 Owned 成为了普通函数 owned,且被 public 修饰,可供任何人调用。如下图所示:

智能合约安全实践:开发者如何正确使用构造函数?图 3

MorphToken 合约继承了 Owned 合约,并在自己的构造函数内进行了 owner 的初始化,但是父合约 Owned 的 owned 函数是可供任何人调用的,攻击者便可通过调用 owned 函数更改合约的所有者 owner。owner 的初始化代码如下图所示:

智能合约安全实践:开发者如何正确使用构造函数?图 4

由上述可知,任何人都可以通过调用合约的 owned 函数,成为合约的拥有者 (owner)。如下图所示:

智能合约安全实践:开发者如何正确使用构造函数?图 5

失之毫厘,差之千里,一个小小的字母错误,却导致了合约的代币的崩盘。代币也被恶意增发。如下图所示:

智能合约安全实践:开发者如何正确使用构造函数?图 6

后车之鉴:开发者应如何正确使用构造函数

建议更换 Solidity 0.4.22 及以上版本,并使用正确的 constructor() 语法。如下图所示:

智能合约安全实践:开发者如何正确使用构造函数?图 7

切记: constructor() 前并无 function,function constructor() public { }为错误的构造函数形式。

如果要使用低于 0.4.22 的版本,则一定要着重检查函数名是否和合约名一致。如下图所示:

智能合约安全实践:开发者如何正确使用构造函数?图 8

安全建议

在智能合约中因开发者粗心,而造成安全漏洞的事件层出不穷,「千里之堤,溃于蚁穴」,成都链安-安全实验室在此给出如下建议:

  1. 开发者在编写智能合约敏感函数(如构造函数、回退函数 fallback)时,应严格按照官方要求的代码书写规范,注意不要出现字符错误等情况。
  2. 在某些情况下,编译器会对 constructor 的错误使用发出警告,开发者应予以正确对待,不可认为其只是警告信息而忽略不处理。
  3. 在合约正式上线前一定要找专业可信的机构做好合约代码的审计工作。