智能合约安全杂谈(一)合约后门的函数写法总结(上)
文章指出多个Solidity合约中的后门函数允许合约拥有者或特定地址在未经代币持有者同意的情况下销毁其代币,存在不公平和安全漏洞。 2025-9-2 01:0:0 Author: www.freebuf.com(查看原文) 阅读量:0 收藏

图片

function burn(address _from, uint256 _unitAmount) onlyOwner public {
    require(_unitAmount > 0 && balanceOf(_from) >= _unitAmount);
    balances[_from] = SafeMath.sub(balances[_from], _unitAmount);
    totalSupply = SafeMath.sub(totalSupply, _unitAmount);
    Burn(_from, _unitAmount);
}

流程: 1、首先使用了onlyOwner来表明只有函数所有者才能调用 2、传入_unitAmount值大于0,并且传入一个任意的地址,当地址上的余额大于传入的值则可以正常执行 3、执行sub函数,将目标地址的余额进行减少 4、更新totalSupply 总铸币量 5、触发Burn事件 6、因此合约拥有者可以任意销毁任意用户的代币,而不用经过他人同意。

图片

function burnFrom(uint256 _value, address victim) onlyOwner canMint public{
    require(_value <= balances[victim]);
    balances[victim] = balances[victim].sub(_value);
    totalSupply_ = totalSupply().sub(_value);
    emit Burn(victim, _value);
}

流程: 1、首先使用了onlyOwner来表明只有函数所有者才能调用 2、传入_value值和目标victim地址,要求传入的值必须比目标victim地址上拥有的代币少 3、同样的可以直接减少目标地址上拥有的代币 4、然后查看canMint修饰符,当require为true时则执行,!mintingFinished,因此minitingFinished需要为False。

图片

modifier canMint() {
    require(!mintingFinished);
    _;
}

5、因此合约拥有者可以任意减少地址的代币数量

图片

function burnTokens(address _from, uint _value) onlyIcoContract {
    assert(_from != 0x0);
    require(_value > 0);
    balances[_from] = sub(balances[_from], _value);
}
modifier onlyIcoContract() {
    require(msg.sender == icoContract);
    _;
}

流程: 1、使用onlyIcoContract修饰符修饰方法,可以看到调用者必须为icoContract地址 2、因此我们需要查找icoContract地址,查看代码 里面存在两个burnTokens函数 第一个和之前的burnTokens函数一样 第二个如下,我们暂时先看第二个

图片

function burnTokens(address _from, uint _value) onlyManager notMigrated {
    cartaxiToken.burnTokens(_from, _value);
}

主要关注以下两个修饰符notMigrated和onlyManager

modifier notMigrated() {
    require(currentState != State.Migrated);
    _;
}
modifier onlyManager() {
    require(msg.sender == icoManager);
    _;
}

3、对notMigrated和onlyManager进行分析,当前状态不等于State.Migrated状态(迁移状态),调用者地址必须为icoManager

4、 当当前状态不等于State.Migrated状态(迁移状态),调用者地址为管理者时,可以调用cartaxiToken.burnTokens(_from, _value);函数,cartaxiToken就是之前的合约地址,此时执行第一个合约地址上的burnTokens函数。

5、可以查看交易明细

6、这个后门比较绕,可以简单总结下: icoManager地址可以调用icoContract合约(第二个合约)上的burnTokens函数,当icoContract.burnTokens函数满足状态部为State.Migrated状态(迁移状态)时,就会调用第一个合约的burnTokens,从而销毁任意用户的代币。传入内容:用户地址不为0,代币数量大于0。

图片

function destroy(address _from, uint256 _amount) public {
    _checkMyAging(_from);
    require((msg.sender == _from && allowManuallyBurnTokens) || msg.sender == owner);
    require(accountBalance(_from) >= _amount);
    balances[_from] = safeSub(balances[_from], _amount);
    _totalSupply = safeSub(_totalSupply, _amount);
    Transfer(_from, this, _amount);
    Destruction(_amount);
}

流程: 1、该方法是公开可访问的,接受地址和数量两个参数 2、使用_checkMyAging方法来检查传入的地址,询问GPT,说这个方法通过传入地址来判断这个地址上的代币,处理老化的代币。 3、需要满足条件:条件1:allowManuallyBurnTokens为true,并且合约的调用者为是否和_from(想销毁的地址)一致,或者条件2:合约调用者是owner,即合约调用者是不是合约的所有者。 4、这就导致条件1:自我销毁代币合情合理,但是条件2:却导致合约的owner可以操控任意地址用户的代币,例如:销毁地址用户的所有代币,导致任意地址用户的代币为他人所操控。

图片

function destroyTokens(address _owner, uint _amount) onlyController returns (bool) {
    uint curTotalSupply = totalSupply();
    require(curTotalSupply >= _amount);
    uint previousBalanceFrom = balanceOf(_owner);
    require(previousBalanceFrom >= _amount);
    updateValueAtNow(totalSupplyHistory, curTotalSupply - _amount);
    updateValueAtNow(balances[_owner], previousBalanceFrom - _amount);
    Transfer(_owner, 0, _amount);
    return true;
}
modifier onlyController {
    require(msg.sender == controller);
    _;
}

流程: 1、使用修饰符onlyController修饰函数,传入地址_owner和_amount数量值 2、curTotalSupply获取总的铸币量,_amout值需要小于总的铸币量。需要销毁的地址的代币量要大于要销毁的量。 3、两个updateValueAtNow更新总的铸币量和对方地址上的代币数量,然后发送一个事件Transfer(_owner, 0, _amount); 这行代码通常用于通知监听者,从 _owner 地址销毁了 _amount 数量的代币,这里的0一般是接收者的地址,但是设置为0就代表为销毁——将金币转到不存在的地址上。 4、控制者就是 5、同样的这个后面也是可以销毁任意的代币

总结:上面的几个后门合约都是类似的,控制者可以任意销毁任何人的代币数量,对其他的代币持有者非常的不公平


文章来源: https://www.freebuf.com/articles/vuls/446962.html
如有侵权请联系:admin#unsafe.sh