区块链安全—实测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;
这意味着:
- 转账授权检查被绕过
- 可能导致下溢/溢出漏洞
- 用户可以转移超过其授权额度的代币
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);
}
这可能导致:
- 余额下溢攻击
- 用户可以通过精心构造的交易创建大量代币
3. 提现函数误导
submitEther函数声称允许提现,但实际上只是将用户A的资金转给用户B:
function submitEther(address recipient) payable {
if (msg.value == 0) { throw; }
if (!recipient.send(msg.value)) { throw; }
}
这实际上不涉及合约资金的转移,只是简单的资金转发。
四、漏洞利用演示
攻击场景:下溢攻击
-
准备阶段:
- 部署合约
- 用户A存入1 ether获取代币
-
授权阶段:
- 用户A调用
approve授权用户B转账300001代币
- 用户A调用
-
攻击执行:
- 用户B调用
transferFrom转移300001代币 - 由于余额不足且安全检查缺失,导致下溢
- 用户B调用
-
结果验证:
- 用户A的余额变为极大值(下溢结果)
- 攻击者成功利用漏洞获取大量代币
五、安全建议
-
完整实现SafeMath检查:
- 恢复所有被注释掉的安全检查
- 确保所有数学运算都有边界检查
-
完善余额验证:
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); } -
授权转账严格检查:
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); } -
合约功能透明化:
- 明确说明资金流向
- 避免功能描述与实际代码不符
六、相关资源
- 以太坊合约地址: 0xd3F5056D9a112cA81B0e6f9f47F3285AA44c6AAA
- 游戏官网: http://www.saveunicoins.com/
- 游戏介绍页面: http://www.saveunicoins.com/unicorn.html#currency
通过本案例分析,开发者可以学习到智能合约安全的关键要点,特别是算术运算安全检查和授权机制的正确实现方式。