MSG Chain 安全审计清单与常见漏洞指南
数据来源:MSG Chain 代码库核实
主网状态: No-Go — 当前 MSGChain 主网裁决为 No-Go,以下内容反映代码实际状态,不代表生产可用。
适用链: MSG Chain (基于Cosmos SDK + DAR Consensus + Dilithium-5)
适用合约: CosmWasm 智能合约
目录
1. 安全审计概述
1.1 MSG Chain 安全模型
MSG Chain 是基于 Cosmos SDK 构建的 Layer 1 应用链,采用以下核心安全机制:
| 安全层 | 技术实现 | 安全等级 |
|---|---|---|
| 共识层 | DAR (Dynamic Authority Rotation) 共识 | 中 - 高 |
| 签名层 | Dilithium-5 后量子密码签名 | 高 (PQ安全) |
| 存储层 | BadgerDB (键值存储) | 中 |
| 智能合约 | CosmWasm (wasmd) | 中 - 高 |
| 跨链 | IBC (规划阶段) | 待审计 |
1.2 威胁模型
┌─────────────────────────────────────────────────┐
│ 攻击者能力 │
├─────────────────────────────────────────────────┤
│ 🟢 链上交易监控 (Mempool监听) │
│ 🟡 有限计算资源 (非量子计算机) │
│ 🟡 控制部分验证者集 (少于104/96阈值) │
│ 🔴 无法攻破 Dilithium-5 (后量子安全) │
│ 🔴 无法伪造 DAR 共识 │
└─────────────────────────────────────────────────┘
信任假设:
- 验证者集诚实比例 > 2/3
- Dilithium-5 签名算法安全
- CosmWasm 虚拟机沙箱隔离有效
- 链升级治理过程安全
不信任假设:
- 所有用户输入不可信
- 外部合约调用不可信
- 价格预言机来源不可信
- IBC 中继器不可信(跨链启用后)
1.3 后量子密码学优势
MSG Chain 采用 Dilithium-5 签名算法,相比 ECDSA/EdDSA 提供:
抗量子攻击 Dilithium-5 ✅ | ECDSA ❌ | EdDSA ❌
签名大小 4595 bytes (Dilithium-5) | 64-72 bytes (ECDSA) | 64 bytes (EdDSA)
公钥大小 2592 bytes (Dilithium-5) | 32-33 bytes (ECDSA) | 32 bytes (EdDSA)
验证速度 较慢 (3-5x ECDSA) | 快 | 快
安全影响:
- 所有交易签名具有量子抗性
- 重放攻击保护依赖于 nonce + ChainID 双重机制
- 签名验证 gas 成本高于 ECDSA,需在合约中注意 gas 限制
1.4 错误码参考映射
审计过程中可能遇到的系统级错误码:
| 错误码 | 名称 | 说明 | 安全含义 |
|---|---|---|---|
| 3 | UNAUTHORIZED | 未授权操作 | 权限检查失败 |
| 4 | INSUFFICIENT_FUNDS | 余额不足 | 资金验证 |
| 5 | CONTRACT_EXECUTION_FAILED | 合约执行失败 | 通用执行错误 |
| 7 | INVALID_PARAMETER | 无效参数 | 输入验证 |
| 10 | AGENT_NOT_FOUND | Agent 未找到 | AI Agent 不存在 |
| 12 | CONSTITUTION_VIOLATION | 违反 Agent 宪法 | 行为约束违规 |
| 15 | INVALID_SIGNATURE | Dilithium-5 签名无效 | 签名验证失败 |
| 16 | DUPLICATE_NONCE | 重复 Nonce | 重放攻击检测 |
| 17 | RATE_LIMIT_EXCEEDED | 超出门限 | 速率限制 |
2. CosmWasm 常见漏洞
本章涵盖 20 种在 CosmWasm 合约中发现的典型安全漏洞。每个漏洞均包含:漏洞描述、脆弱代码示例、攻击场景、修复代码及测试用例。
VULN-01: 重入攻击 (Reentrancy)
描述
CosmWasm 的 reply 机制可能被利用进行重入攻击。与 EVM 不同,CosmWasm 默认不支持同步调用重入,但通过 SubMsg + Reply 模式,攻击者可以在 reply 处理函数中重新进入合约状态。
类似 The DAO 重入攻击(2016年,360万 ETH 被盗),攻击者利用合约在状态更新前调用了外部合约。
脆弱代码
// ⚠️ 脆弱版本 — 存在重入风险
use cosmwasm_std::{
entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError,
StdResult, SubMsg, Reply, BankMsg, Coin, Uint128, CosmosMsg,
to_binary,
};
use cw_storage_plus::Item;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct State {
pub balances: Uint128,
pub locked: bool,
}
const STATE: Item<State> = Item::new("state");
#[entry_point]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> StdResult<Response> {
match msg {
ExecuteMsg::Withdraw { amount } => withdraw(deps, env, info, amount),
// ...
}
}
pub fn withdraw(deps: DepsMut, _env: Env, info: MessageInfo, amount: Uint128) -> StdResult<Response> {
let state = STATE.load(deps.storage)?;
if amount > state.balances {
return Err(StdError::generic_err("Insufficient balance"));
}
// ⚠️ 漏洞:在状态更新前发送资金
let bank_msg = BankMsg::Send {
to_address: info.sender.to_string(),
amount: vec![Coin::new(amount.u128(), "umsg")],
};
// ⚠️ 使用 SubMsg 允许 reply 钩子
let sub_msg: SubMsg = SubMsg::new(bank_msg);
// ❌ 状态更新在消息发送之后!
STATE.save(deps.storage, &State {
balances: state.balances.checked_sub(amount)?,
locked: state.locked,
})?;
Ok(Response::new()
.add_submessage(sub_msg)
.add_attribute("action", "withdraw")
.add_attribute("amount", amount))
}
攻击场景
- 攻击者部署恶意合约,在
reply处理函数中重新调用withdraw - 当受害合约发送资金后、更新状态前,
reply触发再次提取 - 由于状态尚未更新,
state.balances仍然包含原始余额 - 攻击者可重复提取直到 gas 耗尽或达到栈深度限制
修复代码
// ✅ 修复版本 — Checks-Effects-Interactions 模式
pub fn withdraw(deps: DepsMut, _env: Env, info: MessageInfo, amount: Uint128) -> StdResult<Response> {
let state = STATE.load(deps.storage)?;
if amount > state.balances {
return Err(StdError::generic_err("Insufficient balance"));
}
// ✅ 首先更新状态 (Effects)
let new_balance = state.balances.checked_sub(amount)?;
STATE.save(deps.storage, &State {
balances: new_balance,
locked: state.locked,
})?;
// ✅ 然后发送资金 (Interactions)
let bank_msg = BankMsg::Send {
to_address: info.sender.to_string(),
amount: vec![Coin::new(amount.u128(), "umsg")],
};
// ✅ 使用 ReplyOn::Never 防止额外的 reply 处理
use cosmwasm_std::ReplyOn;
let sub_msg = SubMsg {
id: 0,
msg: CosmosMsg::Bank(bank_msg),
gas_limit: None,
reply_on: ReplyOn::Never,
};
Ok(Response::new()
.add_submessage(sub_msg)
.add_attribute("action", "withdraw")
.add_attribute("amount", amount))
}
正式测试用例
#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::{
mock_dependencies, mock_env, mock_info,
};
use cosmwasm_std::{coins, from_binary, Attribute};
#[test]
fn test_secure_withdraw_prevents_reentrancy() {
let mut deps = mock_dependencies();
let env = mock_env();
let owner = String::from("owner");
STATE.save(deps.as_mut().storage, &State {
balances: Uint128::new(1000),
locked: false,
}).unwrap();
let info = mock_info(&owner, &coins(0, "umsg"));
let res = withdraw(deps.as_mut(), env, info, Uint128::new(500)).unwrap();
let state = STATE.load(deps.as_ref().storage).unwrap();
assert_eq!(state.balances, Uint128::new(500));
let env2 = mock_env();
let info2 = mock_info(&owner, &coins(0, "umsg"));
let reentry_result = withdraw(deps.as_mut(), env2, info2, Uint128::new(600));
assert!(reentry_result.is_err());
assert_eq!(
reentry_result.err().unwrap().to_string(),
StdError::generic_err("Insufficient balance").to_string()
);
}
}
VULN-02: 未授权访问 (Unauthorized Access)
描述
最常见的 CosmWasm 漏洞 — 缺少适当的权限检查。类似 PolyNetwork 跨链桥被黑事件(2021年,6.1亿美元损失),管理员函数缺少调用者验证。
脆弱代码
// ⚠️ 脆弱版本 — 缺少权限检查
use cosmwasm_std::{
entry_point, DepsMut, Env, MessageInfo, Response, StdError, StdResult,
};
use cw_storage_plus::Item;
const OWNER: Item<String> = Item::new("owner");
const TOTAL_SUPPLY: Item<Uint128> = Item::new("total_supply");
#[derive(Serialize, Deserialize)]
pub enum ExecuteMsg {
Mint { to: String, amount: Uint128 },
Burn { from: String, amount: Uint128 },
}
#[entry_point]
pub fn execute(deps: DepsMut, _env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
match msg {
ExecuteMsg::Mint { to, amount } => {
// ❌ 未检查调用者是否为合约所有者
let mut supply = TOTAL_SUPPLY.load(deps.storage)?;
supply = supply.checked_add(amount)?;
TOTAL_SUPPLY.save(deps.storage, &supply)?;
Ok(Response::new()
.add_attribute("action", "mint")
.add_attribute("to", to)
.add_attribute("amount", amount))
}
ExecuteMsg::Burn { from, amount } => {
// ❌ 未检查授权
let mut supply = TOTAL_SUPPLY.load(deps.storage)?;
supply = supply.checked_sub(amount)?;
TOTAL_SUPPLY.save(deps.storage, &supply)?;
Ok(Response::new()
.add_attribute("action", "burn")
.add_attribute("from", from)
.add_attribute("amount", amount))
}
}
}
修复代码
// ✅ 修复版本 — 所有权检查
fn assert_owner(deps: &DepsMut, sender: &Addr) -> StdResult<()> {
let owner = OWNER.load(deps.storage)?;
if sender.as_str() != owner.as_str() {
return Err(StdError::generic_err("Unauthorized: only owner can call"));
}
Ok(())
}
#[entry_point]
pub fn execute(deps: DepsMut, _env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
match msg {
ExecuteMsg::Mint { to, amount } => {
assert_owner(&deps, &info.sender)?;
let mut supply = TOTAL_SUPPLY.load(deps.storage)?;
supply = supply.checked_add(amount)
.map_err(|_| StdError::generic_err("Overflow"))?;
TOTAL_SUPPLY.save(deps.storage, &supply)?;
Ok(Response::new()
.add_attribute("action", "mint")
.add_attribute("to", &to)
.add_attribute("amount", amount))
}
ExecuteMsg::Burn { from, amount } => {
let owner = OWNER.load(deps.storage)?;
if info.sender.as_str() != owner.as_str() && info.sender.as_str() != from.as_str() {
return Err(StdError::generic_err("Unauthorized"));
}
let from_addr = deps.api.addr_validate(&from)?;
let mut supply = TOTAL_SUPPLY.load(deps.storage)?;
supply = supply.checked_sub(amount)
.map_err(|_| StdError::generic_err("Underflow"))?;
TOTAL_SUPPLY.save(deps.storage, &supply)?;
Ok(Response::new()
.add_attribute("action", "burn")
.add_attribute("from", from)
.add_attribute("amount", amount))
}
}
}
VULN-03: 整数溢出/下溢 (Integer Overflow/Underflow)
描述
CosmWasm 中 Rust 的整数运算在 debug 模式下会 panic 溢出,但在 release 模式下会 静默回绕。使用 Uint128 / Uint256 等类型时需注意。
类似 BatchOverflow 漏洞(2018年,多代币受影响),攻击者利用算术溢出绕过余额检查,铸造无限代币。
脆弱代码
// ⚠️ 脆弱版本 — 使用原始 u128 算术
pub fn unsafe_transfer(
deps: DepsMut,
sender: String,
recipient: String,
amount: u128,
) -> StdResult<Response> {
// ❌ 使用原始 u128,release 模式下可能回绕
let sender_balance = BALANCES
.load(deps.storage, &sender)
.unwrap_or(0u128);
let recipient_balance = BALANCES
.load(deps.storage, &recipient)
.unwrap_or(0u128);
// ❌ 减法可能下溢(release 模式下变成超大数)
let new_sender = sender_balance - amount;
// ❌ 加法可能上溢
let new_recipient = recipient_balance + amount;
BALANCES.save(deps.storage, &sender, &new_sender)?;
BALANCES.save(deps.storage, &recipient, &new_recipient)?;
Ok(Response::new().add_attribute("action", "unsafe_transfer"))
}
修复代码
// ✅ 修复版本 — 使用 checked 算术
use cosmwasm_std::Uint128;
use cw_storage_plus::Map;
const BALANCES: Map<&Addr, Uint128> = Map::new("balances");
pub fn safe_transfer(
deps: DepsMut,
sender: Addr,
recipient: Addr,
amount: Uint128,
) -> StdResult<Response> {
let sender_balance = BALANCES
.load(deps.storage, &sender)
.unwrap_or(Uint128::zero());
let new_sender = sender_balance
.checked_sub(amount)
.map_err(|_| StdError::generic_err("Insufficient balance"))?;
let recipient_balance = BALANCES
.load(deps.storage, &recipient)
.unwrap_or(Uint128::zero());
let new_recipient = recipient_balance
.checked_add(amount)
.map_err(|_| StdError::generic_err("Recipient balance overflow"))?;
BALANCES.save(deps.storage, &sender, &new_sender)?;
BALANCES.save(deps.storage, &recipient, &new_recipient)?;
Ok(Response::new()
.add_attribute("action", "safe_transfer")
.add_attribute("amount", amount))
}
VULN-04: 存储碰撞与迁移漏洞 (Storage Collision in Migrations)
描述
CosmWasm 使用扁平键值存储。合约迁移时,如果新版本的存储键与旧版本冲突,可能导致数据损坏或权限提升。
类似 Parity 多签钱包库自杀漏洞 — 存储布局不兼容导致 50 万 ETH 被锁。
脆弱代码
// ⚠️ v1 合约 — 原始存储布局
const OWNER: Item<String> = Item::new("owner");
const BALANCES: Map<&Addr, Uint128> = Map::new("balances");
// ⚠️ v2 合约升级 — 存储碰撞!
const ADMIN: Item<String> = Item::new("owner"); // ❌ 与旧 OWNER 键碰撞
const AUTHORIZED: Map<&Addr, bool> = Map::new("auth");
#[entry_point]
pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> StdResult<Response> {
// ❌ 错误地读取了旧版本数据
let old_owner = ADMIN.load(deps.storage)?;
Ok(Response::new())
}
修复代码
// ✅ v1 合约 — 使用版本化键名
const V1_OWNER: Item<String> = Item::new("v1_owner");
const V1_BALANCES: Map<&Addr, Uint128> = Map::new("v1_balances");
// ✅ v2 合约 — 使用不同的键名前缀
const V2_ADMIN: Item<Addr> = Item::new("v2_admin");
const V2_AUTHORIZED: Map<&Addr, bool> = Map::new("v2_authorized");
#[entry_point]
pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> StdResult<Response> {
// ✅ 明确处理存储迁移
let old_owner: String = V1_OWNER.load(deps.storage)?;
let new_admin = deps.api.addr_validate(&old_owner)?;
V2_ADMIN.save(deps.storage, &new_admin)?;
Ok(Response::new()
.add_attribute("action", "migrate")
.add_attribute("from", "v1")
.add_attribute("to", "v2"))
}
存储键命名约定
// ✅ 推荐的存储键命名模式
const CONTRACT_INFO: Item<ContractInfo> = Item::new("contract_info_v1");
const CONFIG: Item<Config> = Item::new("config_v1");
const BALANCES: Map<&Addr, Uint128> = Map::new("balance_v1");
const ALLOWANCES: Map<(&Addr, &Addr), Uint128> = Map::new("allowance_v1");
const STATE_V1: Item<StateV1> = Item::new("state_v1");
VULN-05: Reply 处理漏洞 (Reply-Based Attacks)
描述
Reply 是 CosmWasm 中处理子消息结果的关键机制。不正确的 reply 处理可导致状态不一致、资金损失或条件竞争。
类似 Lido 合约 reply 处理漏洞 — 子消息失败但主合约继续执行,导致 stETH 错误铸造。
脆弱代码
// ⚠️ 脆弱版本 — 忽略子消息执行结果
#[entry_point]
pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> StdResult<Response> {
match msg.id {
1 => {
// ❌ 未检查 msg.result 是否成功!
let data = msg.result.into_result().unwrap_or_default();
let deposited: Uint128 = from_binary(&data.data.unwrap_or_default())?;
// ❌ 即使子消息失败也更新状态
let state = LOCKED_FUNDS.load(deps.storage)?;
let new_state = state.checked_add(deposited)?;
LOCKED_FUNDS.save(deps.storage, &new_state)?;
Ok(Response::new()
.add_attribute("reply_id", "1")
.add_attribute("deposited", deposited))
}
_ => Ok(Response::new())
}
}
修复代码
// ✅ 修复版本 — 始终检查 SubMsgResult
pub const DEPOSIT_REPLY_ID: u64 = 1;
#[entry_point]
pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> StdResult<Response> {
match msg.id {
DEPOSIT_REPLY_ID => {
match msg.result {
SubMsgResult::Ok(response) => {
let deposited: Uint128 = response
.data
.map(|d| from_binary(&d))
.transpose()?
.unwrap_or(Uint128::zero());
let state = LOCKED_FUNDS.load(deps.storage)?;
let new_state = state.checked_add(deposited)
.map_err(|_| StdError::generic_err("Overflow"))?;
LOCKED_FUNDS.save(deps.storage, &new_state)?;
Ok(Response::new()
.add_attribute("reply_id", "deposit")
.add_attribute("status", "success")
.add_attribute("deposited", deposited))
}
SubMsgResult::Error(e) => {
Ok(Response::new()
.add_attribute("reply_id", "deposit")
.add_attribute("status", "failed")
.add_attribute("error", e))
}
}
}
_ => Ok(Response::new())
}
}
SubMsg 安全使用规范
// ✅ 安全的 SubMsg 模式
use cosmwasm_std::ReplyOn;
// 不需要回复
let msg = SubMsg {
id: 0,
msg: bank_msg,
gas_limit: None,
reply_on: ReplyOn::Never,
};
// 只需要成功通知
let msg = SubMsg {
id: DEPOSIT_REPLY_ID,
msg: cosmos_msg,
gas_limit: Some(100_000), // ✅ 设置 gas 限制
reply_on: ReplyOn::Success,
};
// 必须知道成功或失败
let msg = SubMsg {
id: CRITICAL_REPLY_ID,
msg: cosmos_msg,
gas_limit: Some(500_000),
reply_on: ReplyOn::Always,
};
VULN-06: IBC 超时漏洞 (IBC Timeout Exploits)
描述
IBC 使用超时机制防止资金锁定。如果超时处理不当,攻击者可利用时序攻击窃取跨链资产。
类似 Poly Network 跨链桥攻击(2021年,6.1亿美元损失) — 跨链消息验证不充分。
⚠️ MSG Chain 的跨链层目前处于规划阶段。以下适用于跨链功能启用后。
脆弱代码
// ⚠️ 脆弱版本 — IBC 超时处理不完整
#[entry_point]
pub fn ibc_packet_timeout(
deps: DepsMut,
_env: Env,
msg: IbcPacketTimeoutMsg,
) -> StdResult<Response> {
let packet = msg.packet;
// ❌ 未验证 packet 来源
// ❌ 未检查 packet 是否已被处理
let mut escrow = ESCROW_BALANCE.load(deps.storage)?;
let amount: Uint128 = from_binary(&packet.data)?;
escrow = escrow.checked_sub(amount)
.map_err(|_| StdError::generic_err("Insufficient escrow"))?;
ESCROW_BALANCE.save(deps.storage, &escrow)?;
Ok(Response::new())
}
修复代码
// ✅ 修复版本 — 完整的 IBC 超时处理
#[entry_point]
pub fn ibc_packet_timeout(
deps: DepsMut,
env: Env,
msg: IbcPacketTimeoutMsg,
) -> StdResult<Response> {
let packet = msg.packet;
let config = CONFIG.load(deps.storage)?;
// ✅ 1. 验证通道来源
if packet.src.port_id != config.ibc_port
|| packet.src.channel_id != config.ibc_channel
{
return Err(StdError::generic_err("Invalid packet source"));
}
// ✅ 2. 检查序列号 — 防止重复处理
let last_seq = LAST_TIMEOUT_SEQ.may_load(deps.storage)?.unwrap_or(0);
if packet.sequence <= last_seq {
return Err(StdError::generic_err("Packet sequence already processed"));
}
LAST_TIMEOUT_SEQ.save(deps.storage, &packet.sequence)?;
// ✅ 3. 解析并验证数据
let transfer_data: TransferData = from_binary(&packet.data)?;
let refund_addr = deps.api.addr_validate(&transfer_data.sender)?;
// ✅ 4. 执行退款
let mut escrow = ESCROW_BALANCE.load(deps.storage)?;
escrow = escrow.checked_sub(transfer_data.amount)
.map_err(|_| StdError::generic_err("Insufficient escrow"))?;
ESCROW_BALANCE.save(deps.storage, &escrow)?;
let refund_msg = BankMsg::Send {
to_address: refund_addr.to_string(),
amount: vec![Coin::new(transfer_data.amount.u128(), &config.denom)],
};
Ok(Response::new()
.add_message(refund_msg)
.add_attribute("action", "ibc_timeout_refund")
.add_attribute("sequence", packet.sequence.to_string()))
}
VULN-07: 价格预言机操纵 (Price Oracle Manipulation)
描述
使用链上价格预言机的合约易受操纵攻击,尤其是使用即时价格(如 AMM 池瞬时价格)时。
类似 TWAP 操纵攻击 — 攻击者通过大额交易操纵预言机价格以套利。
脆弱代码
// ⚠️ 脆弱版本 — 使用单一来源即时价格
pub fn calculate_collateral_value(
deps: DepsMut,
collateral_amount: Uint128,
asset: String,
) -> StdResult<Uint128> {
// ❌ 使用 AMM 池瞬时价格 — 极易操纵
let price: Uint128 = PRICE_ORACLE.load(deps.storage, &asset)?;
// ❌ 无滑点保护
let value = collateral_amount.checked_mul(price)?;
Ok(value)
}
修复代码
// ✅ 修复版本 — 使用 TWAP + 多重价格源
pub fn get_twap_price(
deps: &DepsMut,
asset: &str,
current_time: u64,
) -> StdResult<Decimal> {
let cumulative = PRICE_CUMULATIVE.load(deps.storage, asset)?;
let time_elapsed = current_time - cumulative.last_update;
if time_elapsed < 60 {
return Err(StdError::generic_err("TWAP window too short"));
}
let twap = Decimal::from_ratio(cumulative.cumulative_price, time_elapsed);
Ok(twap)
}
pub fn secure_liquidate(
deps: DepsMut,
env: Env,
info: MessageInfo,
borrower: String,
) -> StdResult<Response> {
let position = POSITIONS.load(deps.storage, &borrower)?;
// ✅ 使用 TWAP 价格
let twap_price = get_twap_price(
&deps,
&position.collateral_asset,
env.block.time.seconds(),
)?;
// ✅ 使用链下价格源交叉验证
let external_price = query_external_price_source(
deps.as_ref(),
&position.collateral_asset,
)?;
// ✅ 取较低者(保守估值)
let safe_price = if twap_price < external_price { twap_price } else { external_price };
let collateral_value = position.collateral * safe_price;
if collateral_value < position.debt.checked_mul(Uint128::new(120))? {
// 执行清算
Ok(Response::new().add_attribute("action", "liquidated"))
} else {
Err(StdError::generic_err("Position is healthy"))
}
}
VULN-08: 闪电贷攻击 (Flash Loan Attacks)
描述
虽然 CosmWasm 本身不原生支持闪电贷,但通过复合消息操作可以实现类似攻击模式。
类似 bZx 闪电贷攻击(2020年) — 利用闪电贷操纵价格并在同一交易中获利。
脆弱代码
// ⚠️ 脆弱合约 — 未处理大规模临时价格冲击
pub fn swap_exact_in(
deps: DepsMut,
env: Env,
info: MessageInfo,
token_in: String,
token_out: String,
amount_in: Uint128,
min_amount_out: Uint128,
) -> StdResult<Response> {
let pool = POOL.load(deps.storage)?;
// ❌ 未考虑大额 swap 对价格的影响
let amount_out = pool.calculate_output(amount_in);
if amount_out < min_amount_out {
return Err(StdError::generic_err("Slippage exceeded"));
}
Ok(Response::new())
}
修复代码
// ✅ 修复版本 — TWAP + 滑点保护 + 交易检查
pub fn secure_swap(
deps: DepsMut,
env: Env,
info: MessageInfo,
token_in: String,
token_out: String,
amount_in: Uint128,
min_amount_out: Uint128,
) -> StdResult<Response> {
// ✅ 1. 使用 TWAP 检测价格偏离
let twap_price = get_twap_price(&deps, &token_in, env.block.time.seconds())?;
let current_price = get_current_price(&deps, &token_in)?;
let deviation = (current_price - twap_price) / twap_price;
if deviation > Decimal::percent(10) {
return Err(StdError::generic_err("Price deviation too high"));
}
// ✅ 2. 防止同一交易中重入
let last_tx = LAST_TX_HASH.load(deps.storage)?;
if last_tx == env.transaction.unwrap().index.to_string() {
return Err(StdError::generic_err("Reentrant transaction detected"));
}
LAST_TX_HASH.save(deps.storage, &env.transaction.unwrap().index.to_string())?;
// ✅ 3. 强制执行滑点保护
let pool = POOL.load(deps.storage)?;
let amount_out = pool.calculate_output(amount_in);
if amount_out < min_amount_out {
return Err(StdError::generic_err("Slippage exceeded"));
}
Ok(Response::new().add_attribute("action", "secure_swap"))
}
VULN-09: 签名重放 (Signature Replay)
描述
MSG Chain 使用 Dilithium-5 签名,支持 nonce + ChainID 双重保护。自定义合约中的签名验证可能忽略这些保护。
类似 Wormhole 跨链桥攻击(2022年,3.26亿美元损失) — 签名验证逻辑缺失关键检查。
脆弱代码
// ⚠️ 脆弱版本 — 自定义签名验证缺少重放保护
pub fn execute_permit(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
permit: Permit,
) -> StdResult<Response> {
// ❌ 未验证 ChainID
// ❌ 未验证 nonce
let signer = verify_permit_signature(&deps, &permit)?;
let balance = BALANCES.load(deps.storage, &signer)?;
let new_balance = balance.checked_sub(permit.amount)?;
BALANCES.save(deps.storage, &signer, &new_balance)?;
Ok(Response::new())
}
修复代码
// ✅ 修复版本 — 完整的重放保护
const USED_NONCES: Map<(&Addr, u64), bool> = Map::new("used_nonce_v1");
pub fn execute_permit(
deps: DepsMut,
env: Env,
_info: MessageInfo,
permit: Permit,
) -> StdResult<Response> {
// ✅ 1. ChainID 验证
if env.block.chain_id != "msgchain-1" {
return Err(StdError::generic_err("Invalid chain ID"));
}
// ✅ 2. 有效期验证
if env.block.time.seconds() > permit.deadline {
return Err(StdError::generic_err("Permit expired"));
}
// ✅ 3. Nonce 验证
let signer = deps.api.addr_validate(&permit.signer)?;
if USED_NONCES.has(deps.storage, (&signer, permit.nonce)) {
return Err(StdError::generic_err("Permit already used"));
}
// ✅ 4. 签名验证
let valid = verify_dilithium_signature(
&deps, &permit.signer, &permit.message, &permit.signature,
)?;
if !valid {
return Err(StdError::generic_err("Invalid signature"));
}
// ✅ 5. 标记 nonce 已使用(在状态变更前!)
USED_NONCES.save(deps.storage, (&signer, permit.nonce), &true)?;
// ✅ 6. 执行操作
let balance = BALANCES.load(deps.storage, &signer)?;
let new_balance = balance.checked_sub(permit.amount)?;
BALANCES.save(deps.storage, &signer, &new_balance)?;
Ok(Response::new()
.add_attribute("action", "permit_executed")
.add_attribute("nonce", permit.nonce.to_string()))
}
VULN-10: 随机数预测 (Randomness Prediction)
描述
CosmWasm 中无法生成安全的链上随机数。使用 env.block.* 或交易哈希等确定性输入作为随机数源是可预测的。
类似 SotM 黑客攻击 — 攻击者预测随机数赢得彩票合约。
脆弱代码
// ⚠️ 脆弱版本 — 可预测的随机数
pub fn lottery_pick_winner(deps: DepsMut, env: Env) -> StdResult<Response> {
let players = PLAYERS.load(deps.storage)?;
// ❌ 使用可预测的随机数源
let pseudo_random = env.block.height ^ env.block.time.nanos();
let winner_index = pseudo_random % players.len() as u64;
let winner = &players[winner_index as usize];
let prize = PRIZE_POOL.load(deps.storage)?;
PRIZE_POOL.save(deps.storage, &Uint128::zero())?;
Ok(Response::new()
.add_message(BankMsg::Send {
to_address: winner.to_string(),
amount: vec![Coin::new(prize.u128(), "umsg")],
}))
}
修复代码 — 承诺-揭示机制
// ✅ 修复 — Commit-Reveal 方案
const COMMITMENTS: Map<&Addr, Binary> = Map::new("commit_v1");
#[entry_point]
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
match msg {
ExecuteMsg::Commit { hash } => {
if COMMITMENTS.has(deps.storage, &info.sender) {
return Err(StdError::generic_err("Already committed"));
}
COMMITMENTS.save(deps.storage, &info.sender, &hash)?;
Ok(Response::new().add_attribute("action", "commit"))
}
ExecuteMsg::Reveal { secret } => {
let committed = COMMITMENTS.load(deps.storage, &info.sender)?;
let hash = deps.api.sha256(&secret);
if hash != committed {
return Err(StdError::generic_err("Invalid reveal"));
}
// 收集揭示用于最终随机数生成
Ok(Response::new().add_attribute("action", "reveal"))
}
ExecuteMsg::Finalize => {
// 使用所有揭示 + 区块数据生成最终随机数
let final_random = deps.api.sha256(&[
&combined_secrets[..],
&env.block.height.to_be_bytes()[..],
&env.block.time.nanos().to_be_bytes()[..],
].concat());
Ok(Response::new().add_attribute("random", hex::encode(final_random)))
}
}
}
VULN-11: 抢先交易与 MEV (Front-Running / MEV)
描述
Mempool 中的交易对验证者/搜索者可见,他们可插入自己的交易以获取优势。
类似 Uniswap 三明治攻击 — 攻击者在用户交易前后插入交易以榨取价值。
脆弱代码
// ⚠️ 脆弱版本 — 易受抢先交易
pub fn submit_order(deps: DepsMut, env: Env, info: MessageInfo, order: Order) -> StdResult<Response> {
// ❌ 立即执行,易被三明治攻击
let result = execute_order(deps, &order)?;
Ok(Response::new().add_attribute("action", "order_executed"))
}
修复代码
// ✅ 修复版本 — 批量拍卖模式
pub fn submit_order(deps: DepsMut, _env: Env, info: MessageInfo, order: Order) -> StdResult<Response> {
let mut batch = BATCH_ORDERS.load(deps.storage)?;
batch.push(BatchOrder {
sender: info.sender,
order,
submitted_at: _env.block.time.seconds(),
});
BATCH_ORDERS.save(deps.storage, &batch)?;
Ok(Response::new().add_attribute("action", "order_queued"))
}
pub fn execute_batch(deps: DepsMut, env: Env) -> StdResult<Response> {
let batch = BATCH_ORDERS.load(deps.storage)?;
let clearing_price = calculate_clearing_price(&batch)?;
for order in batch {
fill_order(deps.storage, order, clearing_price)?;
}
BATCH_ORDERS.save(deps.storage, &vec![])?;
Ok(Response::new().add_attribute("action", "batch_executed"))
}
VULN-12: Gas Griefing (Gas 消耗攻击)
描述
攻击者通过触发高 gas 消耗操作使合约执行失败,导致用户损失 gas 费。MSG Chain 的 40/30/20/10 分配模式加剧了此问题。
脆弱代码
// ⚠️ 脆弱版本 — 无限迭代
pub fn process_deposits(deps: DepsMut, _env: Env, _info: MessageInfo, users: Vec<String>) -> StdResult<Response> {
for user in &users {
let deposit = USER_DEPOSITS.load(deps.storage, user)?;
PROCESSED.save(deps.storage, user, &true)?;
}
Ok(Response::new().add_attribute("processed", users.len().to_string()))
}
修复代码
// ✅ 修复版本 — 限制迭代 + 分页
pub fn process_deposits_paginated(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<Response> {
let limit = limit.unwrap_or(20).min(100); // ✅ 硬限制
let users: Vec<String> = USER_DEPOSITS
.keys(deps.storage, None, Some(limit), Order::Ascending)
.collect();
let mut total_deposited = Uint128::zero();
for user in users {
let deposit = USER_DEPOSITS.load(deps.storage, &user)?;
total_deposited = total_deposited.checked_add(deposit)?;
PROCESSED.save(deps.storage, &user, &true)?;
}
Ok(Response::new()
.add_attribute("processed", users.len().to_string())
.add_attribute("total", total_deposited))
}
VULN-13: 不安全的地址验证 (Unsafe Address Validation)
描述
CosmWasm 合约必须使用 deps.api.addr_validate() 验证地址,而不是 Addr::unchecked()。
脆弱代码
// ⚠️ 脆弱版本 — 未验证地址
pub fn transfer(
deps: DepsMut,
_env: Env,
info: MessageInfo,
recipient: String,
amount: Uint128,
) -> StdResult<Response> {
let recipient_addr = Addr::unchecked(&recipient); // ❌
let sender_balance = BALANCES.load(deps.storage, &info.sender)?;
BALANCES.save(deps.storage, &info.sender, &sender_balance.checked_sub(amount)?)?;
Ok(Response::new()
.add_message(BankMsg::Send {
to_address: recipient_addr.to_string(),
amount: vec![Coin::new(amount.u128(), "umsg")],
}))
}
修复代码
// ✅ 修复版本 — 验证所有用户地址
pub fn secure_transfer(
deps: DepsMut,
_env: Env,
info: MessageInfo,
recipient: String,
amount: Uint128,
) -> StdResult<Response> {
let recipient_addr = deps.api.addr_validate(&recipient)?;
let sender_balance = BALANCES.load(deps.storage, &info.sender)?;
if sender_balance < amount {
return Err(StdError::generic_err("Insufficient balance"));
}
BALANCES.save(deps.storage, &info.sender, &sender_balance.checked_sub(amount)?)?;
Ok(Response::new()
.add_message(BankMsg::Send {
to_address: recipient_addr.to_string(),
amount: vec![Coin::new(amount.u128(), "umsg")],
})
.add_attribute("action", "secure_transfer"))
}
VULN-14: funds 验证遗漏 (Missing Funds Validation)
描述
合约必须验证 info.funds 与操作金额一致,否则用户可利用错误金额执行操作。
脆弱代码
// ⚠️ 脆弱版本 — 未验证 funds
pub fn deposit(deps: DepsMut, _env: Env, info: MessageInfo, amount: Uint128) -> StdResult<Response> {
// ❌ 用户传 1000 但只附上 1 umsg
let mut balance = BALANCES.load(deps.storage, &info.sender)?;
balance = balance.checked_add(amount)?;
BALANCES.save(deps.storage, &info.sender, &balance)?;
Ok(Response::new().add_attribute("action", "deposit").add_attribute("amount", amount))
}
修复代码
// ✅ 修复版本 — 验证 funds
pub fn secure_deposit(
deps: DepsMut,
_env: Env,
info: MessageInfo,
amount: Uint128,
denom: String,
) -> StdResult<Response> {
if info.funds.is_empty() {
return Err(StdError::generic_err("No funds sent"));
}
let sent_amount = info.funds.iter()
.find(|c| c.denom == denom)
.map(|c| c.amount)
.unwrap_or(Uint128::zero());
if sent_amount != amount {
return Err(StdError::generic_err(format!(
"Sent {} {} but expected {}", sent_amount, denom, amount
)));
}
let mut balance = BALANCES.load(deps.storage, &info.sender)?;
balance = balance.checked_add(amount)?;
BALANCES.save(deps.storage, &info.sender, &balance)?;
Ok(Response::new()
.add_attribute("action", "secure_deposit")
.add_attribute("amount", amount))
}
VULN-15: 不安全的跨合约查询 (Unsafe Cross-Contract Queries)
描述
查询外部合约时,如果外部合约返回恶意数据,可能导致反序列化攻击或逻辑错误。
脆弱代码
// ⚠️ 脆弱版本 — 未验证查询结果
pub fn get_asset_price(deps: DepsMut, oracle_addr: String) -> StdResult<Uint128> {
// ❌ 未验证预言机地址和返回值
let price: Uint128 = deps.querier.query_wasm_smart(
oracle_addr,
&OracleQuery::GetPrice {},
)?;
Ok(price)
}
修复代码
// ✅ 修复版本 — 验证跨合约查询结果
pub fn secure_get_asset_price(
deps: DepsMut,
oracle_addr: String,
) -> StdResult<Uint128> {
let config = CONFIG.load(deps.storage)?;
if !config.trusted_oracles.contains(&oracle_addr) {
return Err(StdError::generic_err("Untrusted oracle"));
}
let price: Uint128 = deps.querier.query_wasm_smart(
&deps.api.addr_validate(&oracle_addr)?,
&OracleQuery::GetPrice {},
)?;
if price.is_zero() {
return Err(StdError::generic_err("Oracle returned zero price"));
}
if price > Uint128::new(1_000_000_000) {
return Err(StdError::generic_err("Oracle price exceeds maximum"));
}
Ok(price)
}
VULN-16: 不安全的 IBC 包验证 (Unsafe IBC Packet Validation)
描述
IBC 包处理时未验证发送者、通道或顺序,导致伪造跨链消息。
脆弱代码
// ⚠️ 脆弱版本 — 未验证 IBC 包
#[entry_point]
pub fn ibc_packet_receive(
deps: DepsMut,
env: Env,
msg: IbcPacketReceiveMsg,
) -> StdResult<IbcReceiveResponse> {
let packet = msg.packet;
let data: TransferData = from_binary(&packet.data)?;
mint_tokens(deps, &data.recipient, data.amount)?;
Ok(IbcReceiveResponse::new().add_attribute("action", "mint"))
}
修复代码
// ✅ 修复版本 — 完整的 IBC 包验证
#[entry_point]
pub fn ibc_packet_receive(
deps: DepsMut,
env: Env,
msg: IbcPacketReceiveMsg,
) -> StdResult<IbcReceiveResponse> {
let packet = msg.packet;
let config = CONFIG.load(deps.storage)?;
// ✅ 1. 验证端口和通道
if packet.dest.port_id != config.ibc_port
|| packet.dest.channel_id != config.ibc_channel {
return Err(StdError::generic_err("Invalid destination"));
}
if packet.src.port_id != config.counterparty_port
|| packet.src.channel_id != config.counterparty_channel {
return Err(StdError::generic_err("Invalid source"));
}
// ✅ 2. 验证序列号
let last_seq = LAST_RECEIVED_SEQ.load(deps.storage)?;
if packet.sequence <= last_seq {
return Err(StdError::generic_err("Duplicate packet"));
}
LAST_RECEIVED_SEQ.save(deps.storage, &packet.sequence)?;
// ✅ 3. 验证超时
if packet.timeout.has_expired(&env) {
return Err(StdError::generic_err("Packet expired"));
}
// ✅ 4. 验证数据
let data: TransferData = from_binary(&packet.data)?;
let recipient = deps.api.addr_validate(&data.recipient)?;
if data.amount.is_zero() {
return Err(StdError::generic_err("Zero amount"));
}
mint_tokens(deps, &recipient, data.amount)?;
Ok(IbcReceiveResponse::new()
.add_attribute("action", "mint")
.add_attribute("sequence", packet.sequence.to_string()))
}
VULN-17: 无限制代币增发 (Unchecked Token Minting)
描述
合约中铸币函数缺少权限控制或上限检查,导致任意用户可无限铸造代币。
脆弱代码
// ⚠️ 脆弱版本 — 无限制铸币
pub fn mint(deps: DepsMut, _env: Env, info: MessageInfo, to: String, amount: Uint128) -> StdResult<Response> {
// ❌ 无权限检查!无总量上限!
let mut supply = TOTAL_SUPPLY.load(deps.storage)?;
supply = supply.checked_add(amount)?;
TOTAL_SUPPLY.save(deps.storage, &supply)?;
let mut balance = BALANCES.load(deps.storage, &to).unwrap_or(Uint128::zero());
balance = balance.checked_add(amount)?;
BALANCES.save(deps.storage, &to, &balance)?;
Ok(Response::new()
.add_attribute("action", "mint")
.add_attribute("to", to)
.add_attribute("amount", amount))
}
修复代码
// ✅ 修复版本 — 权限 + 上限控制
pub const MAX_SUPPLY: Uint128 = Uint128::new(1_000_000_000_000_000);
pub fn secure_mint(
deps: DepsMut,
_env: Env,
info: MessageInfo,
to: String,
amount: Uint128,
) -> StdResult<Response> {
let config = CONFIG.load(deps.storage)?;
if info.sender != config.minter {
return Err(StdError::generic_err("Unauthorized: not a minter"));
}
let to_addr = deps.api.addr_validate(&to)?;
let supply = TOTAL_SUPPLY.load(deps.storage)?;
let new_supply = supply.checked_add(amount)
.map_err(|_| StdError::generic_err("Supply overflow"))?;
if new_supply > MAX_SUPPLY {
return Err(StdError::generic_err("Exceeds maximum supply"));
}
TOTAL_SUPPLY.save(deps.storage, &new_supply)?;
let mut balance = BALANCES.load(deps.storage, &to_addr).unwrap_or(Uint128::zero());
balance = balance.checked_add(amount)?;
BALANCES.save(deps.storage, &to_addr, &balance)?;
Ok(Response::new()
.add_attribute("action", "secure_mint")
.add_attribute("amount", amount)
.add_attribute("new_supply", new_supply))
}
VULN-18: 权限提升 (Privilege Escalation)
描述
合约中存储权限或角色时使用了可变存储,攻击者可通过更新特定状态获得未授权权限。
脆弱代码
// ⚠️ 脆弱版本 — 角色存储可被篡改
const ROLES: Map<&Addr, String> = Map::new("roles");
pub fn has_role(deps: Deps, addr: &Addr, role: &str) -> bool {
ROLES.may_load(deps.storage, addr).unwrap_or(None)
.map(|r| r == role).unwrap_or(false)
}
#[entry_point]
pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> StdResult<Response> {
// ❌ 存储操作可能被利用设置任意角色
ROLES.save(deps.storage, &Addr::unchecked("attacker"), &"admin".to_string())?;
Ok(Response::new())
}
修复代码
// ✅ 修复版本 — 不可变权限 + 硬编码角色检查
pub struct Config {
pub owner: Addr,
pub admin: Addr,
pub operators: Vec<Addr>,
}
const CONFIG: Item<Config> = Item::new("config_v1");
pub fn assert_admin(deps: &Deps, sender: &Addr) -> StdResult<()> {
let config = CONFIG.load(deps.storage)?;
if *sender != config.admin && *sender != config.owner {
return Err(StdError::generic_err("Unauthorized: not admin"));
}
Ok(())
}
pub fn assert_operator(deps: &Deps, sender: &Addr) -> StdResult<()> {
let config = CONFIG.load(deps.storage)?;
if *sender == config.owner || *sender == config.admin { return Ok(()); }
if config.operators.contains(sender) { return Ok(()); }
Err(StdError::generic_err("Unauthorized: not operator"))
}
VULN-19: 精度损失 (Precision Loss)
描述
使用 Decimal 类型时可能因精度问题导致价值损耗或分配不均。
脆弱代码
// ⚠️ 脆弱版本 — 精度损失
pub fn distribute_rewards(deps: DepsMut, total_reward: Uint128) -> StdResult<Response> {
let stakers = STAKERS.load(deps.storage)?;
let total_staked = TOTAL_STAKED.load(deps.storage)?;
for (staker, stake) in &stakers {
// ❌ 多次舍入导致总分配 !== total_reward
let share = Decimal::from_ratio(*stake, total_staked);
let reward = total_reward * share;
distribute(deps, staker, reward)?;
}
Ok(Response::new())
}
修复代码
// ✅ 修复版本 — 余数处理
pub fn secure_distribute_rewards(deps: DepsMut, total_reward: Uint128) -> StdResult<Response> {
let stakers = STAKERS.load(deps.storage)?;
let total_staked = TOTAL_STAKED.load(deps.storage)?;
let mut distributed = Uint128::zero();
let staker_count = stakers.len();
for (i, (staker, stake)) in stakers.iter().enumerate() {
// ✅ 最后一个参与者获得所有余数
if i == staker_count - 1 {
let final_reward = total_reward.checked_sub(distributed)?;
distribute(deps, staker, final_reward)?;
} else {
let raw_share = stake.u128() * total_reward.u128();
let reward = Uint128::new(raw_share / total_staked.u128());
distributed = distributed.checked_add(reward)?;
distribute(deps, staker, reward)?;
}
}
Ok(Response::new().add_attribute("action", "distribute_rewards"))
}
VULN-20: Agent API 滥用 (Agent API Abuse)
描述
MSG Chain 的 Agent API 允许 AI Agent 执行链上操作。如果验证不严,攻击者可通过恶意 Agent 执行越权操作。
脆弱代码
// ⚠️ 脆弱版本 — Agent 操作缺少宪法约束
#[entry_point]
pub fn execute_agent_action(
deps: DepsMut,
env: Env,
info: MessageInfo,
action: AgentAction,
) -> StdResult<Response> {
// ❌ 未验证 Agent 身份和宪法
match action {
AgentAction::Transfer { recipient, amount } => {
return Ok(Response::new()
.add_message(BankMsg::Send {
to_address: recipient,
amount: vec![Coin::new(amount, "umsg")],
}));
}
}
}
修复代码
// ✅ 修复版本 — 完整的 Agent 验证
#[entry_point]
pub fn execute_agent_action(
deps: DepsMut,
env: Env,
info: MessageInfo,
action: AgentAction,
) -> StdResult<Response> {
// ✅ 1. 验证 Agent 存在且属于调用者
let agent = AGENTS.load(deps.storage, &info.sender)?;
if agent.owner != info.sender {
return Err(StdError::generic_err("Agent not owned by sender"));
}
// ✅ 2. 检查宪法约束 (CONSTITUTION_VIOLATION)
if let Some(constitution) = &agent.constitution {
if !constitution.allows_action(&action) {
return Err(StdError::generic_err("CONSTITUTION_VIOLATION"));
}
}
// ✅ 3. 检查 Agent 状态
if agent.paused {
return Err(StdError::generic_err("Agent is paused"));
}
// ✅ 4. 验证操作权限
let allowed_actions = agent.get_allowed_actions();
if !allowed_actions.contains(&action.action_type()) {
return Err(StdError::generic_err("Action not permitted"));
}
// ✅ 5. 执行(X-MSG-Stub 审计标记)
AUDIT_LOG.save(deps.storage, &AuditEntry {
agent_id: agent.id,
action: action.clone(),
timestamp: env.block.time.seconds(),
})?;
Ok(Response::new()
.add_attribute("action", "agent_executed")
.add_attribute("x_msg_stub", "true"))
}
3. MSG Chain 特有安全考量
3.1 Dilithium-5 签名验证
Gas 成本影响
操作 Gas 消耗 (估计) 对比 ECDSA
─────────────────────────────────────────────────────
Dilithium-5 签名验证 ~150,000 - 300,000 ~5-10x ECDSA
ECDSA 签名验证 ~30,000 - 50,000 基准线
公钥序列化/反序列化 ~20,000 - 40,000 ~10x ECDSA
安全含义:
- 合约中频繁使用
deps.api验证签名可能导致高 gas 消耗 - 在自定义签名验证逻辑中,应考虑 gas 限制
- 批量签名验证可能导致区块 gas 上限问题
签名验证模式
// ✅ Dilithium-5 签名验证模式
pub fn verify_agent_signature(
deps: &DepsMut,
agent: &Agent,
message: &[u8],
signature: &DilithiumSignature,
) -> StdResult<bool> {
let pk = DilithiumPublicKey::from_bytes(&agent.public_key)
.map_err(|_| StdError::generic_err("Invalid public key"))?;
let valid = deps.api.dilithium5_verify(message, signature, &pk)?;
Ok(valid)
}
3.2 DAR 共识操纵攻击
共识模型
DAR (Dynamic Authority Rotation) 验证者集轮换:
- 验证者集大小: 动态(至少 4 个)
- 轮换阈值: 104/96 确认
- 出块者选择: Stake 权重轮换
潜在攻击向量
| 攻击类型 | 描述 | 风险等级 |
|---|---|---|
| Stake 集中化 | 攻击者控制 > 1/3 总 stake | 高 |
| 轮换抢占 | 在轮换窗口内快速获取验证者席位 | 中 |
| 长程攻击 | 攻击者从过去某点分叉链 | 低 (BadgerDB 无历史) |
| Nothing-at-Stake | 验证者在多个分叉上出块 | 低 (DAR 缓解) |
安全建议
// 验证者集变更监测
pub fn monitor_validator_set(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
) -> StdResult<Response> {
let current_set = VALIDATOR_SET.load(deps.storage)?;
let pending_set = PENDING_VALIDATOR_SET.may_load(deps.storage)?;
if let Some(pending) = pending_set {
let confirmations = pending.confirmation_count;
if confirmations >= 104 || (current_set.size() >= 96 && confirmations >= 96) {
execute_rotation(deps, pending)?;
}
}
Ok(Response::new().add_attribute("action", "monitor_validators"))
}
3.3 BadgerDB vs IAVL 存储安全差异
| 特性 | BadgerDB (MSG Chain) | IAVL (Cosmos SDK) |
|---|---|---|
| 存储模型 | LSM-Tree | 平衡二叉树 |
| 证据 (Proof) | 无原生支持 | Merkle 证据 |
| 状态快照 | 高效 (LSM) | 较慢 |
| 历史状态 | 需额外配置 | 原生支持 |
| 并发性 | 高 | 中 |
| 加密 | 支持 | 不支持 |
安全差异
// BadgerDB 无 IAVL 的 Merkle 证据
// → 轻客户端验证需要额外机制
// BadgerDB 垃圾回收
pub fn badger_gc_maintenance(deps: DepsMut) -> StdResult<Response> {
// MSG Chain 节点定期 GC
Ok(Response::new().add_attribute("action", "badger_gc"))
}
// 加密配置
pub struct BadgerConfig {
pub encryption_enabled: bool,
pub key_rotation_days: u64,
}
3.4 Agent API 安全边界
已知安全边界
Agent API 写路径 X-MSG-Stub=true (未审计存根标记)
@msg-chain/sdk alpha 阶段,未审计
Agent 宪法检查 错误码 12: CONSTITUTION_VIOLATION
Agent 发现 错误码 10: AGENT_NOT_FOUND
Agent 安全通信
// Agent 间安全通信
pub struct AgentMessage {
pub sender_id: String,
pub recipient_id: String,
pub nonce: Uint128,
pub timestamp: u64,
pub message_type: MessageType,
pub payload: Binary,
pub signature: Vec<u8>,
}
pub fn verify_agent_message(deps: &DepsMut, msg: &AgentMessage) -> StdResult<bool> {
let sender_agent = AGENTS.load(deps.storage, &msg.sender_id)?;
// 验证时间戳 (最大偏移 5 分钟)
let current_time = mock_env().block.time.seconds();
if msg.timestamp > current_time || current_time - msg.timestamp > 300 {
return Ok(false);
}
// 验证 nonce
if USED_NONCES.has(deps.storage, (&msg.sender_id, msg.nonce.u64())) {
return Ok(false);
}
// 验证签名
let message_bytes = msg.get_signing_bytes();
let sig = DilithiumSignature::from_bytes(&msg.signature)
.map_err(|_| StdError::generic_err("Invalid signature format"))?;
deps.api.dilithium5_verify(&message_bytes, &sig, &sender_agent.public_key)
}
3.5 Constitution 违规攻击面
Agent 宪法是约束 Agent 行为的规则集。
宪法验证
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct Constitution {
pub allowed_actions: Vec<ActionType>,
pub max_transfer_amount: Uint128,
pub restricted_addresses: Vec<Addr>,
pub required_approvals: u32,
}
pub fn strict_constitution_check(
constitution: &Constitution,
action: &AgentAction,
) -> StdResult<()> {
// 1. 验证操作类型
if !constitution.allowed_actions.contains(&action.action_type()) {
return Err(StdError::generic_err("CONSTITUTION_VIOLATION"));
}
// 2. 检查转账金额
if let AgentAction::Transfer { amount, .. } = action {
if Uint128::new(*amount) > constitution.max_transfer_amount {
return Err(StdError::generic_err("CONSTITUTION_VIOLATION: Exceeds max"));
}
}
// 3. 检查限制地址
if let AgentAction::Transfer { recipient, .. } = action {
let recipient_addr = Addr::unchecked(recipient);
if constitution.restricted_addresses.contains(&recipient_addr) {
return Err(StdError::generic_err("CONSTITUTION_VIOLATION: Restricted"));
}
}
// 4. 检查多签批准
if constitution.required_approvals > 0 {
let approvals = ACTION_APPROVALS.load(deps.storage, &action.id())?;
if approvals < constitution.required_approvals {
return Err(StdError::generic_err("CONSTITUTION_VIOLATION: Need approvals"));
}
}
Ok(())
}
4. 合约安全审计检查表
50+ 审计项,按类别组织。每个审计项包含:检查 ID、描述、严重等级、验证方法。
4.1 访问控制 (Access Control)
| ID | 检查项 | 严重等级 | 验证方法 |
|---|---|---|---|
| AC-01 | 管理员函数是否验证调用者权限 | 🔴 Critical | 代码审查每处 execute 入口 |
| AC-02 | 是否使用 addr_validate() 验证地址 |
🟠 High | grep Addr::unchecked |
| AC-03 | 角色管理是否有提权保护 | 🟠 High | 测试角色升级路径 |
| AC-04 | 迁移函数是否有额外权限检查 | 🔴 Critical | 审查 migrate 入口 |
| AC-05 | Owner 地址是否可通过外部调用修改 | 🟠 High | 检查 owner setter 权限 |
| AC-06 | 暂停/恢复功能是否只对管理员开放 | 🟡 Medium | 测试 pause/unpause |
| AC-07 | 代理合约的调用者是否为预期合约 | 🟠 High | 审查代理模式 |
| AC-08 | Agent API 是否验证 Agent 身份 | 🔴 Critical | 审查 agent 调用路径 |
4.2 算术与精度 (Arithmetic)
| ID | 检查项 | 严重等级 | 验证方法 |
|---|---|---|---|
| AR-01 | 是否使用 checked_add/sub/mul |
🔴 Critical | grep + 和 - 操作符 |
| AR-02 | 除零保护 | 🟠 High | 检查除法前分母验证 |
| AR-03 | Decimal 精度损失 | 🟡 Medium | 计算 total_in = total_out |
| AR-04 | 费率计算舍入方向是否正确 | 🟡 Medium | 测试边界值 |
| AR-05 | Uint128 转换为 u64 时 checked 转换 | 🟠 High | grep as u64 / as u128 |
| AR-06 | 批量分配时余数处理 | 🟡 Medium | 验证分配总和 = 初始总和 |
4.3 重入与执行流 (Reentrancy)
| ID | 检查项 | 严重等级 | 验证方法 |
|---|---|---|---|
| RE-01 | Checks-Effects-Interactions 模式 | 🔴 Critical | 审查状态更新与消息发送顺序 |
| RE-02 | Reply handler 检查 SubMsgResult |
🟠 High | 审查所有 reply 函数 |
| RE-03 | SubMsg 的 reply_on 设置是否正确 |
🟡 Medium | 审查 SubMsg 构造 |
| RE-04 | 状态锁 (mutex) 是否有效 | 🟡 Medium | 测试并发场景 |
| RE-05 | 跨合约调用是否导致重入 | 🔴 Critical | 追踪所有跨合约消息流 |
4.4 代币与经济 (Token/Economics)
| ID | 检查项 | 严重等级 | 验证方法 |
|---|---|---|---|
| TK-01 | 铸币有无总量上限 | 🔴 Critical | 审查 mint 函数 |
| TK-02 | 销毁有无最小余额限制 | 🟡 Medium | 审查 burn 函数 |
| TK-03 | 转账是否验证 sender 余额 | 🔴 Critical | 审查 transfer 函数 |
| TK-04 | 授权 (allowance) 是否正确扣减 | 🟠 High | 测试 approve-transfer |
| TK-05 | 代币销毁是否同步减少总供应量 | 🟠 High | 审查 burn + supply |
| TK-06 | 是否支持原生代币与 CW20 混合 | 🟡 Medium | 审查 fund 处理 |
| TK-07 | 锁定代币是否可强制转移 | 🟠 High | 审查锁仓逻辑 |
4.5 IBC 跨链安全 (IBC)
| ID | 检查项 | 严重等级 | 验证方法 |
|---|---|---|---|
| IB-01 | IBC 包是否验证端口和通道 | 🔴 Critical | 审查 ibc_packet_receive |
| IB-02 | IBC 包序列号是否去重 | 🔴 Critical | 审查序列号跟踪 |
| IB-03 | IBC 超时处理是否完整 | 🟠 High | 审查 ibc_packet_timeout |
| IB-04 | IBC ACK 处理是否验证 | 🟠 High | 审查 ibc_packet_ack |
| IB-05 | 跨链转账铸币/销毁是否对称 | 🔴 Critical | 端到端 IBC 测试 |
| IB-06 | IBC 轻客户端验证是否跳过 | 🔴 Critical | 审查 ics20/ics721 |
4.6 存储安全 (Storage)
| ID | 检查项 | 严重等级 | 验证方法 |
|---|---|---|---|
| ST-01 | 存储键是否使用版本化前缀 | 🟡 Medium | 审查所有 Item/Map 键 |
| ST-02 | 迁移函数是否处理旧存储键 | 🟠 High | 审查 migrate 函数 |
| ST-03 | 是否存在存储键碰撞 | 🟠 High | 分析所有存储键 |
| ST-04 | 敏感数据是否存储在链上 | 🟡 Medium | 审查存储数据结构 |
| ST-05 | BadgerDB 的 TTL 设置是否合理 | 🟢 Low | 审查过期数据策略 |
| ST-06 | 存储迭代是否限制数量 | 🟡 Medium | 审查 range / keys 调用 |
4.7 输入验证 (Input Validation)
| ID | 检查项 | 严重等级 | 验证方法 |
|---|---|---|---|
| IV-01 | 所有 String 输入是否经 addr_validate |
🟠 High | grep addr_validate |
| IV-02 | 金额是否为 0 或负数 | 🟡 Medium | 审查金额检查 |
| IV-03 | 数组/向量是否为空 | 🟢 Low | 审查空集合处理 |
| IV-04 | 分页参数是否有限制 | 🟡 Medium | 审查分页函数 |
| IV-05 | Denom 是否在白名单中 | 🟠 High | 审查 denom 验证 |
| IV-06 | 智能合约地址是否有效 | 🟡 Medium | 审查合约地址检查 |
| IV-07 | 非空字符串验证 | 🟢 Low | 审查字符串空值 |
4.8 事件与日志 (Events)
| ID | 检查项 | 严重等级 | 验证方法 |
|---|---|---|---|
| EV-01 | 所有状态变更是否发出事件 | 🟡 Medium | 审查状态变更无事件 |
| EV-02 | 事件属性是否包含关键信息 | 🟢 Low | 审查 event attribute |
| EV-03 | 敏感数据是否泄露在事件中 | 🟠 High | 审查事件中的 secret |
| EV-04 | 错误处理是否泄露存储路径 | 🟢 Low | 审查错误消息 |
4.9 MSG Chain 特有安全
| ID | 检查项 | 严重等级 | 验证方法 |
|---|---|---|---|
| MS-01 | Dilithium-5 签名验证正确处理非 ASCII | 🟠 High | 审查签名验证路径 |
| MS-02 | Gas 估算是否考虑 Dilithium-5 成本 | 🟡 Medium | 基准测试 |
| MS-03 | Agent Constitution 检查是否完整 | 🔴 Critical | 审查宪法验证 |
| MS-04 | X-MSG-Stub 标记的写路径是否限制 | 🔴 Critical | 审查 stub 标记路径 |
| MS-05 | Agent 通信 nonce 是否唯一 | 🟠 High | 审查 agent 消息 nonce |
| MS-06 | DAR 验证者集变化是否在阈值内 | 🟡 Medium | 审查阈值检查 |
| MS-07 | @msg-chain/sdk 使用最新版本 |
🟡 Medium | 检查 sdk 版本 |
| MS-08 | 是否在正式测试网测试 | 🟢 Low | 检查测试环境 |
| MS-09 | Explorer hash 搜索 503 处理 | 🟡 Medium | 审查 explorer 回调 |
| MS-10 | Nonce + ChainID 重放保护是否生效 | 🔴 Critical | 审查 nonce/chainid 检查 |
4.10 紧急停止与升级
| ID | 检查项 | 严重等级 | 验证方法 |
|---|---|---|---|
| EM-01 | 是否有暂停/紧急停止功能 | 🔴 Critical | 审查 pause 机制 |
| EM-02 | 暂停功能是否保护关键状态 | 🟠 High | 测试暂停后操作 |
| EM-03 | 升级是否需要治理通过 | 🟠 High | 审查迁移治理 |
| EM-04 | 紧急提现功能是否安全 | 🔴 Critical | 审查 emergency withdraw |
| EM-05 | 多签是否实现 | 🟠 High | 审查多签逻辑 |
5. 安全编码规范
5.1 输入验证模式
// ✅ 输入验证最佳实践
pub fn validate_inputs(
deps: DepsMut,
recipient: &str,
amount: Uint128,
denom: &str,
) -> StdResult<Addr> {
let recipient_addr = deps.api.addr_validate(recipient)?;
if amount.is_zero() {
return Err(StdError::generic_err("Amount must be positive"));
}
if !is_valid_denom(denom) {
return Err(StdError::generic_err("Invalid denomination"));
}
Ok(recipient_addr)
}
fn is_valid_denom(denom: &str) -> bool {
let valid_denoms = ["umsg", "uconst", "uagent"];
valid_denoms.contains(&denom)
}
5.2 授权模式
// ✅ 所有权模式
pub fn assert_owner(deps: &Deps, sender: &Addr) -> StdResult<()> {
let config = CONFIG.load(deps.storage)?;
if sender != &config.owner {
return Err(StdError::generic_err("Unauthorized"));
}
Ok(())
}
// ✅ RBAC 模式
pub enum Role { Admin, Operator, User }
pub fn assert_role(deps: &Deps, sender: &Addr, required_role: Role) -> StdResult<()> {
let config = CONFIG.load(deps.storage)?;
match required_role {
Role::Admin => {
if sender != &config.admin && sender != &config.owner {
return Err(StdError::generic_err("Admin role required"));
}
}
Role::Operator => {
if sender != &config.owner && sender != &config.admin
&& !config.operators.contains(sender) {
return Err(StdError::generic_err("Operator role required"));
}
}
Role::User => {}
}
Ok(())
}
5.3 安全数学操作
// ✅ 安全数学模式
pub struct SafeMath;
impl SafeMath {
pub fn add(a: Uint128, b: Uint128) -> StdResult<Uint128> {
a.checked_add(b).map_err(|_| StdError::generic_err("Addition overflow"))
}
pub fn sub(a: Uint128, b: Uint128) -> StdResult<Uint128> {
a.checked_sub(b).map_err(|_| StdError::generic_err("Subtraction underflow"))
}
pub fn mul(a: Uint128, b: Uint128) -> StdResult<Uint128> {
a.checked_mul(b).map_err(|_| StdError::generic_err("Multiplication overflow"))
}
pub fn div(a: Uint128, b: Uint128) -> StdResult<Decimal> {
if b.is_zero() { return Err(StdError::generic_err("Division by zero")); }
Ok(Decimal::from_ratio(a, b))
}
}
5.4 事件 (Event) 规范
// ✅ 事件发射标准模式
pub fn emit_transfer_event(
response: &mut Response,
from: &Addr,
to: &Addr,
amount: Uint128,
denom: &str,
) {
response.attributes.push(Attribute::new("action", "transfer"));
response.attributes.push(Attribute::new("from", from));
response.attributes.push(Attribute::new("to", to));
response.attributes.push(Attribute::new("amount", amount.to_string()));
response.attributes.push(Attribute::new("denom", denom));
}
// ❌ 避免:
// - 不记录操作类型
// - 泄露敏感数据(密码、私钥)
// - 使用不稳定的值作为 key
// - 记录大量数据
5.5 错误处理模式
// ✅ 使用 thiserror 定义合约特有错误
use cosmwasm_std::StdError;
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum ContractError {
#[error("{0}")]
Std(#[from] StdError),
#[error("Unauthorized: {reason}")]
Unauthorized { reason: String },
#[error("Insufficient funds: required {required}, available {available}")]
InsufficientFunds { required: Uint128, available: Uint128 },
#[error("Invalid input: {reason}")]
InvalidInput { reason: String },
#[error("Contract is paused")]
ContractPaused,
#[error("Constitution violation: {detail}")]
ConstitutionViolation { detail: String },
}
// ✅ 错误处理模式
pub fn process_with_error_handling(
deps: DepsMut,
info: MessageInfo,
amount: Uint128,
) -> Result<Response, ContractError> {
if amount.is_zero() {
return Err(ContractError::InvalidInput {
reason: "Amount must be positive".to_string(),
});
}
let config = CONFIG.load(deps.storage)?;
let balance = BALANCES.load(deps.storage, &info.sender)
.map_err(|_| ContractError::Unauthorized {
reason: "Account not found".to_string(),
})?;
if amount > balance {
return Err(ContractError::InsufficientFunds {
required: amount,
available: balance,
});
}
Ok(Response::new())
}
5.6 升级安全模式
// ✅ 安全升级模式
#[derive(Serialize, Deserialize)]
pub enum ContractVersion { V1, V2 { migration_timestamp: u64 } }
const VERSION: Item<ContractVersion> = Item::new("contract_version");
#[entry_point]
pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> StdResult<Response> {
let version = VERSION.load(deps.storage)?;
match version {
ContractVersion::V1 => migrate_v1_to_v2(deps, msg)?,
ContractVersion::V2 { .. } => {
return Err(StdError::generic_err("Already at latest version"));
}
}
Ok(Response::new())
}
fn migrate_v1_to_v2(deps: DepsMut, msg: MigrateMsg) -> StdResult<()> {
let old_config: V1Config = V1_CONFIG.load(deps.storage)?;
let new_config = V2Config {
owner: old_config.admin,
authorized: old_config.authorized,
version: 2,
migration_timestamp: msg.timestamp,
};
V2_CONFIG.save(deps.storage, &new_config)?;
VERSION.save(deps.storage, &ContractVersion::V2 {
migration_timestamp: msg.timestamp,
})?;
Ok(())
}
// 紧急回滚
#[entry_point]
pub fn sudo(deps: DepsMut, _env: Env, msg: SudoMsg) -> StdResult<Response> {
match msg {
SudoMsg::Rollback => {
V2_CONFIG.remove(deps.storage);
VERSION.save(deps.storage, &ContractVersion::V1)?;
Ok(Response::new().add_attribute("action", "rollback"))
}
}
}
6. 审计工具
6.1 Rust/CosmWasm 静态分析
# 1. Clippy — Rust lint
cargo clippy -- -D warnings
# 2. RustSec — 依赖安全审计
cargo install cargo-audit
cargo audit
# 3. CosmWasm 检查器
cargo install cosmwasm-check
cosmwasm-check ./target/wasm32-unknown-unknown/release/*.wasm
6.2 手动审计检查流程
# 关键搜索模式
echo "=== 检查 Addr::unchecked ==="
rg "Addr::unchecked" --type rust
echo "=== 检查 unchecked 算术 ==="
rg "[+-]" --type rust | rg -v "checked_|wrapping_|saturating_"
echo "=== 检查 unwrap ==="
rg "\.unwrap\(\)" --type rust
echo "=== 检查 SubMsg ==="
rg "SubMsg" --type rust
echo "=== 检查 storage 键碰撞 ==="
rg 'Item::new\("' . | sort
rg 'Map::new\("' . | sort
6.3 Fuzz 测试
// ✅ Fuzz 测试示例
#[cfg(test)]
mod fuzz_tests {
use proptest::prelude::*;
proptest! {
#![proptest_config = ProptestConfig {
cases: 1000,
.. ProptestConfig::default()
}]
#[test]
fn fuzz_transfer_amounts(
sender_balance in 0..1_000_000u128,
transfer_amount in 0..2_000_000u128,
) {
let mut deps = mock_dependencies();
let env = mock_env();
let sender = Addr::unchecked("sender");
let recipient = Addr::unchecked("recipient");
BALANCES.save(
deps.as_mut().storage, &sender, &Uint128::new(sender_balance),
).unwrap();
let result = transfer(
deps.as_mut(), env,
mock_info(&sender.to_string(), &[]),
recipient.to_string(), Uint128::new(transfer_amount),
);
if result.is_ok() {
let new_sender = BALANCES.load(deps.as_ref().storage, &sender).unwrap();
let new_recipient = BALANCES.load(deps.as_ref().storage, &recipient).unwrap();
assert_eq!(new_sender, Uint128::new(sender_balance - transfer_amount));
assert_eq!(new_recipient, Uint128::new(transfer_amount));
} else {
let new_sender = BALANCES.load(deps.as_ref().storage, &sender).unwrap();
assert_eq!(new_sender, Uint128::new(sender_balance));
}
}
}
}
6.4 Gas 分析
# 1. 使用 cosmwasm-profiler
cargo install cosmwasm-profiler
cosmwasm-profiler analyze ./artifacts/my_contract.wasm
# 2. 测试中测量 gas
#[test]
fn measure_gas_costs() {
let mut deps = mock_dependencies();
let env = mock_env();
let info = mock_info("sender", &coins(1000, "umsg"));
let start_gas = deps.as_ref().api.get_gas_used();
let _ = execute(deps.as_mut(), env.clone(), info, ExecuteMsg::SomeHeavyOp {});
let gas_used = deps.as_ref().api.get_gas_used() - start_gas;
println!("Gas used: {}", gas_used);
assert!(gas_used < 500_000, "Gas usage exceeds limit");
}
6.5 自动化审计脚本
#!/bin/bash
# audit.sh — MSG Chain 合约安全审计自动化
set -euo pipefail
CONTRACT_DIR=$1
REPORT_DIR="./audit_reports"
mkdir -p "$REPORT_DIR"
echo "=== MSG Chain 合约安全审计 ==="
# 1. 依赖审计
echo "1. 依赖审计..."
cargo audit --json > "$REPORT_DIR/dependency_audit.json" || true
# 2. Clippy
echo "2. Clippy 分析..."
cargo clippy --all-targets -- -D warnings 2>&1 | tee "$REPORT_DIR/clippy_report.txt" || true
# 3. 模式扫描
echo "3. 漏洞模式扫描..."
PATTERNS=("Addr::unchecked" "\.unwrap\(\)" "checked_add" "SubMsg" "reply_on")
for pattern in "${PATTERNS[@]}"; do
rg --count "$pattern" "$CONTRACT_DIR" --type rust || true
done > "$REPORT_DIR/pattern_analysis.txt"
# 4. WASM 检查
echo "4. WASM 分析..."
for wasm in "$CONTRACT_DIR"/target/wasm32-unknown-unknown/release/*.wasm; do
[ -f "$wasm" ] && cosmwasm-check "$wasm" 2>&1 | tee -a "$REPORT_DIR/wasm_check.txt"
done
echo "=== 审计完成 ==="
echo "报告: $REPORT_DIR/"
7. 审计报告模板
7.1 发现项格式
## 发现项
### [严重等级] 发现项标题
**ID**: `VULN-2026-001`
**类型**: 重入攻击 / 未授权访问 / 算术错误 / 逻辑错误
**严重等级**: Critical / High / Medium / Low
**CVSS 评分**: 9.8 (Critical)
**状态**: 已发现 / 已确认 / 已修复 / 已接受
#### 描述
[漏洞的详细描述,包括上下文和影响]
#### 影响
- 攻击者可提取合约中所有资金
- 攻击者可铸造任意数量代币
#### 风险场景
1. 攻击者调用 `withdraw` 函数
2. 合约在更新余额前发送资金
3. 攻击者通过 reply handler 重新调用
4. 重复提取直到余额为 0
#### 涉及代码
```rust
// 文件: src/contract.rs:45-67
pub fn withdraw(...) {
// 资金发送在状态更新之前
}
修复建议
// 遵循 Checks-Effects-Interactions 模式
pub fn withdraw(...) {
// 1. 先更新状态
// 2. 然后发送资金
}
复现步骤
- 部署恶意合约
- 存入 1000 umsg
- 调用 withdraw(500)
- 在 reply handler 中再次调用 withdraw(500)
- 共提取 1000 umsg,余额记录为 500
参考资料
- The DAO 攻击 (2016)
- OWASP 重入攻击指南
### 7.2 严重等级分类
┌─────────────────────┬─────────────────────────────────────┐
│ 等级 │ 定义 │
├─────────────────────┼─────────────────────────────────────┤
│ 🔴 Critical (9.0-10)│ 可直接导致资金损失或合约完全失控 │
│ 🟠 High (7.0-8.9) │ 可导致部分资金损失或关键功能失效 │
│ 🟡 Medium (4.0-6.9) │ 可导致合约逻辑异常或非关键资金风险 │
│ 🟢 Low (0.1-3.9) │ 违反最佳实践,但实际利用困难 │
│ ⚪ Info (0) │ 信息性发现或建议 │
└─────────────────────┴─────────────────────────────────────┘
### 7.3 CVSS for CosmWasm
CVSS 向量示例:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
AV (攻击向量): N=网络 A=相邻 L=本地 P=物理
AC (复杂度): L=低 H=高
PR (权限): N=无 L=低 H=高
S (范围): U=不可变 C=可变
严重等级映射:
0.0 - 3.9 Low
4.0 - 6.9 Medium
7.0 - 8.9 High
9.0 - 10.0 Critical
### 7.4 修复跟踪
```markdown
| 发现 ID | 等级 | 描述 | 状态 | 负责人 | 预计修复 | 实际修复 | 验证人 |
|---------|------|------|------|--------|----------|----------|--------|
| VULN-001 | Critical | 重入攻击 | 🔴 待修复 | @alice | 2026-07-15 | - | @bob |
| VULN-002 | High | 缺少权限检查 | 🟡 验证中 | @bob | 2026-07-10 | 2026-07-08 | @alice |
| VULN-003 | Medium | 精度损失 | 🟢 已修复 | @charlie | 2026-07-05 | 2026-07-04 | @alice |
| VULN-004 | Low | 事件参数缺失 | ✅ 已接受 | @dave | - | - | @alice |
7.5 完整报告模板
# 安全审计报告
**项目名称**: [合约名称]
**代码版本**: [Git commit hash]
**审计日期**: 2026-07-06 至 2026-07-20
**审计团队**: [团队名称]
**审计类型**: 完整安全审计
---
## 1. 执行摘要
[1-2 段描述审计范围、关键发现和总体安全状况]
## 2. 审计范围
| 合约 | 文件路径 | 代码行数 | commit |
|------|----------|----------|--------|
| Token | contracts/token | 450 | abc123 |
| Staking | contracts/staking | 780 | abc124 |
### 审计覆盖
- ✅ 静态代码分析
- ✅ 手动代码审查
- ✅ 模糊测试 (10,000 用例)
- ✅ Gas 分析
- ✅ 依赖审计
## 3. 关键发现总结
| 严重等级 | 数量 | 已修复 | 待修复 | 已接受 |
|----------|------|--------|--------|--------|
| Critical | 2 | 2 | 0 | 0 |
| High | 5 | 3 | 1 | 1 |
| Medium | 8 | 6 | 1 | 1 |
| Low | 12 | 8 | 2 | 2 |
| **总计** | **27** | **19** | **4** | **4** |
## 4. 详细发现项
### [Critical] VULN-2026-001: [标题]
...
## 5. 代码质量
- 代码组织: ⭐⭐⭐⭐☆
- 测试覆盖: ⭐⭐⭐☆☆
- 文档质量: ⭐⭐⭐☆☆
- 错误处理: ⭐⭐⭐⭐☆
## 6. 建议
1. 短期(1-2周): 修复所有 Critical 和 High
2. 中期(1月): 增加模糊测试和集成测试
3. 长期(3月): 形式化验证 + 监控告警
8. 安全部署检查清单
8.1 部署前检查
### □ 合约代码
- [ ] 所有 `unwrap()` 被适当地错误处理替代
- [ ] 所有 `Addr::unchecked` 被 `addr_validate` 替代
- [ ] 所有算术使用 `checked_*` 操作
- [ ] 不存在未使用的导入或变量
- [ ] clippy 检查通过(零警告)
- [ ] `cargo audit` 通过
### □ 测试
- [ ] 单元测试覆盖 > 90%
- [ ] 所有边界条件被测试
- [ ] 错误路径被测试
- [ ] 集成测试覆盖主要交易流程
- [ ] Gas 基准测试已建立
### □ 存储
- [ ] 存储键使用版本化前缀
- [ ] 不存在键碰撞
- [ ] 迁移路径已定义并测试
- [ ] 存储迭代有限制
### □ 权限
- [ ] 管理员函数有权限检查
- [ ] 紧急暂停功能已实现
- [ ] 多签已配置
- [ ] Owner 地址已安全管理
### □ 外部依赖
- [ ] 所有预言机地址已白名单
- [ ] IBC 通道已验证
- [ ] 跨合约调用已验证
- [ ] `@msg-chain/sdk` 版本已验证
### □ 部署配置
- [ ] 正确的 ChainID (`msgchain-1`)
- [ ] 正确的 gas 限制
- [ ] 正确的存储限制
- [ ] 验证者抵押已确认
8.2 多签部署流程
// ✅ 多签部署模块
const REQUIRED_SIGNATURES: u32 = 3;
const MULTISIG_MEMBERS: [&str; 5] = ["alice", "bob", "charlie", "dave", "eve"];
const PENDING_DEPLOY: Item<PendingDeploy> = Item::new("pending_deploy_v1");
const APPROVALS: Map<&Addr, bool> = Map::new("approvals_v1");
pub struct PendingDeploy {
pub code_id: u64,
pub label: String,
pub admin: Option<String>,
pub msg: Binary,
pub created_at: u64,
}
#[entry_point]
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
match msg {
ExecuteMsg::ApproveDeploy => {
let pending = PENDING_DEPLOY.load(deps.storage)?;
if env.block.time.seconds() - pending.created_at > 86400 {
return Err(StdError::generic_err("Proposal expired"));
}
let is_member = MULTISIG_MEMBERS.contains(&info.sender.as_str());
if !is_member {
return Err(StdError::generic_err("Not a multisig member"));
}
APPROVALS.save(deps.storage, &info.sender, &true)?;
let approval_count = MULTISIG_MEMBERS.iter()
.filter(|m| APPROVALS.may_load(deps.storage, &Addr::unchecked(**m))
.unwrap_or(Some(false)).unwrap_or(false))
.count() as u32;
if approval_count >= REQUIRED_SIGNATURES {
Ok(Response::new().add_attribute("action", "deploy_approved"))
} else {
Ok(Response::new().add_attribute("action", "approval_added")
.add_attribute("approvals", approval_count.to_string()))
}
}
// ...
}
}
8.3 紧急暂停/升级
// ✅ 紧急暂停机制
#[derive(Serialize, Deserialize)]
pub struct EmergencyState {
pub paused: bool,
pub paused_by: Option<Addr>,
pub paused_at: Option<u64>,
pub pause_reason: Option<String>,
}
const EMERGENCY: Item<EmergencyState> = Item::new("emergency_v1");
pub fn assert_not_paused(deps: &Deps) -> StdResult<()> {
let emergency = EMERGENCY.load(deps.storage)?;
if emergency.paused {
return Err(StdError::generic_err("Contract is paused"));
}
Ok(())
}
#[entry_point]
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
assert_not_paused(&deps.as_ref())?;
match msg {
ExecuteMsg::EmergencyPause { reason } => {
let config = CONFIG.load(deps.storage)?;
if info.sender != config.admin {
return Err(StdError::generic_err("Unauthorized"));
}
EMERGENCY.save(deps.storage, &EmergencyState {
paused: true,
paused_by: Some(info.sender),
paused_at: Some(env.block.time.seconds()),
pause_reason: Some(reason),
})?;
Ok(Response::new().add_attribute("action", "emergency_pause"))
}
ExecuteMsg::EmergencyUnpause => {
let config = CONFIG.load(deps.storage)?;
if info.sender != config.admin {
return Err(StdError::generic_err("Unauthorized"));
}
EMERGENCY.save(deps.storage, &EmergencyState {
paused: false, paused_by: None,
paused_at: None, pause_reason: None,
})?;
Ok(Response::new().add_attribute("action", "emergency_unpause"))
}
// 其他消息
}
}
8.4 事故响应计划
## 事故响应计划
### 检测阶段
1. 监控合约异常交易(大额转账、频繁调用)
2. 监控验证者集异常变化
3. 监控 Agent 异常行为
4. 监控合约暂停状态
5. 监控链上事件/属性异常
### 遏制阶段
1. **暂停合约**: 调用 `EmergencyPause`
2. **限制 Agent**: 更新 Agent 宪法为只读
3. **通知验证者**: 通过多签通道通知
4. **收集证据**: 记录异常交易 hash
5. **隔离资金**: 必要时迁移至安全合约
### 恢复阶段
1. **分析根因**: 确定漏洞类型和触发条件
2. **开发修复**: 编写并通过测试
3. **测试修复**: 在本地测试网完整测试
4. **治理投票**: 提交升级提案给验证者
5. **部署修复**: 通过多签部署
6. **恢复资金**: 评估恢复方案
7. **取消暂停**: 验证修复后解暂停
### 事后总结
1. 编写事故报告
2. 根本原因分析
3. 更新安全流程
4. 添加新监控指标
5. 团队复盘
9. 事故响应
9.1 检测方法
// ✅ 合约级异常检测
pub fn detect_anomalies(
deps: DepsMut,
env: Env,
_info: MessageInfo,
) -> StdResult<Response> {
let config = MONITOR_CONFIG.load(deps.storage)?;
let mut alerts = Vec::new();
// 1. 检测大额转账
let recent_transfers: Vec<_> = RECENT_TRANSFERS
.range(deps.storage, None, None, Order::Descending)
.take(100).collect();
let total_volume: Uint128 = recent_transfers.iter()
.map(|(_, t)| t.amount).sum();
if total_volume > config.daily_volume_threshold {
alerts.push(format!("High volume: {}", total_volume));
}
// 2. 检测频繁调用
let call_count = CALL_FREQUENCY
.may_load(deps.storage, &(env.block.time.seconds() / 3600))?
.unwrap_or(0);
if call_count > config.max_calls_per_hour {
alerts.push(format!("Rate limit: {} calls/hour", call_count));
}
// 3. 检测异常验证者活动
let validator_changes = VALIDATOR_CHANGES
.may_load(deps.storage)?.unwrap_or(0);
if validator_changes > config.max_validator_changes {
alerts.push("Abnormal validator changes".to_string());
}
if !alerts.is_empty() {
let alert = SecurityAlert {
alerts: alerts.clone(),
timestamp: env.block.time.seconds(),
block_height: env.block.height,
};
SECURITY_ALERTS.save(deps.storage, &env.block.height, &alert)?;
}
Ok(Response::new()
.add_attribute("action", "anomaly_detection")
.add_attribute("alerts", alerts.len().to_string()))
}
9.2 遏制流程
// ✅ 遏制操作 — 暂停所有非关键操作
#[entry_point]
pub fn emergency_containment(
deps: DepsMut,
env: Env,
_info: MessageInfo,
) -> StdResult<Response> {
// 停止所有转账
EMERGENCY.save(deps.storage, &EmergencyState {
paused: true,
paused_by: Some(Addr::unchecked("system")),
paused_at: Some(env.block.time.seconds()),
pause_reason: Some("Security incident".to_string()),
})?;
// 锁定所有 Agent
let agents: Vec<Addr> = ALL_AGENTS
.keys(deps.storage, None, None, Order::Ascending)
.collect();
for agent_id in agents {
AGENTS.update(deps.storage, &agent_id, |agent| -> StdResult<_> {
let mut a = agent.unwrap();
a.paused = true;
a.constitution = Some(Constitution::read_only());
Ok(a)
})?;
}
Ok(Response::new()
.add_attribute("action", "emergency_containment")
.add_attribute("agents_locked", agents.len().to_string()))
}
// 资金快照
pub fn snapshot_funds(deps: DepsMut, env: Env) -> StdResult<Response> {
let snapshot = FundsSnapshot {
block_height: env.block.height,
timestamp: env.block.time.seconds(),
contract_balance: deps.querier.query_all_balances(&env.contract.address)?,
};
SNAPSHOTS.save(deps.storage, &env.block.height, &snapshot)?;
Ok(Response::new().add_attribute("action", "funds_snapshotted"))
}
9.3 事后分析模板
# 安全事件事后分析
**事件日期**: 2026-07-06
**报告日期**: 2026-07-07
**报告人**: [姓名]
**严重等级**: [P0/P1/P2]
## 概述
[1-2 段描述事件概要]
## 时间线
| 时间 (UTC) | 事件 |
|------------|------|
| 14:23:15 | 攻击者合约部署 |
| 14:23:45 | 第一次异常交易 |
| 14:24:00 | 检测到异常活动 |
| 14:24:15 | 触发 EmergencyPause |
| 14:25:00 | 验证者收到通知 |
| 15:00:00 | 漏洞分析完成 |
| 16:00:00 | 修复补丁部署 |
## 根本原因
[描述漏洞的技术原因]
## 影响
- 受影响用户: [数量]
- 资金损失: [金额]
- 波及合约: [列表]
## 修复措施
1. [修复 1]
2. [修复 2]
## 后续改进
1. [改进 1]
2. [改进 2]
## 行动项
| 行动项 | 负责人 | 截止日期 | 状态 |
|--------|--------|----------|------|
| [行动] | @name | 2026-07-13 | 进行中 |
## 附件
- 交易哈希: [tx_hash]
- 攻击合约地址: [address]
- 相关代码 commit: [commit]
MSG Chain Whitepaper | https://msgchain.org/whitepaper | 代码库实际状态,不代表生产可用
