Solana Upgrade Authority 보안: 대부분의 프로토콜이 배우지 못한 $40M 교훈

발행: (2026년 3월 14일 오전 05:34 GMT+9)
14 분 소요
원문: Dev.to

Source: Dev.to

죄송합니다만, 번역하려는 전체 텍스트를 제공해 주시면 해당 내용을 한국어로 번역해 드리겠습니다. 현재는 위의 소스 링크만 포함되어 있어 번역할 본문이 없습니다. 텍스트를 복사해서 알려 주시면 바로 번역해 드리겠습니다.

문제

solana program deploy 로 배포된 모든 Solana 프로그램은 기본적으로 업그레이드 가능합니다.
업그레이드 권한 – 단일 키페어 – 은 프로토콜에 갓모드 접근 권한을 가집니다: 프로그램 전체 바이너리를 교체할 수 있습니다. 타임락 없음. 멀티시그 없음. 경고도 없습니다.

2026년 1월 Step Finance 붕괴($40 M 유출)는 스마트‑컨트랙트 버그가 아니었습니다.
이는 업그레이드 권한 키를 보관하고 있던 임원용 디바이스가 탈취된 것이었습니다. 공격자는 코드 취약점이 필요 없었고, 프로그램을 교체할 키만 있으면 되었습니다.

만약 Solana 프로그램의 업그레이드 권한이 단일 핫 월렛이라면, 같은 운명을 맞이할 피싱 이메일 한 통만 남은 것입니다.

업그레이드 권한 작동 방식

Solana 프로그램을 배포하면 BPF Loader가 다음을 생성합니다:

  1. 실행 가능한 바이트를 포함하는 데이터 계정을 가리키는 프로그램 계정.
  2. 프로그램 계정 메타데이터에 저장된 업그레이드‑권한(pubkey).

해당 키를 제어하는 사람은 BPFLoaderUpgradeable::Upgrade를 호출해 전체 프로그램 바이너리를 교체할 수 있습니다.

예시: 프로그램을 누가 제어하고 있는지 확인하기

solana program show 

출력

Program Id:          YourProgram111111111111111111111111
Owner:               BPFLoaderUpgradeab1e11111111111111111111111
ProgramData Address: ProgramData111111111111111111111
Authority:           HotWa11et1111111111111111111111111  ← 단일 실패 지점
Last Deployed In:    Slot 250000000
Data Length:         832456 bytes

Authority 필드가 바로 공격 대상입니다. 공격자가 이 키를 입수하면 다음을 할 수 있습니다:

  • 모든 PDA를 탈취하는 악성 바이너리를 배포한다.
  • 접근 제어를 우회하도록 프로그램 로직을 교체한다.
  • 시간이 지남에 따라 자금을 빼돌리는 백도어를 삽입한다.
  • 프로그램을 완전히 사용할 수 없게 만든다.

기본 권한 = 개발자의 로컬 키페어 (~/.config/solana/id.json).
이는 60 % 이상의 Solana 프로그램이 메인넷에 배포되는 방식이며, 여러분 프로그램의 보안은 노트북 보안과 동일합니다.

위험 요소: 악성코드, 피싱, 기기 도난, 내부자 위협.

Source:

업그레이드 권한 보안

1. 권한을 Ledger(하드웨어 지갑)로 이전

solana program set-upgrade-authority  \
  --new-upgrade-authority  \
  --keypair usb://ledger

장점: 키가 핫 머신에 절대 노출되지 않음.
단점: 여전히 단일 키가 한 사람에게만 존재 → 버스 팩터 = 1.

위험: 물리적 도난, 강제, 단일 인물에 의한 타협.

2. 멀티시그 사용 (Squads Protocol – 표준 Solana 멀티시그)

import { Squads } from "@sqds/sdk";

// Create a multisig vault for upgrade authority
const multisigAccount = await squads.createMultisig(
  2,               // threshold: 2‑of‑3 required
  createKey,
  [
    member1.publicKey, // CTO – hardware wallet
    member2.publicKey, // Lead Dev – hardware wallet
    member3.publicKey, // Security Lead – hardware wallet
  ]
);

// Transfer program upgrade authority to multisig
await squads.createProposalForProgramUpgrade(
  multisigAccount,
  programId,
  bufferAddress,
  spillAddress,
  "v2.1.0 – Fix oracle validation"
);

결과: 2‑of‑3 키 보유자가 업그레이드를 승인해야 함. 공격자는 서로 다른 보안 경계에 있는 여러 당사자를 동시에 타협해야 함.

위험: 여러 구성원을 대상으로 한 사회공학, 거버넌스 공격, 비상 대응 지연.

3. 제안과 실행 사이에 필수 지연(타임락) 추가

예시: 48시간 타임락이 적용된 커스텀 업그레이드 프로그램 (Anchor)

// ── Propose Upgrade ───────────────────────────────────────────────────────
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(())
}

// ── Execute Upgrade ───────────────────────────────────────────────────────
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);

    // Execute the BPFLoaderUpgradeable::Upgrade instruction via CPI
    // ...

    Ok(())
}

// ── Cancel Upgrade ───────────────────────────────────────────────────────
pub fn cancel_upgrade(ctx: Context) -> Result {
    // Any single guardian can cancel during the timelock window
    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 시간 동안 다음을 수행할 수 있음:

  • 무단 업그레이드 제안을 탐지한다.
  • 단일 가디언이 이를 취소한다.
  • 사용자에게 자금을 인출하도록 경고한다.

위험: 긴급 버그를 패치하는 데 시간이 더 걸림 (긴급 패스를 높은 임계값으로 두어 완화 가능).

4. 핵 옵션: 업그레이드 권한을 영구적으로 폐기

solana program set-upgrade-authority  --final

프로그램은 다시는 변경될 수 없음. 이는 보안상의 최상위 수준이지만 다음을 의미함:

  • 버그 수정이 불가능.
  • 기능 추가가 불가능.
  • 코드가 완벽히 올바른지 확신해야 함.

최적 활용 사례: 핵심 AMM 로직, 토큰 계약, 브리지(광범위한 감사를 마친 후).

모두 합쳐 보기 – 성숙도 패턴

경로거버넌스타임락실행 속도
일반 업그레이드2‑of‑3 멀티시그48 h표준
긴급4‑of‑5 멀티시그 (확장된 위원회)4 h빠름
중대한 (활성 악용)5‑of‑5 멀티시그0 h (즉시)즉시 + 자동 일시정지

핵심 인사이트: 긴급도가 높을수록 합의가 더 많이 필요하며, 낮아지는 것이 아닙니다. 정말 즉시 배포가 필요하다면, 모든 5명의 서명자가 참여할 수 있어야 합니다. 이는 위험이 가장 클 때입니다.

Source:

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

Audit Checklist (based on 2026 incidents such as 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 Upgrade and SetAuthority instructions → UpgradeSetAuthority 명령에 대한 실시간 알림
  • 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 (출시)즉시2‑of‑3 멀티시그, 24 h 타임락
단계 26 개월3‑of‑5 멀티시그, 48 h 타임락, ≥2개의 감사 완료
단계 31 년핵심 로직 동결; 주변 모듈만 업그레이드 가능
단계 42년 이상광범위한 실전 테스트 후 완전 불변성 달성

각 단계는 사용자에게 공개하는 약속입니다. 현재 단계와 다음 단계로 진행하기 위한 기준을 문서화하십시오.

Key Takeaways

  • The $40 M loss at Step Finance shows that a program is only as secure as its upgrade authority.
  • A perfectly audited contract is worthless if the key to replace it sits on an executive’s laptop.
  • The fix isn’t exotic cryptography; it’s operational discipline:
    1. Move to multisig today.
    2. Add a timelock this quarter.
    3. Plan your path to immutability.
    4. Monitor everything.

Your users trust you with their funds. The least you can do is ensure a single compromised laptop can’t betray that trust.

DreamWork Security publishes weekly research on DeFi security patterns. Follow us for more Solana and EVM security analysis.

0 조회
Back to Blog

관련 글

더 보기 »

트라비고

Gemini와 함께 말하는 속도만큼 빠르게 여행하세요! 라이브 에이전트가 몰입형 스토리텔링 및 3D 내비게이션과 만나는 곳. 이 프로젝트는 Gemini Live Ag...에 진입하기 위해 만들어졌습니다.