前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >合约安全之-变量隐藏安全问题分析

合约安全之-变量隐藏安全问题分析

作者头像
Tiny熊
发布2022-11-07 09:54:43
6730
发布2022-11-07 09:54:43
举报
文章被收录于专栏:深入浅出区块链技术

本文作者:小驹[1]

1. 原理

在计算机编程中,当在特定范围(代码块、方法或内部类)中声明的变量与在外部范围中声明的变量具有相同的名称时,就会发生变量隐藏。变量隐藏在多种计算机语言中都存在,并不仅仅是 Solidity 语言独有的特性。

我们把这种同一个合约可能存在多个具有相同名称的变量,这种变量称为影子变量。

  1. 在更复杂的合约系统中,这种情况可能不会引起注意,并且随后可能引起某些安全问题。
  2. 当在合约和函数层级存在多个定义时,影子变量也可能在单个合约内发生。

Solidity 支持继承,继承的引入可能会给 Solidity 的状态变量进行影子变量的问题。如想像一下这种场景:

带有变量 x 的合约 A 可以继承同样定义了状态变量 x 的合约 B,这将导致 x 变量 的两个不同版本。其中一个从合约 A 访问,另一个从合约 B 访问。

这种场景下极容易出现变量隐藏的安全问题。

在 Solidity 编码中,变量隐藏常出现的场景包括:

  1. 同一个合约中,不同特定作用范围的变量。
  2. 继承关系的多个合约中,不同合约中具有相同名称的变量。

在编写更复杂的合约系统时,要时刻注意上面的两种场景,可能会由于忽视并随后导致变量隐藏安全问题。对于这两种场景分别用下面的代码进行演示。

2. 代码演示

演示 1:不同特定作用范围的变量

代码语言:javascript
复制
pragma solidity 0.4.24;

contract ShadowingInFunctions {
    uint n = 2;
    uint x = 3;

    function test1() constant returns (uint n) {
        return n; // Will return 0
    }

    function test2() constant returns (uint n) {
        n = 1;
        return n; // Will return 1
    }

    function test3() constant returns (uint x) {
        uint n = 4;
        return n+x; // Will return 4
    }
}

上面的三个函数的输出内容为:

代码语言:javascript
复制
instance deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
test1:0
test2:1
test3:4

在这段代码中,涉及到变量 n 的地方有多个,总结起来代码中变量 n 有三种变量类型,分别是:

  • 状态变量 n 。这个变量的值为 2。
  • 返回值类型的 n 。在 test1 函数和 test2 函数中,n 都是返回值类型的 n.
  • 局部变量的 n 。在 test3 函数中,定义了 n 的局部变量,这个局部变量的值为 n。

在 test1 和 test2 函数中,返回值类型的 n 覆盖了合约的状态变量 n。所以函数返回的内容都是返回值类型的 n 的值。

在 test3 函数中,位于函数内的局部变量 n 覆盖了合约状态变量 n。所以函数返回的内容是局部变量计算出来的 n 的值。

演示 2:继承合约中状态变量的隐藏

可以分析下面的代码,看能否看出哪里有问题?

演示代码 Children.sol:

代码的本意是:Children 合约继承自 Base 合约,同时 Children 合约的 withdraw 取款操作具有 onlyOwner 修饰符,期望withdraw取款操作只能由Children合约的部署者才能调用。

代码语言:javascript
复制
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.4.0;

import "hardhat/console.sol";

contract Base {
    address public owner;
    modifier onlyOwner() {
        require(msg.sender==owner,"Only Owner can call the function");
        _;
    }
}
contract Children is Base {
    uint public totalSupply = 100;
    address public owner;

    constructor() public {
        owner = msg.sender;
        console.log("\\nChildren constructor:%s",owner);

    }

    function withdraw(uint _amount) public onlyOwner   {
        totalSupply = totalSupply - _amount;
        // console.log(“aaa”);
    }
}

下面是 attack.ts 模拟的测试过程,通过 npx hardhat run script/attack.ts 进行测试。模拟的操作为:

  1. 使用user1部署Children合约,并输出合约部署的地址,合约的 owner,totalSupply 信息。
  2. 使用user1调用合约的withdraw方法
  3. 查看 totalSupply 的值,验证 user1 的 withdraw 方法调用成功。
代码语言:javascript
复制
import { ethers, waffle } from "hardhat";

async function main() {
    let user1, user2;
    [user1, user2] = await ethers.getSigners();
    const Children = await ethers.getContractFactory("Children",user1);
    const children = await Children.deploy();

    await children.deployed();
    console.log("deploy at:%s", children.address);
    console.log("contract owner:%s", await children.owner());
    console.log("totalSupply:%s", await children.totalSupply());

    await children.connect(user1).withdraw(10);

    console.log("totalSupply:%s", await children.totalSupply());

}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

在 user1 调用 withdraw 方法时,却失败了。错误提示如下:

0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 地址是 Children 合约的部署者,却无法调用 withdraw 方法。

返回源代码分析,Base 合约和 Children 合约都具有 owner 状态变量,却只有 Base 合约有 onlyOwner 修饰符。

  • 对 Base 合约:没有对 owner 进行赋值(owner 默认为 0x0000000000000000000000000000000000000000),却直接定义了 onlyOwner 的修饰符。
  • 对 Children 合约:对 owner 进行赋值(owner 默认为 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266),却没有直接定义 onlyOwner 的修饰符(直接继承使用了 Base 合约的 onlyOwner 的修饰符)。

这样的结果就是导致调用Children合约的 withdraw 方法时,使用的是Base合约的 onlyOwner 的修饰符,而 Base 合约的 onlyOwner 的修饰符检查的自然是Base合约的owner,也就是 0x0000000000000000000000000000000000000000。因此就导致了 Children 合约无法调用 withdraw()方法。

所以说,能调用 Children 合约 withdraw()方法的地址只能是 0x0000000000000000000000000000000000000000 黑洞地址。

本质上还是因为父子合约中都使用了 owner 变量,使 owner 合约变成了影子变量,程序员在实现功能时混淆了 owner 变量的指代。

理解了上面的原因,很简单的两种修改方法:

给 Base 合约加上 owner 的赋值。

代码语言:javascript
复制
constructor() public {
        owner = msg.sender;
        console.log("\\nBase constructor:%s",owner);
    }

给 Children 合约加上 OnlyOwner 修饰符。

代码语言:javascript
复制
modifier onlyOwner() {
        require(msg.sender==owner,"Only Owner can call the function");
        _;
    }

3. 安全建议

变量隐藏常出现的两类场景:

  1. 同一个合约中,不同特定作用范围的变量
  2. 继承关系的多个合约中,不同合约中具有相同名称的变量。

对于场景 1, 在开发环境中,编辑器会提示如下的影子变量的风险。如在代码开发过程中,遇到下面的提示,建议移除影子变量或重命名影子变量。

对于 场景 2,编译器不会有影子变量的提示,更需要依赖于仔细检查您的合约代码的存储变量的定义,尽量消除任何歧义。

4.参考

Smart Contract Weakness Classification and Test Cases 中对变量隐藏的解释

https://swcregistry.io/docs/SWC-119[2]

参考资料

[1]

小驹: https://learnblockchain.cn/people/9625

[2]

https://swcregistry.io/docs/SWC-119: https://swcregistry.io/docs/SWC-119

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-06-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深入浅出区块链技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 原理
  • 2. 代码演示
    • 演示 1:不同特定作用范围的变量
      • 演示 2:继承合约中状态变量的隐藏
      • 3. 安全建议
      • 4.参考
        • 参考资料
        相关产品与服务
        腾讯云代码分析
        腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档