Back to Blog

~$598만 달러 손실: Aztec, Raydium 등 | BlockSec 위클리

Code Auditing
June 17, 2026
11 min read
Key Insights

지난 한 주(2026/06/08 - 2026/06/14) 동안 이더리움과 솔라나에서 4건의 주목할 만한 사건이 감지되었으며, 총 약 $5.98M의 손실이 발생하였습니다. 아래 표는 대표적인 사건들을 요약합니다:

날짜 사건 유형 추정 손실
2026/06/08 Flooring Protocol 정수 오버플로우 ~$900K
2026/06/09 Top Token 거버넌스 공격 ~$1.59M
2026/06/10 Raydium (솔라나) 입력 검증 부재 ~$1.34M
2026/06/14 Aztec 입력 검증 부재 ~$2.15M
  • Aztec: 롤업의 증명 경로와 L1 정산 경로 사이의 검증 격차로 인해 두 경로가 서로 다른 트랜잭션 집합을 처리하게 되어 불일치 상태에 도달하였습니다.
  • Raydium: 검증 확인 누락으로 인해 공격자가 LP 토큰 상환 계산을 조작할 수 있었으며, 네 개의 풀에서 전체 예비금을 탈취하였습니다.

Web3 최고의 보안 감사 기관

출시 전 설계, 코드, 비즈니스 로직을 검증하세요

주간 하이라이트: Aztec

이번 사건에서 ZK 증명 검증자와 L1 정산 로직은 단일 매개변수가 제한되지 않아 서로 다른 트랜잭션 집합을 처리하였습니다. 증명과 정산 사이의 이러한 일관성 격차는 두 경로가 별도의 코드로 실행되는 모든 롤업 설계에 적용됩니다.

2026년 6월 14일, 이더리움 기반의 프라이버시 중심 롤업인 Aztec Connect가 약 $2.15M의 공격을 받았습니다 [1]. 근본 원인은 검증된 롤업 트랜잭션 집합과 L1 정산 처리 경계 사이의 불일치로, ZK 증명 경로와 정산 로직이 서로 다른 트랜잭션 목록을 처리할 수 있었습니다. 공격자는 이 격차를 이용하여 롤업 상태에 뒷받침되지 않은 예치 잔액을 기록한 후, 일반적인 정산 흐름을 통해 인출하였습니다.

배경

Aztec Connect는 이더리움 기반의 프라이버시 중심 롤업으로, L2에서 프라이빗 트랜잭션을 가능하게 합니다. 사용자 자금은 L1에서 시작되므로, L2 머클 트리에서 노트로 표현되기 전에 먼저 롤업 프로세서 컨트랙트에 예치되어야 합니다.

예치 프로세스는 두 단계로 이루어집니다:

1단계: 사용자가 depositPendingFunds()를 호출하면, increasePendingDepositBalance()를 통해 userPendingDeposits[assetId][owner]가 증가하고 토큰이 RollupProcessor로 전송됩니다. 이로써 L1에 대기 중인 예치가 생성됩니다.

function depositPendingFunds(uint256 _assetId, uint256 _amount, address _owner, bytes32 _proofHash) external {
    increasePendingDepositBalance(_assetId, _owner, _amount);
    // ... 컨트랙트로 토큰 전송
}

2단계: 사용자가 예치 증명을 제출하면, 이후 롤업에 포함되어 L2 상태에 추가됩니다. processRollup()이 실행될 때, decodeProof()는 인코딩된 calldata에서 numTxs를 읽어 디코딩된 증명 데이터와 함께 반환합니다. 두 값 모두 processRollupProof()로 전달됩니다:

function processRollup(bytes calldata, bytes calldata _signatures) external {
    (bytes memory proofData, uint256 numTxs, uint256 publicInputsHash) = decodeProof();
    processRollupProof(proofData, _signatures, numTxs, publicInputsHash, rollupBeneficiary);
}

processRollupProof() 내부에서는 두 함수가 순차적으로 호출됩니다. 먼저 verifyProofAndUpdateState()가 모든 디코딩된 트랜잭션에 대해 ZK 증명을 검증하고 롤업 상태를 업데이트합니다. 이후 processDepositsAndWithdrawals()가 L1 정산을 처리하며, 처음 _numTxs 슬롯만 반복하여 각 예치에 대해 decreasePendingDepositBalance()를 호출합니다(이 호출은 사용자가 1단계에서 실제로 자금을 예치하지 않은 경우 되돌려지므로, 롤업 크레딧을 실제 L1 전송에 연결합니다):

function processRollupProof(bytes memory _proofData, bytes memory _signatures,
    uint256 _numTxs, uint256 _publicInputsHash, address _rollupBeneficiary) internal {
    verifyProofAndUpdateState(_proofData, _publicInputsHash);       // 증명 경로: 모든 디코딩된 트랜잭션
    processDepositsAndWithdrawals(_proofData, _numTxs, _signatures); // 정산 경로: 처음 _numTxs개만
}
// processDepositsAndWithdrawals 내부:
end := add(proofDataPtr, mul(_numTxs, TX_PUBLIC_INPUT_LENGTH))
while (proofDataPtr < end) {
    // ... 각 예치에 대해:
    decreasePendingDepositBalance(assetId, publicOwner, publicValue);
}

이 두 단계 설계는 L1 정산 로직이 ZK 증명이 검증한 것과 정확히 동일한 트랜잭션 집합을 처리할 것을 요구합니다. 두 경로가 처리할 트랜잭션이 일치하지 않으면, L1에서 대기 중인 잔액을 소비하지 않고도 롤업 상태에 예치가 기록될 수 있습니다.

취약점 분석

롤업 프로세서 컨트랙트(0x7d65...2728)에서 회로는 내부적으로 올바른 num_txs를 강제하였지만, 이를 공개 입력으로 노출하지 않았습니다. Solidity 측에서는 검증되지 않은 calldata 메타데이터에서 numTxs를 읽었기 때문에, 증명 경로와 정산 경로가 서로 다른 트랜잭션 목록을 처리할 수 있었습니다.

오프체인 rollup_circuit에서 num_txs는 위트니스로 로드되어 어떤 슬롯이 실제 트랜잭션으로 처리될지를 제어합니다. 회로는 num_txs가 실제 비패딩 증명 수와 일치하도록 강제합니다 — 슬롯에 실제 증명(비제로 데이터 루트)이 포함되어 있지만 is_real이 false인 경우(i >= num_txs이므로), is_real.assert_equal(data_root_exists) 검사가 실패하여 증명 생성이 중단됩니다. 그러나 num_txs는 증명의 공개 입력으로 노출되지 않습니다:

const auto num_txs = uint32_ct(witness_ct(&composer, rollup.num_txs));
field_ct(num_txs).create_range_constraint(MAX_TXS_BIT_LENGTH);
// ...
auto is_real = num_txs > uint32_ct(&composer, i);  // 슬롯별 실제 트랜잭션 로직 제어
// ...
is_real.assert_equal(data_root_exists);  // num_txs == 실제 트랜잭션 수 강제
회로 코드: num_txs가 내부적으로 강제되지만 공개 입력으로 노출되지 않음
회로 코드: num_txs가 내부적으로 강제되지만 공개 입력으로 노출되지 않음

Solidity 측에서 decodeProof()verifyProofAndUpdateState()가 검증하는 재구성된 proofData에 복사되지 않는 calldata 메타데이터에서 numTxs를 읽습니다. 회로의 내부 num_txs가 공개 입력이 아니기 때문에, 검증자는 calldata의 numTxs가 회로 내부에서 증명된 값과 일치하는지 확인할 방법이 없습니다:

decodeProof: numTxs 메타데이터가 검증된 proofData에 포함되지 않음
decodeProof: numTxs 메타데이터가 검증된 proofData에 포함되지 않음

따라서 공격자는 calldata의 numTxs를 회로에서 증명된 num_txs보다 낮게 설정할 수 있었습니다. 정산 루프는 증명이 롤업 상태에 기록한 트랜잭션을 건너뛰게 됩니다. 실행 불가능한 트랜잭션이 첫 번째 디코딩된 슬롯(정산 스캔 범위 내)을 차지하고, 실제 예치는 이후 슬롯(회로에 의해 증명되었지만 정산 스캔 범위 외)에 위치할 수 있었습니다. 증명은 롤업 상태에 예치를 기록하지만, 정산 로직은 decreasePendingDepositBalance() 호출을 포함하여 이를 완전히 건너뜁니다. 결과적으로 L1의 대기 예치 잔액은 소비되지 않은 채로 남아 있지만, 롤업 상태에는 이미 예치가 반영된 상태가 됩니다.

공격 분석

다음 분석은 트랜잭션 0x074ec9...9aeeb1을 기반으로 합니다.

공격자는 증명 경로와 정산 경로 사이의 격차를 두 단계로 악용하였습니다.

1단계: 뒷받침되지 않은 잔액 생성

  • Step 1: 공격자는 두 개의 디코딩된 트랜잭션(슬롯 1의 실행 불가능한(정크) 트랜잭션과 슬롯 2의 실제 예치)을 포함하는 여러 롤업 배치를 제출하였습니다. 회로의 num_txs는 2로 설정(두 실제 트랜잭션과 일치)되었지만, calldata의 numTxs는 1로 설정되었습니다. L1 정산 로직은 슬롯 1의 정크 트랜잭션만 처리하고, 슬롯 2의 실제 예치는 완전히 건너뛰었습니다.

  • Step 2: 그러나 ZK 증명은 슬롯 2의 예치를 포함한 모든 디코딩된 트랜잭션을 검증하고 기록하였습니다. 정산 로직이 이 예치에 도달하지 않았으므로, decreasePendingDepositBalance()가 호출되지 않아 L1의 대기 예치 잔액이 소비되지 않은 채로 남았습니다. 공격자는 이 패턴을 일곱 가지 다른 자산에 반복하여 롤업 상태에 뒷받침되지 않은 잔액을 축적하였습니다.

2단계: 자금 탈취

  • Step 3: 일곱 개의 뒷받침되지 않은 잔액이 구축되자, 공격자는 각 자산에 대해 표준 인출을 시작하였습니다. 이 인출들은 잔액이 롤업 상태에 존재하였기 때문에 정산 로직에 합법적으로 보였으며, L1 컨트랙트는 해당 자금(총 약 $2.15M)을 방출하였습니다.

결론

이 취약점은 암호학적 약점이 아니라 롤업 아키텍처의 두 핵심 코드 경로 간의 상태 일관성 버그였습니다. 근본 원인: 회로가 내부적으로 num_txs가 실제 트랜잭션 수와 일치하도록 강제하였지만, 이 값이 공개 입력으로 노출되지 않았습니다. Solidity 디코더는 검증되지 않은 calldata 메타데이터에서 numTxs를 읽었으며, 증명된 값과 대조할 방법이 없었습니다. 공격자는 calldata의 numTxs를 회로에서 증명된 수보다 낮게 설정하여 정산 로직이 증명이 이미 롤업 상태에 기록한 예치를 건너뛰도록 하였습니다. 이렇게 생성된 뒷받침되지 않은 잔액은 이후 일반적인 정산 흐름을 통해 인출되었습니다.

이 취약점은 Aztec Connect 종료 이전부터 존재하였습니다. 롤업은 2024년 3월 31일까지 트랜잭션 처리 및 인출을 종료하는 종료 계획을 발표하였습니다 [2]. 그러나 롤업 프로세서 컨트랙트는 2024년 4월 10일 풀 리퀘스트 [3]를 통해 업그레이드되어 모든 사용자에게 증명 제출이 허용되었으며, 이로 인해 기존에 존재하던 취약점이 누구나 악용 가능하게 되었습니다.

수정 방법은 numTxs를 ZK 증명이 검증한 전체 트랜잭션 집합에 바인딩하여 두 경로가 항상 동일한 집합을 처리하도록 하는 것입니다. 증명 검증과 L1 정산을 분리하는 모든 롤업 설계는 두 경로가 검증 가능하게 경계가 설정된 동일한 트랜잭션 집합에서 작동하도록 강제해야 합니다. 단 하나의 매개변수에서 불일치가 발생하더라도, 그렇지 않으면 건전한 증명 시스템을 뒷받침되지 않은 잔액 생성의 벡터로 전환할 수 있습니다.

참조

Phalcon Explorer 시작하기

트랜잭션을 심층 분석하여 현명하게 행동하세요

지금 무료로 사용하기

이번 주 추가 사건

Raydium

2026년 6월 10일, 솔라나의 Raydium 레거시 AMM v3 프로그램의 네 개 풀이 약 $1.34M의 공격을 받았습니다 [1]. 인출 핸들러가 호출자가 제공한 계정이 풀에 저장된 대응 계정과 일치하는지 검증하지 않았기 때문에, 공격자는 제어하는 계정으로 대체하여 지급 계산을 조작하였습니다. 동일한 기법으로 단 몇 초 만에 네 개의 풀에서 모든 예비금이 탈취되었습니다.

배경

Raydium의 AMM은 솔라나의 상수 곱 마켓 메이커입니다. 각 풀은 두 개의 토큰 볼트를 보유하고 예비금의 비례 지분을 나타내는 LP 토큰을 발행합니다. 유동성 공급자가 인출할 때, 핸들러는 지급액을 비례적으로 계산하고 두 볼트의 해당 지분을 전송합니다:

coin_out = total_coin * withdraw_amount / lp_supply
pc_out   = total_pc   * withdraw_amount / lp_supply

솔라나에서 각 토큰 유형은 총 공급량, 소수점 자리수, 발행 권한을 저장하는 Mint 계정으로 정의됩니다. 각 보유자의 잔액은 해당 Mint에 바인딩된 별도의 Token 계정에 저장되며, 하나의 Mint는 여러 보유자에게 걸쳐 많은 Token 계정을 가질 수 있습니다. 이는 단일 ERC-20 컨트랙트가 토큰 정의와 모든 잔액을 내부적으로 관리하는 EVM과 다릅니다.

위의 인출 공식에서 lp_supply는 풀의 LP Mint 계정(LP 총 공급량을 추적하는 계정)에서 읽습니다. 계산의 정확성은 이 값이 실제 LP Mint에서 온 것에 달려 있습니다. 그러나 솔라나에서는 호출자가 각 명령에 모든 계정을 위치 기반으로 전달하므로, 핸들러는 호출자가 제공한 각 계정이 풀 상태에 저장된 표준 계정과 일치하는지 검증해야 합니다.

취약점 분석

공격받은 프로그램(27haf8...8vQv)은 오픈소스가 아니었으며, 실행 가능한 데이터(ProgramData)가 공격 후 삭제되어 직접적인 바이트코드 검사가 불가능합니다. 아래 분석은 프로그램의 마지막 업그레이드 버퍼에서 재구성된 바이트코드와 온체인 트랜잭션 동작을 교차 참조하여 작성하였습니다.

인출 핸들러에서 호출자가 전달한 LP Mint 계정은 풀의 기록된 amm.lp_mint에 바인딩되지 않았습니다. 온체인 바이트코드에서 재구성된 다음의 역공학 의사코드는 계정 레이아웃을 보여줍니다. 핸들러는 풀 상태, PDA 권한, 두 볼트, 사용자 계정에 대한 바인딩을 확인하였지만, 슬롯 5의 LP Mint에 대해서는 확인하지 않았습니다:

let amm_info         = next_account_info(it)?;  // accounts[1] — 풀 상태 (amm.lp_mint 보유)
// ...
let amm_lp_mint_info = next_account_info(it)?;  // accounts[5] — 호출자가 제공한 mint

let amm = AmmInfo::load(amm_info)?;
// 권한, 볼트, open_orders 바인딩 여기서 확인...
// >>> 누락: accounts[5].key == amm.lp_mint 확인 <<<

let lp_mint = Mint::unpack(&amm_lp_mint_info.data.borrow())?;
let lp_mint_supply = lp_mint.supply;  // 검증되지 않은 mint에서 읽음

let coin_amount = total_coin * withdraw_amount / lp_mint_supply;
let pc_amount   = total_pc   * withdraw_amount / lp_mint_supply;

LP Mint 계정이 바인딩되지 않았기 때문에, 공격자는 자신이 완전히 제어하는 Mint 계정으로 대체할 수 있었습니다. 총 supply를 1로 설정하고 1개의 토큰을 소각하면 지급 비율이 1 / 1 = 100%가 되어 각 예비금을 전량 탈취할 수 있었습니다.

취약한 코드는 2023년 1월 3일 프로그램의 마지막 업그레이드 이후 변경 없이 존재하였으며, 익스플로잇 발생 약 1,254일 전부터 유지되어 왔습니다.

공격 분석

다음 분석은 트랜잭션 1csN6v...3s7s를 기반으로 합니다.

  • Step 1: 공격자는 decimals = 0, 총 supply = 0의 가짜 LP Mint 계정을 생성하였습니다.
  • Step 2: 공격자는 가짜 LP Mint에 바인딩된 Token 계정을 초기화한 후, (Mint 권한으로서) 정확히 1개의 토큰을 발행하여 Mint의 총 supply를 1로 고정하였습니다.
  • Step 3: 공격자는 예상되는 계정 슬롯에 가짜 LP Mint를 전달하고, 2단계의 Token 계정(1개의 가짜 LP 토큰 보유)을 LP 소스로 하여 인출 함수를 호출하였습니다. withdraw_amount = 1, lp_supply = 1로 핸들러는 total_coin * 1 / 1total_pc * 1 / 1을 계산하여 두 예비금의 100%(RAY/USDC 풀의 경우 893,700 USDC와 66,837 RAY)가 되었습니다.
  • Step 4: 핸들러는 공격자의 1개 토큰을 소각하고 두 풀 볼트에서 전체 예비금을 전송하여 RAY/USDC 풀을 완전히 소진시켰습니다.

공격자는 약 15초 이내에 세 개의 추가 풀에 동일한 패턴을 반복하였습니다. 네 개의 풀에서 탈취된 총량은 다음과 같습니다:

탈취량 (약)
RAY/USDC ~66,837 RAY + ~893,700 USDC
RAY/wSOL ~74,720 RAY + ~5,603 wSOL
RAY/SRM ~8,622 RAY + ~10,692 SRM
RAY/Sollet ETH ~5,038 RAY + ~16 Sollet ETH

결론

근본 원인은 단 하나의 누락된 계정 검증 확인입니다: 인출 핸들러가 호출자가 제공한 Mint 계정의 supply를 LP 공급 제수로 사용하면서 풀에 기록된 amm.lp_mint에 바인딩하지 않았습니다. 솔라나에서는 호출자가 제공한 모든 계정을 풀 상태에 저장된 표준 대응 계정에 바인딩해야 합니다. 올바른 구현은 키가 풀의 저장된 기록과 일치하지 않는 LP Mint를 거부하고, 외부에서 제공된 Mint의 supply가 아닌 풀 내부 LP 카운터에서 상환을 계산해야 합니다. 익스플로잇된 컨트랙트는 공격 당일 삭제된 구형 배포(2023년 1월 마지막 업그레이드)였습니다. Raydium 팀에 따르면, 전액 보상은 Raydium의 재무에서 처리될 예정입니다 [1].

참조

Phalcon Security 시작하기

모든 위협을 감지하고, 중요한 것을 알리며, 공격을 차단하세요.

지금 무료로 사용하기

BlockSec 소개

BlockSec은 풀스택 블록체인 보안 및 암호화폐 컴플라이언스 전문 기업입니다. 저희는 고객이 프로토콜과 플랫폼의 전체 생애주기에 걸쳐 코드 감사(스마트 컨트랙트, 블록체인 및 지갑 포함), 실시간 공격 차단, 사건 분석, 불법 자금 추적, AML/CFT 의무 이행을 수행할 수 있도록 돕는 제품과 서비스를 구축합니다.

BlockSec은 권위 있는 학술 컨퍼런스에 다수의 블록체인 보안 논문을 발표하였으며, 여러 DeFi 애플리케이션의 제로데이 공격을 보고하고, 다수의 해킹을 차단하여 2천만 달러 이상을 구제하였으며, 수십억 달러 규모의 암호화폐를 보호하였습니다.

Sign up for the latest updates
~$410만 손실: Taiko, SecondFi 익스플로잇 | BlockSec 위클리
Security Insights

~$410만 손실: Taiko, SecondFi 익스플로잇 | BlockSec 위클리

이 주간 블록체인 보안 리포트는 2026년 6월 22~28일 발생한 주요 사건 2건을 다루며, 이더리움과 카르다노에서 약 410만 달러의 피해가 확인됐습니다. Taiko 브릿지 공격은 노출된 SGX 서명 키와 디버그 엔클레이브를 거부하지 못한 증명 정책 결함을 이용해 악성 증명자를 등록하고 L2 상태 증명을 위조했습니다. SecondFi 지갑은 Ed25519 논스 도출 시 비밀 입력이 제거되는 결함으로 공개 트랜잭션 데이터만으로 개인 키 복구가 가능했습니다.

~$18M 손실: jaredFromSubway, Aztec 등 | BlockSec 위클리
Security Insights

~$18M 손실: jaredFromSubway, Aztec 등 | BlockSec 위클리

이 주간 블록체인 보안 보고서는 2026년 6월 15일~21일을 다루며, 이더리움과 BNB 체인에서 3건의 주요 사고가 발생해 약 $18.3M의 손실이 발생했습니다. jaredFromSubway 사건은 MEV 봇이 차익거래를 위해 신뢰할 수 없는 제3자 컨트랙트에 자산을 승인한 역방향 승인 공격으로, 가짜 래퍼 토큰과 스왑 풀을 이용해 약 $15M 손실이 발생했습니다. Aztec은 이스케이프 해치 ZK 회로의 제약 누락으로 공격자가 가짜 머클 트리로 온체인 검증을 통과했습니다.

Web3 컴패니언: 오픈소스 보안 에이전틱 지갑

Web3 컴패니언: 오픈소스 보안 에이전틱 지갑

BlockSec가 Web3 Companion을 오픈소스로 공개했습니다. 이 보안 중심의 에이전트 지갑은 자체 AI 에이전트를 신뢰하지 않는 방식으로 설계되었으며, 키 격리, 강력한 정책, Passkey를 활용해 온체인 자산을 보호합니다.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit