Solana Upgrade Authority 安全:大多数协议未学到的 $40M 教训
Source: Dev.to
请提供您希望翻译的具体文本内容,我将按照要求保留源链接并将文本翻译成简体中文。
问题
每个使用 solana program deploy 部署的 Solana 程序默认都是 可升级的。
升级权限——一个单独的密钥对——拥有对你的协议的 全权 访问:它可以替换整个程序二进制文件。没有时间锁。没有多签。没有警告。
2026 年 1 月 Step Finance 崩溃(被盗 4000 万美元)并非智能合约漏洞。
它是一个被攻破的高管设备,持有升级权限密钥。攻击者并不需要代码漏洞——只需要那个密钥就能替换程序。
如果你的 Solana 程序的升级权限是 单一热钱包,那么只需一封钓鱼邮件,你就可能遭遇同样的结局。
升级权限如何运作
当你部署一个 Solana 程序时,BPF Loader 会创建:
- 一个指向包含可执行字节的 data 账户的 program account。
- 存储在 program account 元数据中的 upgrade‑authority 公钥。
拥有该密钥的任何人都可以调用 BPFLoaderUpgradeable::Upgrade 来替换整个程序二进制文件。
示例:检查谁控制你的程序
solana program show 输出
Program Id: YourProgram111111111111111111111111
Owner: BPFLoaderUpgradeab1e11111111111111111111111
ProgramData Address: ProgramData111111111111111111111
Authority: HotWa11et1111111111111111111111111 ← 单点故障
Last Deployed In: Slot 250000000
Data Length: 832456 bytesAuthority 字段就是攻击目标。如果攻击者获取了此密钥,他们可以:
- 部署恶意二进制文件,耗尽所有 PDA。
- 替换程序逻辑以绕过访问控制。
- 插入后门,长期 siphon 资金。
- 完全使程序失效(brick)。
默认 authority = 开发者本地密钥对 (
~/.config/solana/id.json)。
这正是 60 %+ 的 Solana 程序上链的方式。你的程序安全性等同于你的笔记本电脑安全性。
风险:恶意软件、钓鱼、设备被盗、内部威胁。
确保升级权限的安全
1. 将权限转移到 Ledger(硬件钱包)
solana program set-upgrade-authority \
--new-upgrade-authority \
--keypair usb://ledger优点: 私钥从不接触热机器。
缺点: 仍然是 单一私钥 由单个人持有 → 单点故障因子 = 1。
风险: 实体盗窃、胁迫、单人泄露。
2. 使用多签(Squads Protocol – 标准 Solana 多签)
import { Squads } from "@sqds/sdk";
// 为升级权限创建多签金库
const multisigAccount = await squads.createMultisig(
2, // 阈值:需要 2‑of‑3 同意
createKey,
[
member1.publicKey, // CTO – 硬件钱包
member2.publicKey, // 首席开发 – 硬件钱包
member3.publicKey, // 安全负责人 – 硬件钱包
]
);
// 将程序升级权限转移到多签
await squads.createProposalForProgramUpgrade(
multisigAccount,
programId,
bufferAddress,
spillAddress,
"v2.1.0 – 修复预言机校验"
);结果: 2‑of‑3 持钥者必须批准任何升级。攻击者必须同时攻破多个拥有不同安全边界的成员。
风险: 对多名成员的社会工程攻击、治理攻击、应急响应延迟。
3. 在提案与执行之间加入强制延迟(时间锁)
示例:使用 Anchor 实现 48 小时时间锁的自定义升级程序
// ── 提出升级 ───────────────────────────────────────────────────────
pub fn propose_upgrade(ctx: Context, buffer: Pubkey) -> Result {
let proposal = &mut ctx.accounts.proposal;
proposal.buffer = buffer;
proposal.proposed_at = Clock::get()?.unix_timestamp;
proposal.execution_time = proposal.proposed_at + TIMELOCK_DURATION; // 48 h
proposal.approvals = 0;
proposal.executed = false;
emit!(UpgradeProposed {
program: ctx.accounts.program.key(),
buffer,
execution_time: proposal.execution_time,
});
Ok(())
}
// ── 执行升级 ───────────────────────────────────────────────────────
pub fn execute_upgrade(ctx: Context) -> Result {
let proposal = &ctx.accounts.proposal;
let now = Clock::get()?.unix_timestamp;
require!(proposal.approvals >= THRESHOLD, ErrorCode::InsufficientApprovals);
require!(now >= proposal.execution_time, ErrorCode::TimelockNotExpired);
require!(!proposal.executed, ErrorCode::AlreadyExecuted);
// 通过 CPI 调用 BPFLoaderUpgradeable::Upgrade 指令
// ...
Ok(())
}
// ── 取消升级 ───────────────────────────────────────────────────────
pub fn cancel_upgrade(ctx: Context) -> Result {
// 任意单个守护者可在时间锁窗口内取消
let proposal = &mut ctx.accounts.proposal;
require!(!proposal.executed, ErrorCode::AlreadyExecuted);
proposal.cancelled = true;
emit!(UpgradeCancelled {
program: ctx.accounts.program.key(),
cancelled_by: ctx.accounts.guardian.key(),
});
Ok(())
}效果: 即使攻击者获取了多签,社区仍有 48 h 的时间:
- 检测到未经授权的升级提案。
- 通过任意单个守护者进行取消。
- 通知用户撤回资金。
风险: 紧急漏洞的修复时间变长(可通过设置更高阈值的 紧急快速通道 来缓解)。
4. 核心选项:永久撤销升级权限
solana program set-upgrade-authority --final程序将 永远无法再被更改。这是安全的最高标准,但意味着:
- 无法再进行 bug 修复。
- 无法添加新功能。
- 必须对代码的正确性绝对确信。
适用场景: 核心 AMM 逻辑、代币合约、桥接合约(在经过充分审计后)。
综合应用 – 成熟度模式
| 路径 | 治理 | 时间锁 | 执行速度 |
|---|---|---|---|
| 常规升级 | 2‑of‑3 multisig | 48 h | 标准 |
| 紧急 | 4‑of‑5 multisig(扩展议会) | 4 h | 快速 |
| 关键(活跃漏洞) | 5‑of‑5 multisig | 0 h(立即) | 立即 + 自动暂停 |
关键洞察: 更高的紧迫性需要更高的共识,而不是更低的共识。 如果真的需要立即部署,收集 全部 5 位签署人 的签名应该是可行的,因为风险最高。
TL;DR 检查清单
- 绝不要在主网保留默认热钱包权限。
- 将权限转移到硬件钱包(最好是多签)。
- 为任何升级路径添加时间锁。
- 在经过实战测试后考虑冻结程序。
- 记录升级流程并对团队进行钓鱼/物理安全最佳实践的培训。
今天就确保升级权限的安全——这是保护您的 Solana 协议的最关键钥匙。
升级权限监控与安全检查清单
检测与防御同等重要。 为任何与您程序的升级权限交互的行为设置警报:
import asyncio
from datetime import datetime
from solders.pubkey import Pubkey
from solana.rpc.websocket_api import connect
PROGRAM_ID = Pubkey.from_string("YourProgram111111111111111111111111")
BPF_LOADER = Pubkey.from_string("BPFLoaderUpgradeab1e11111111111111111111111")
async def monitor_upgrades():
async with connect("wss://api.mainnet-beta.solana.com") as ws:
# Subscribe to any transaction mentioning the BPF Loader + your program
await ws.logs_subscribe(
filter_={"mentions": [str(BPF_LOADER)]},
commitment="confirmed"
)
async for msg in ws:
logs = msg[0].result.value.logs
signature = msg[0].result.value.signature
# Check if this upgrade targets our program
if any("Upgrade" in log for log in logs):
if any(str(PROGRAM_ID) in log for log in logs):
await send_alert(
f"🚨 PROGRAM UPGRADE DETECTED!\n"
f"Program: {PROGRAM_ID}\n"
f"Tx: {signature}\n"
f"Time: {datetime.utcnow()}\n"
f"ACTION REQUIRED: Verify this was authorized"
)
async def send_alert(message: str):
# Send to Telegram, Discord, PagerDuty, etc.
print(message)
asyncio.run(monitor_upgrades())同时监控 SetAuthority 指令
攻击者的第一步可能是将升级权限转移到他们自己的密钥:
# Quick check: has your authority changed?
solana program show | grep Authority审计清单(基于 2026 年诸如 Step Finance 的事件)
- Upgrade authority is NOT a single hot wallet → 升级权限 不是 单一热钱包
- Multisig with ≥2‑of‑3 threshold → 多签,阈值为 ≥2‑of‑3
- Each signer uses a hardware wallet → 每个签名者使用硬件钱包
- Signers are geographically distributed → 签名者地理位置分散
- No single person has access to the threshold number of keys → 没有单个人拥有阈值数量的密钥访问权限
- Backup/recovery procedure documented and tested → 备份/恢复流程已文档化并经过测试
- Timelock of ≥24 hours for normal upgrades → 正常升级的时间锁为 ≥24 小时
- Emergency path with higher threshold (not lower timelock) → 紧急路径使用 更高 阈值(而非更短时间锁)
- Program binary verified on‑chain before execution → 程序二进制在执行前已链上验证
- Buffer account contents independently verified by ≥2 parties → 缓冲账户内容由 ≥2 方独立验证
- Upgrade proposals publicly announced before execution → 升级提案在执行前公开宣布
- Real‑time alerts for
UpgradeandSetAuthorityinstructions →Upgrade和SetAuthority指令的实时警报 - Authority pubkey checked on every deployment → 每次部署时检查权限公钥
- On‑chain program hash verified against source code → 链上程序哈希与源代码进行核对
- Solana Explorer / Anchor Verifiable Builds used → 使用 Solana Explorer / Anchor 可验证构建
- Program pause mechanism exists (separate from upgrade authority) → 存在程序暂停机制(独立于升级权限)
- Emergency contact list for all multisig signers → 所有多签签名者的紧急联系人列表
- Written runbook for “upgrade authority compromised” scenario → 针对 “升级权限被泄露” 情景的书面运行手册
- Regular drills (quarterly) to test emergency upgrade flow → 定期演练(每季度)以测试紧急升级流程
分阶段实现不可变性
| 阶段 | 时间线 | 要求 |
|---|---|---|
| 阶段 1(Launch) | 立即 | 2‑of‑3 multisig,24 h timelock |
| 阶段 2 | 6 months | 3‑of‑5 multisig,48 h timelock,≥2 audits completed |
| 阶段 3 | 1 year | Core logic frozen;only peripheral modules upgradeable |
| 阶段 4 | 2+ years | Full immutability after extensive battle‑testing |
每个阶段都是对用户的公开承诺。记录您当前的阶段以及晋升到下一阶段的标准。
关键要点
- $40 M 的损失在 Step Finance 表明一个程序的安全性仅与其升级权限一样可靠。
- 即使合约经过完美审计,如果替换它的钥匙在高管的笔记本电脑上,也毫无价值。
- 解决方案并非高深的密码学,而是 运营纪律:
- 今天 就迁移到多签。
- 本季度添加时间锁。
- 规划实现不可变性的路径。
- 监控所有内容。
你的用户把资金托付给你。你能做的最基本的事就是确保单个被攻破的笔记本电脑不会背叛这种信任。
DreamWork Security 每周发布 DeFi 安全模式研究。关注我们获取更多 Solana 和 EVM 安全分析。