您当前的位置: 首页 > 

MateZero

暂无认证

  • 2浏览

    0关注

    92博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

一种绕过管理员权限调用智能合约view函数的小技巧

MateZero 发布时间:2021-06-27 22:54:14 ,浏览量:2

在部分以太坊智能合约中,有时可以看到某些view函数被限定了管理员调用,以阻止其它人查看。其实这个限定基本上是无效的,本文介绍一下绕过这个限定调用相应的view函数的小技巧。注意:我们这里仅限view类型的函数(非改变状态的交易)。

一、介绍

有的时候,智能合约开发者由于需求,会将部分数据设置为私有类型,并且限定了只有管理员才能查看(例如一个随机数的种子,或者管理员地址)。这时我们又想知道这个数据怎么办?正常的函数调用肯定是无法验证调用者地址验证的,这里,我们就得想想办法了,利用ethers.js框架中调用智能合约时指定的overrides对象可以轻松绕过地址验证,从而将不可见信息变成可见信息。

当然有读过我上一篇《使用ethers.js直接读取智能合约中插槽内容》的读者会说到,我们可以直接读取插槽信息!Very Good!我们当然要利用这个技巧,但是有部分数据不方便使用插槽直接读取(例如mapping),有些数据还要经过复杂的计算和第三方调用,此时利用读取插槽来获取足够多的信息最终得到结果就显得有些笨拙了,我们需要一种更简单的方式。

二、编写测试合约

我们编写一个测试合约,源码如下:

pragma solidity =0.6.6;

contract ViewTest {
    address private owner;
    uint private _seed;
    mapping(address => uint) private luckyNum;
    mapping(address => uint) private luckyNumTwo;

    modifier onlyOwner() {
        require(msg.sender == owner, "no access");
        _;
    }

    constructor() public {
        owner = msg.sender;
        _seed = block.number;
        luckyNum[msg.sender] = uint(msg.sender) % 1000;
        luckyNum[address(0)] = 666;
        luckyNumTwo[msg.sender] = uint(msg.sender) % 10000;
        luckyNumTwo[address(0)] = 6666;
    }

    function getMyAddress() external view returns(address) {
        return msg.sender;
    }

    function getLuckyNum() external view returns(uint) {
        return luckyNum[msg.sender];
    }

    function getLuckyNumTwo(address user) external view onlyOwner returns(uint) {
        return luckyNumTwo[user];
    }

    function getSeed() external view onlyOwner returns(uint) {
        return _seed;
    }

    function setSeed(uint seed) external onlyOwner returns(bool) {
        _seed = seed;
        return true;
    }
}

该合约已经部署在Kovan测试网上,合约地址为0x2B4FCa37e5FE788ecE0BFC997a91a8993C176Ed5,并且已经通过浏览器验证,使用下面的链接可以直接查看代码: https://kovan.etherscan.io/address/0x2B4FCa37e5FE788ecE0BFC997a91a8993C176Ed5#code

测试合约很简单,主要是将部分私有状态变量的读取(view类型函数)限定为owner调用(作为对比,部分view类型函数未限定调用权限),注意,这个owner本身也是私有的。

三、overrides对象

ethers.js框架中,调用合约的函数时可以添加一个自定义的overrides对象,这个对象其中有这么一个属性:from,用来指定调用者的地址。如果我们不写这个overrides对象,这个调用者地址就是零地址(当然,如果在ether.js中将合约绑定了钱包,默认调用者地址就是钱包地址)。我们平常在调用合约时指定的gasLimitgasPrice也是属于这个overrides对象的,from字段我们很少用到。

于是,如果我们得到了owner地址,就可以将from字段设置成owner地址来指定调用者地址。乍一看,我们没有别人的私钥怎么能指定调用者为别人呢?记住,我们这里的主题说的是调用view函数(并不是一个改变状态的交易),是不需要签名的,因此,也就可以通过验证。

四、编写测试脚本

我们编写一个viewTest.js来进行上述技巧的验证。

//导入ethers
const {ethers,utils} = require("ethers")  
//连接infura节点
const url = "https://kovan.infura.io/v3/your_infura_key"
const provider = new ethers.providers.JsonRpcProvider(url,"kovan")
//创建合约实例
const address = "0x2B4FCa37e5FE788ecE0BFC997a91a8993C176Ed5"
const abi = [
    "function getLuckyNum() view returns(uint)",
    "function getSeed() view returns(uint)",
    "function getMyAddress() view returns(address)",
    "function getLuckyNumTwo(address user) view returns(uint)",
]
const contract = new ethers.Contract(address,abi,provider)

//获取不指定调用者账号时的地址,为零地址
async function getMyAddress() {
    const my_address = await contract.getMyAddress()
    console.log(my_address === ethers.constants.AddressZero)  //true
}

//获取零地址对应幸运数字,应该为666
async function getLuckyNum() {
    const n = await contract.getLuckyNum()
    console.log("luckyNum:", n.toString())   //666
}

//获取账号对应的幸运数字,因为权限限定,会报错。
async function getLuckyNumTwo() {
    try{
        const n = await contract.getLuckyNumTwo(ethers.constants.AddressZero)
        console.log("luckyNumTwo:", n.toString())   
    }catch(e) {
        console.log("getLuckyNumTwo error")
    }
}

//使用管理员地址去调用
async function getLuckyNumTwoByOwner(owner_address) {
    const n = await contract.getLuckyNumTwo(owner_address,{
        from:owner_address
    })
    console.log("luckyNumTwo:", n.toString())  
}

//获取私密数字_seed,因为权限限定,应该报错。
async function getSeed() {
    try {
        const seed = await contract.getSeed()
        console.log("seed:",seed.toString())
    }catch(e) {
        console.log("getSeed error")
    }
}

//使用管理员地址去调用
async function getSeedByOwner(owner_address) {
    const seed = await contract.getSeed({
        from:owner_address
    })
    console.log("seed:",seed.toString())  //blockNumber 25753852
}

//直接从插槽获取owner地址
async function getOwnerBySlot() {
    const owner_info = await provider.getStorageAt(address ,0)  //owner在零插槽
    const owner_address = utils.getAddress("0x" + owner_info.substring(26))
    console.log("owner_address:",owner_address)
    return owner_address
}

//直接从插槽获取seed值,绕过onlyOwner
async function getSeedBySlot() {
    const seed_info = await provider.getStorageAt(address ,1)  //seed在1插槽
    const seed = ethers.BigNumber.from(seed_info)
    console.log("seed:", seed.toString())  //blockNumber 25753852
}

//获取其它插槽信息,主要是演示不是所有数据都方便通过插槽获取(比如中间有复杂的计算,比如map等)
async function getOtherSlotInfo() {
    for(let i=2;i            
关注
打赏
1648304347
查看更多评论
0.0367s