欢迎光临
我们一直在努力

2.85 亿美元 Drift 协议被黑: 这不是代码漏洞,而是治理失效

2.85 亿美元 Drift 协议被黑:这不是代码漏洞,而是治理失效

2026 年 4 月 1 日,当 2.85 亿美元从 Drift 协议不翼而飞时,加密社区的第一反应是寻找“老面孔”:智能合约漏洞、闪电贷攻击,或者私钥被盗。但上述任何一种都不成立。这次事件远比表面复杂——它是一场持续数月的社交工程行动,攻击者将 Solana 的一个合法特性“持久化 nonce”(durable nonce)武器化,直接对准了协议自身的治理结构。

攻击者没有“破解”代码,他们“攻破”的是代码背后的人为流程。

接下来,我将带你一步步拆解整个过程,每个环节代码在做什么,以及最关键的一点:如何避免重蹈覆辙。


六个月的前奏:信任如何变成武器

这次攻击并非一次性漏洞利用,而是一场精心策划的持久战,始于 2025 年底。

攻击者伪装成一家量化交易机构,在各大加密会议上主动接触 Drift 的核心贡献者。此后,他们通过 Telegram、线上协作会议以及多场全球性活动的线下会面,与团队保持密切沟通。他们甚至向 Drift 存入了超过 100 万美元的真实资金,并积极参与策略讨论。这一切都是为了构建信任、积累人脉,同时为后续利用埋下伏笔。


第一阶段:制造虚假资产

2026 年 3 月 11 日,攻击者从 Tornado Cash 提取了 10 个 ETH,部署了 CarbonVote Token(CVT),并铸造了 7.5 亿枚。他们控制了大约 80% 的供应量。

随后,他们制造了一场“假象”。在 Raydium 上,他们用大约 500 美元的真实流动性建立了小额资金池,并在自己控制的多个钱包之间进行洗盘交易,让 CVT 看起来像是稳定在 1 美元的真实交易对。他们还部署了自己的价格预言机,将这个虚构的价格喂给 Drift 协议。从外部系统来看,CVT 就像一个有真实需求、有稳定价格的合规代币。


第二阶段:埋下持久化 nonce 的陷阱

3 月 23 日至 30 日之间,攻击者创建了多个持久化 nonce 账户。这正是技术利用的核心起点。


代码解析:持久化 nonce 到底是怎么回事

在正常的 Solana 交易中,交易会附带一个近期区块哈希(recent blockhash),如果未及时广播,交易很快就会失效。这其实是一层隐形的安全屏障:签名后的交易要么立即执行,要么很快作废。

而持久化 nonce 则用存储在专用账户中的 nonce 值取代了那个短命的区块哈希,使得已签名的交易可以无限期保持有效。这在某些合法场景下很有用,比如离线签名或延迟提交。

下面是一个典型的持久化 nonce 交易代码示例:

// 必须将推进 nonce 账户作为第一条指令
let advance_ix = system_instruction::advance_nonce_account(
&nonce_account_pubkey,
&authority_pubkey,
);

// 交易使用 nonce 值作为 recent_blockhash
let message = Message::new_with_nonce(
&[advance_ix, target_ix],
Some(&payer_pubkey),
&nonce_account_pubkey,
&authority_pubkey,
);

// 一旦签名,这笔交易可以无限期存放
let signed_tx = sign_transaction(&message, &[&payer_keypair, &authority_keypair]);
// 攻击者可以等待数天甚至数周后再广播

关键风险在于:一旦签名者批准了一笔持久化 nonce 交易,除非 nonce 权限账户被手动推进,否则签名者无法撤销自己的签名。


致命缺陷:签名与执行相分离

在普通基于区块哈希的交易中,短有效期就像一道隐形闸门。即使签名者被诱骗签署了恶意交易,攻击者也必须在极短的时间内广播,这大大限制了大规模利用的可行性。

而持久化 nonce 彻底移除了这个限制。一次被骗的签名不再在几分钟内失效,而是无限期可用,攻击者可以完全掌控执行时机。

攻击者正是利用这一点,让安全委员会的五位成员中的两位签署了看起来无害或常规的交易,但这些交易实际上包含转移管理权限的指令。


第三阶段:移除了最后一道防线

3 月 26 日,Drift 迁移到了一个新的 2/5 阈值安全委员会多签,并且没有时间锁。这彻底消除了任何可能用于发现和干预的延迟窗口。

攻击者随后又获得了新多签中两位成员的签名,再次凑齐了执行管理操作所需的 2/5 法定人数。


第四阶段:执行

4 月 1 日 16:05:18 UTC,攻击者提交了第一笔预签名交易:

// 交易 1:创建并批准恶意管理员转移提案
// 此处利用持久化 nonce 绕过了区块哈希过期机制
let tx1 = Transaction::new_with_nonce(
&[proposal_create_ix, proposal_approve_ix],
Some(&payer),
&nonce_account,
&authority,
);
// 提案将管理员权限转移至攻击者地址:H7PiGqqUaanBovwKgEtreJbKmQe6dbq6VTrw6guy7ZgL

一秒之后,即 16:05:19 UTC,第二笔交易执行:

// 交易 2:批准并执行该提案
// 首先推进 nonce 账户,然后执行金库交易
let tx2 = Transaction::new_with_nonce(
&[
advance_nonce_account_ix, // 必须放在第一条
proposal_approve_ix,
vault_transaction_execute_ix, // 此处调用 UpdateAdmin
],
Some(&payer),
&nonce_account,
&authority,
);

执行流程为:AdvanceNonceAccount -> proposalApprove -> vaultTransactionExecute -> UpdateAdmin。

仅仅两笔交易,间隔一秒,攻击者便获得了完整的管理权限。


Squads 多签:代码实际在做什么

Drift 使用的是 Squads 多签框架。在 Squads 中,特权操作被打包成提案中的交易。一旦获得足够成员的批准,这些存储的指令就可以通过 vaultTransactionExecute 原子化执行。

下面是相关代码路径:

// 创建提案 —— 签名者以为自己在批准什么
const proposalCreateIx = await multisig.instructions.proposalCreate({
multisigPda: multisigAddress,
transactionIndex: nextIndex,
// 签名者看到的是一次“无害的管理操作”
// 但实际上攻击者精心构造了转移管理员权限的指令
});

// 批准 —— 签名者认为只是常规操作
const proposalApproveIx = await multisig.instructions.proposalApprove({
multisigPda: multisigAddress,
transactionIndex: index,
member: signerPublicKey,
});

// 执行 —— 数天后由攻击者触发
const executeIx = await multisig.instructions.vaultTransactionExecute({
multisigPda: multisigAddress,
transactionIndex: index,
// 这里执行的是预先批准的指令
// 签名者的私钥从未被泄露
});

问题在于:签名者批准了提案,却没有真正理解提案里包含什么。系统只校验了签名的有效性,却没有校验意图的安全性。


第五阶段:资金洗劫

获得管理员权限后,攻击者执行了三步提取操作:

  • 为 CVT 创建了一个恶意的抵押品市场,并设置了极其宽松的风险参数
  • 切换到攻击者控制的预言机,人为抬高 CVT 的价格
  • 解除提款保护和熔断机制
  • 然后,他们存入了 5 亿枚 CVT,由于人为抬价,系统认为这些 CVT 价值约 5 亿美元。

    攻击者在大约 12 分钟内连续执行了 31 次提款,抽走了真实资产:

    • USDC:7140 万美元
    • JLP:1.593 亿美元
    • cbBTC:1130 万美元
    • USDT:560 万美元
    • USDS:530 万美元
    • WETH:470 万美元
    • dSOL:450 万美元
    • WBTC:440 万美元
    • 以及其他多种资产

    总损失:285,279,417.69 美元。


    核心问题:这不是“漏洞”

    此次事件并非智能合约漏洞或私钥泄露所致,而是多签授权流程的失效,外加持久化 nonce 带来的延迟执行风险。

    攻击者通过误导性签名,预先收集了安全委员会五位成员中两位的有效多签批准,并利用持久化 nonce 交易将其长期保存,随后再执行以获得管理权限。

    决定性的授权并非发生在执行时刻,而是更早的签名阶段。链上交易不过是把已经授予的权限“兑现”而已。


    如何修复:具体方案

    方案一:为高特权操作增加时间锁

    管理操作(如所有权转移)不应立即执行。Drift 的零时间锁配置意味着,一旦预签名交易被触发,管理权限在几分钟内就被转移并遭到利用。

    // 原来的立即执行方式:
    pub fn update_admin(ctx: Context<UpdateAdmin>, new_admin: Pubkey) -> Result<()> {
    // ❌ 危险:立即执行
    ctx.accounts.protocol.admin = new_admin;
    Ok(())
    }

    // 改为带时间锁的版本:
    pub fn propose_update_admin(ctx: Context<ProposeUpdateAdmin>, new_admin: Pubkey) -> Result<()> {
    ctx.accounts.timelock_proposal = TimelockProposal {
    proposed_admin: new_admin,
    proposed_at: Clock::get()?.unix_timestamp,
    execution_time: Clock::get()?.unix_timestamp + TIMELOCK_DELAY, // 比如 24 小时
    executed: false,
    };
    Ok(())
    }

    pub fn execute_update_admin(ctx: Context<ExecuteUpdateAdmin>) -> Result<()> {
    let proposal = &ctx.accounts.timelock_proposal;
    require!(
    Clock::get()?.unix_timestamp >= proposal.execution_time,
    ErrorCode::TimelockNotExpired
    );
    require!(!proposal.executed, ErrorCode::AlreadyExecuted);
    // 现在才执行管理员转移
    ctx.accounts.protocol.admin = proposal.proposed_admin;
    proposal.executed = true;
    Ok(())
    }

    加入时间锁后,就能留出响应窗口,在这个窗口内发现并阻止恶意转移。


    方案二:在治理场景中限制持久化 nonce 的使用

    持久化 nonce 将签名与执行解耦,移除了签名者所依赖的隐式时间保证。在治理系统中,使用该机制时必须附加额外的安全措施:

    // 对于治理级关键操作,要求:
    // 1. 更高的签名阈值(例如 3/5 而非 2/5)
    // 2. 即使使用持久化 nonce,也要有时间限制的批准
    // 3. 可撤销的批准

    pub struct GovernanceProposal {
    pub instructions: Vec<Instruction>,
    pub approvals: Vec<Pubkey>,
    pub created_at: i64,
    pub expires_at: i64, // 即使有持久化 nonce,治理批准也必须过期
    pub executed: bool,
    pub revoked: bool,
    }

    // 签名者可以在执行前撤销批准
    pub fn revoke_approval(ctx: Context<RevokeApproval>) -> Result<()> {
    let proposal = &mut ctx.accounts.proposal;
    require!(!proposal.executed, ErrorCode::AlreadyExecuted);
    proposal.approvals.retain(|&a| a != ctx.accounts.signer.key());
    Ok(())
    }


    方案三:执行前的意图评估

    Drift 攻击之所以成功,就在于所有操作从技术上看都是“合法”的,没有一个明显恶意的痕迹。

    类似 Hexagate 的 GateSigner 这类方案,会在交易执行前评估它“做了什么”,而不仅仅是“谁签了字”。

    // 执行前检查示例:
    async function evaluateTransaction(tx: Transaction): Promise<EvaluationResult> {
    const instructions = tx.instructions;

    for (const ix of instructions) {
    // 检查 1:这是敏感的管理操作吗?
    if (isAdminInstruction(ix)) {
    const target = extractAdminTarget(ix);
    // 目标地址是否是刚创建不久的新账户?
    if (await isNewlyCreatedAccount(target)) {
    return {
    action: "BLOCK",
    reason: `管理员权限正转移至新创建账户:${target}`
    };
    }
    }

    // 检查 2:是否在添加新的抵押资产?
    if (isAddCollateralInstruction(ix)) {
    const token = extractTokenAddress(ix);
    const tokenAge = await getTokenAge(token);
    const liquidity = await getLiquidityDepth(token);
    // CVT 仅在 20 天前创建,且几乎没有真实流动性
    if (tokenAge < 30|| liquidity < MIN_LIQUIDITY) {
    return {
    action: "REQUIRE_ADDITIONAL_APPROVAL",
    reason: `可疑的抵押资产:${token}`
    };
    }
    }

    // 检查 3:是否在移除提款限额?
    if (isRemoveWithdrawalLimit(ix)) {
    return {
    action: "BLOCK",
    reason: "试图移除提款限额"
    };
    }
    }

    return { action: "ALLOW" };
    }


    方案四:自动化熔断机制

    在 Drift 事件中,由于缺乏自动化控制,即使金库资金持续流失超过两小时,也没有任何熔断被触发。

    // 实现自动熔断:
    pub struct CircuitBreaker {
    pub withdrawal_count: u64,
    pub withdrawal_amount: u64,
    pub last_withdrawal_time: i64,
    pub paused: bool,
    }

    pub fn check_circuit_breaker(state: &CircuitBreaker) -> Result<()> {
    require!(!state.paused, ErrorCode::ProtocolPaused);

    // 跨多个金库的快速大额提款
    if state.withdrawal_count > MAX_WITHDRAWALS_PER_MINUTE {
    return Err(ErrorCode::CircuitBreakerTriggered.into());
    }

    if state.withdrawal_amount > MAX_WITHDRAWAL_AMOUNT_PER_HOUR {
    return Err(ErrorCode::CircuitBreakerTriggered.into());
    }

    Ok(())
    }


    真正的教训

    多签安全远不止于“保管好私钥”。保护私钥、执行签名阈值只是基础,整个授权链条——包括交易构造、展示、签名者理解——都必须被信任。

    在这次事件中,签名者的私钥并未泄露,但批准流程被操纵,导致他们授权了本不该执行的操作。

    攻击之所以得手,原因在于:

  • 安全委员会使用 2/5 阈值且零时间锁
  • 持久化 nonce 使签名可无限期有效
  • 签名者被诱骗“盲签”了自己并不完全理解的交易
  • 没有执行前检查来评估交易的真正意图
  • 当异常提款模式出现时,没有任何熔断机制触发
  • 这是一次由技术特性助长的治理失效。代码本身运行得“完美无缺”,问题在于设计时没有考虑到“人”的因素。

    随着 DeFi 基础设施日益复杂、操作链路越来越长,这类事件警示我们:当前最大的风险已不再仅仅藏于智能合约之中,而是存在于包围代码的系统与人之间。

    赞(0)
    未经允许不得转载:171主机测评 » 2.85 亿美元 Drift 协议被黑: 这不是代码漏洞,而是治理失效
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址