区块链安全—实测UCN代码漏洞
字数 972 2025-08-22 12:22:30

区块链安全分析:UCN智能合约漏洞详解

一、项目背景

SaveUNICOINs是一个建立在以太坊上的DAPP应用,其核心特点包括:

  • 使用ERC20代币标准创建了名为"SaveUNICOINs"(UCN)的代币
  • 宣称用户可以通过充币来"喂养"虚拟独角兽
  • 承诺用户可以获得"全网广播"特权作为奖励
  • 实际运作模式是将用户资金转入特定钱包地址

二、合约架构分析

1. 基础ERC20合约

合约遵循标准的ERC20代币结构:

contract ERC20Basic {
    uint256 public totalSupply=100000000;
    function balanceOf(address who) constant returns (uint256);
    function transfer(address to, uint256 value);
    event Transfer(address indexed from, address indexed to, uint256 value);
}

contract ERC20 is ERC20Basic {
    function allowance(address owner, address spender) constant returns (uint256);
    function transferFrom(address from, address to, uint256 value);
    function approve(address spender, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

2. 代币实现合约

contract BasicToken is ERC20Basic {
    using SafeMath for uint256;
    mapping(address => uint256) balances;
    
    function transfer(address _to, uint256 _value) {
        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        Transfer(msg.sender, _to, _value);
    }
    
    function balanceOf(address _owner) constant returns (uint256 balance) {
        return balances[_owner];
    }
}

contract StandardToken is ERC20, BasicToken {
    mapping (address => mapping (address => uint256)) allowed;
    
    function transferFrom(address _from, address _to, uint256 _value) {
        var _allowance = allowed[_from][msg.sender];
        balances[_to] = balances[_to].add(_value);
        balances[_from] = balances[_from].sub(_value);
        allowed[_from][msg.sender] = _allowance.sub(_value);
        Transfer(_from, _to, _value);
    }
    
    function approve(address _spender, uint256 _value) {
        if ((_value != 0) && (allowed[msg.sender][_spender] != 0)) throw;
        allowed[msg.sender][_spender] = _value;
        Approval(msg.sender, _spender, _value);
    }
    
    function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
        return allowed[_owner][_spender];
    }
}

3. 项目特定合约

contract owned {
    address owner;
    
    modifier onlyOwner {
        if(msg.sender != owner) { throw; }
        _;
    }
}

contract UniContract is owned, StandardToken {
    // 代币信息
    string public constant name = "SaveUNICOINs";
    string public constant symbol = "UCN";
    uint256 public constant decimals = 0;
    
    // 资金接收地址
    address public multisig;
    address public founder;
    
    // 时间参数
    uint public start;
    uint public end;
    uint public launch;
    
    // 代币定价和销售状态
    uint256 public PRICE = 300000;
    uint256 public OVERALLSOLD = 0;
    uint256 public MAXTOKENSOLD = 85000000;
    
    // 构造函数
    function UniContract() onlyOwner {
        founder = 0x204244062B04089b6Ef55981Ad82119cEBf54F88;
        multisig= 0x9FA2d2231FE8ac207831B376aa4aE35671619960;
        start = 1507543200; // 10/09/17
        end = 1509098400;   // 10/27/2017
        launch = 1509534000; // 11/01/2017
        balances[founder] = balances[founder].add(15000000); // 创始人获得15%代币
    }
    
    // 多阶段销售参数
    uint256 public constant PRICE_PRESALE = 300000;
    uint256 public constant FACTOR_PRESALE = 38;
    // ... 其他阶段参数省略
    
    // 核心功能函数
    function submitTokens(address recipient) payable {
        if (msg.value == 0) { throw; }
        
        // 限定购买时间
        if((now > start && now < end) || now > launch) {
            uint256 tokens = msg.value.mul(PRICE).div(1 ether);
            
            if(tokens.add(OVERALLSOLD) > MAXTOKENSOLD) { throw; }
            
            // 预售上限检查
            if(((tokens.add(OVERALLSOLD)) > RANGEEND_PRESALE) && (now > start && now < end)) {
                throw;
            }
            
            OVERALLSOLD = OVERALLSOLD.add(tokens);
            balances[recipient] = balances[recipient].add(tokens);
            
            // 资金转入multisig地址
            if (!multisig.send(msg.value)) { throw; }
        } else {
            throw;
        }
    }
    
    // 提现函数(实际功能与描述不符)
    function submitEther(address recipient) payable {
        if (msg.value == 0) { throw; }
        if (!recipient.send(msg.value)) { throw; }
    }
    
    // 消息广播功能
    struct MessageQueue {
        string message;
        string from;
        uint expireTimestamp;
        uint startTimestamp;
        address sender;
    }
    
    uint256 public constant maxSpendToken = 3600; // 消息最长持续约1小时
    MessageQueue[] public mQueue;
    
    function addMessageToQueue(string msg_from, string name_from, uint spendToken) {
        if(balances[msg.sender]>spendToken && spendToken>=10) {
            if(spendToken>maxSpendToken) { spendToken=maxSpendToken; }
            
            UniCoinSize=UniCoinSize+spendToken;
            balances[msg.sender] = balances[msg.sender].sub(spendToken);
            
            uint expireTimestamp=now;
            if(mQueue.length>0) {
                if(mQueue[mQueue.length-1].expireTimestamp>now) {
                    expireTimestamp = mQueue[mQueue.length-1].expireTimestamp;
                }
            }
            
            mQueue.push(MessageQueue({
                message: msg_from,
                from: name_from,
                expireTimestamp: expireTimestamp.add(spendToken)+60,
                startTimestamp: expireTimestamp,
                sender: msg.sender
            }));
        }
    }
    
    // 喂养独角兽功能
    function feedUnicorn(uint spendToken) {
        if(balances[msg.sender]>spendToken) {
            UniCoinSize=UniCoinSize.add(spendToken);
            balances[msg.sender] = balances[msg.sender].sub(spendToken);
        }
    }
}

三、安全漏洞分析

1. 关键漏洞:安全函数失效

合约虽然使用了SafeMath库进行数学运算,但关键的安全检查被注释掉了:

// 在transferFrom函数中,本应存在的检查被注释掉
// if (_value > _allowance) throw;

这意味着:

  1. 转账授权检查被绕过
  2. 可能导致下溢/溢出漏洞
  3. 用户可以转移超过其授权额度的代币

2. 余额检查缺失

转账函数没有检查发送者是否有足够余额:

function transfer(address _to, uint256 _value) {
    balances[msg.sender] = balances[msg.sender].sub(_value);
    balances[_to] = balances[_to].add(_value);
    Transfer(msg.sender, _to, _value);
}

这可能导致:

  1. 余额下溢攻击
  2. 用户可以通过精心构造的交易创建大量代币

3. 提现函数误导

submitEther函数声称允许提现,但实际上只是将用户A的资金转给用户B:

function submitEther(address recipient) payable {
    if (msg.value == 0) { throw; }
    if (!recipient.send(msg.value)) { throw; }
}

这实际上不涉及合约资金的转移,只是简单的资金转发。

四、漏洞利用演示

攻击场景:下溢攻击

  1. 准备阶段

    • 部署合约
    • 用户A存入1 ether获取代币
  2. 授权阶段

    • 用户A调用approve授权用户B转账300001代币
  3. 攻击执行

    • 用户B调用transferFrom转移300001代币
    • 由于余额不足且安全检查缺失,导致下溢
  4. 结果验证

    • 用户A的余额变为极大值(下溢结果)
    • 攻击者成功利用漏洞获取大量代币

五、安全建议

  1. 完整实现SafeMath检查

    • 恢复所有被注释掉的安全检查
    • 确保所有数学运算都有边界检查
  2. 完善余额验证

    function transfer(address _to, uint256 _value) {
        require(balances[msg.sender] >= _value);
        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        Transfer(msg.sender, _to, _value);
    }
    
  3. 授权转账严格检查

    function transferFrom(address _from, address _to, uint256 _value) {
        require(balances[_from] >= _value);
        require(allowed[_from][msg.sender] >= _value);
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
        Transfer(_from, _to, _value);
    }
    
  4. 合约功能透明化

    • 明确说明资金流向
    • 避免功能描述与实际代码不符

六、相关资源

  1. 以太坊合约地址: 0xd3F5056D9a112cA81B0e6f9f47F3285AA44c6AAA
  2. 游戏官网: http://www.saveunicoins.com/
  3. 游戏介绍页面: http://www.saveunicoins.com/unicorn.html#currency

通过本案例分析,开发者可以学习到智能合约安全的关键要点,特别是算术运算安全检查和授权机制的正确实现方式。

区块链安全分析:UCN智能合约漏洞详解 一、项目背景 SaveUNICOINs是一个建立在以太坊上的DAPP应用,其核心特点包括: 使用ERC20代币标准创建了名为"SaveUNICOINs"(UCN)的代币 宣称用户可以通过充币来"喂养"虚拟独角兽 承诺用户可以获得"全网广播"特权作为奖励 实际运作模式是将用户资金转入特定钱包地址 二、合约架构分析 1. 基础ERC20合约 合约遵循标准的ERC20代币结构: 2. 代币实现合约 3. 项目特定合约 三、安全漏洞分析 1. 关键漏洞:安全函数失效 合约虽然使用了SafeMath库进行数学运算,但关键的安全检查被注释掉了: 这意味着: 转账授权检查被绕过 可能导致下溢/溢出漏洞 用户可以转移超过其授权额度的代币 2. 余额检查缺失 转账函数没有检查发送者是否有足够余额: 这可能导致: 余额下溢攻击 用户可以通过精心构造的交易创建大量代币 3. 提现函数误导 submitEther 函数声称允许提现,但实际上只是将用户A的资金转给用户B: 这实际上不涉及合约资金的转移,只是简单的资金转发。 四、漏洞利用演示 攻击场景:下溢攻击 准备阶段 : 部署合约 用户A存入1 ether获取代币 授权阶段 : 用户A调用 approve 授权用户B转账300001代币 攻击执行 : 用户B调用 transferFrom 转移300001代币 由于余额不足且安全检查缺失,导致下溢 结果验证 : 用户A的余额变为极大值(下溢结果) 攻击者成功利用漏洞获取大量代币 五、安全建议 完整实现SafeMath检查 : 恢复所有被注释掉的安全检查 确保所有数学运算都有边界检查 完善余额验证 : 授权转账严格检查 : 合约功能透明化 : 明确说明资金流向 避免功能描述与实际代码不符 六、相关资源 以太坊合约地址: 0xd3F5056D9a112cA81B0e6f9f47F3285AA44c6AAA 游戏官网: http://www.saveunicoins.com/ 游戏介绍页面: http://www.saveunicoins.com/unicorn.html#currency 通过本案例分析,开发者可以学习到智能合约安全的关键要点,特别是算术运算安全检查和授权机制的正确实现方式。