궁금한 주제를 찾아보세요

비트코인, 스테이블코인, 온체인 데이터처럼 주제로 검색하세요.

다시 읽을 이야기

저장한 글은 이 브라우저에만 보관됩니다.

해설기술·생태계

재진입 공격은 외부 호출과 상태 변경 순서를 어떻게 악용하나

외부 호출이 제어권을 넘긴 동안 같은 잔액을 다시 읽는 단일·교차 함수 재진입과 CEI·mutex·pull payment 방어를 사례로 설명합니다.

난이도 중급주제 카테고리와 개념 밀도 기준검토 정보AI 보조 초안 · 출처 목록 제공 · 주장별 대조 진행 중
출금 중인 금고로 호출 화살표가 다시 돌아와 상태 갱신 전 반복 인출을 만드는 재진입 개념도
주제의 이해를 돕기 위해 imagegen으로 제작한 AI 생성 개념 일러스트
먼저 읽는 핵심

모든 외부 호출은 악성 callback으로 제어권을 넘길 수 있습니다.
Effects를 interaction보다 먼저 기록해 재호출이 최신 상태를 보게 합니다.
Guard 범위 밖 교차 함수와 외부 의존 상태도 함께 모델링해야 합니다.

재진입은 계약 A가 token 전송이나 ETH CALL로 외부 계약 B에 제어권을 넘긴 뒤, A가 중요한 상태를 갱신하기 전에 B가 A의 함수로 다시 들어와 같은 권리나 잔액을 재사용하는 공격입니다. 방어의 핵심은 입력·권한을 검사한 다음 잔액 차감과 청구 완료 같은 effects를 먼저 기록하고 마지막에 interaction을 수행하는 checks-effects-interactions입니다. ReentrancyGuard의 mutex는 같은 guard 영역 재호출을 막지만 unguarded 함수, 별도 계약 callback, read-only 관찰과 잘못된 상태 설계까지 자동으로 고치지 않습니다.

취약점은 외부 호출 전의 낡은 상태입니다

Vault가 balances[user]를 읽고 user.call{value:amount}를 실행한 뒤 잔액을 0으로 만든다고 하겠습니다. User가 contract면 receive 함수에서 Vault.withdraw를 다시 호출할 수 있습니다. 첫 frame이 아직 balance를 0으로 쓰지 않았으므로 두 번째 frame도 같은 amount를 통과해 반복 지급합니다.

ETH 전송만 위험한 것은 아닙니다. ERC-777 hook, NFT safeTransfer callback, lending protocol의 임의 token·oracle 호출도 control을 넘깁니다. 알려진 contract를 호출해도 그 contract가 다시 알 수 없는 contract를 호출할 수 있습니다. 외부 interaction 여부는 함수 이름이 아니라 실제 CALL 계열 trace로 판단합니다.

재진입의 본질은 두 번 호출됐다는 사실보다, 두 번째 호출이 첫 번째 호출의 완료 전 상태를 유효한 것으로 읽었다는 데 있습니다.

JOBCOIN 해설

VISUAL GUIDE보상 발표와 지급을 구분하기
보상 공지, 청구 조건, 실제 지급 확인이 서로 다른 단계임을 보여주는 개념도
그림과 함께 짚어볼 본문 내용

그림은 이 주제의 공통 개념을 단순화한 설명입니다. 아래 항목에서 이 글의 구체적인 조건과 예외를 함께 읽어보세요.

  1. 취약점은 외부 호출 전의 낡은 상태입니다

    Vault가 balances[user]를 읽고 user.

  2. Checks-Effects-Interactions로 순서를 뒤집습니다

    안전한 withdraw는 먼저 amount와 권한을 검사하고 balances[user]=0을 저장한 뒤 외부 전송을 시도합니다.

  3. Mutex는 범위를 정확히 설정해야 합니다

    OpenZeppelin ReentrancyGuard는 entered status를 설정하고 nonReentrant 함수가 끝난 뒤 복구합니다.

AI로 제작한 개념도 · 실제 가격·거래 내역·통계가 아닙니다.

Checks-Effects-Interactions로 순서를 뒤집습니다

안전한 withdraw는 먼저 amount와 권한을 검사하고 balances[user]=0을 저장한 뒤 외부 전송을 시도합니다. Callback이 다시 들어오면 이미 0인 상태를 보므로 지급 조건을 통과하지 못합니다. 외부 전송이 실패하면 require로 전체 transaction을 revert해 먼저 기록한 effect도 원복할 수 있습니다.

CEI는 단순 줄 순서가 아니라 invariant가 외부 호출 전에 성립하는지 보는 방법입니다. 총 부채, share supply, nonce와 claim flag처럼 함께 움직이는 값이 모두 갱신돼야 합니다. 한 변수만 먼저 바꾸고 다른 함수가 갱신 전 변수를 읽으면 cross-function reentrancy가 남습니다.

재진입 유형과 방어 초점
유형 재진입 경로 검토 항목
동일 함수 withdraw→callback→withdraw 잔액 선차감
교차 함수 withdraw→callback→transfer 공유 invariant
교차 계약 A→B→C→A 전체 call graph
Read-only callback 중 view 조회 중간 가격·비율 노출
Callback token transfer hook 재호출 지원 token semantics

Mutex는 범위를 정확히 설정해야 합니다

OpenZeppelin ReentrancyGuard는 entered status를 설정하고 nonReentrant 함수가 끝난 뒤 복구합니다. 같은 guard를 공유하는 protected 함수끼리 중첩 호출할 수 없으므로 외부 entry 함수가 private worker를 호출하는 구조가 흔합니다. Upgradeable 환경에서는 선택한 구현의 초기 상태와 storage slot도 확인합니다.

Withdraw만 guard하고 같은 balances를 수정하는 borrow·transfer를 열어 두면 callback이 다른 entry point로 들어올 수 있습니다. 여러 contract가 각각 guard를 가져도 서로의 mutex는 공유하지 않습니다. Guard가 있다는 사실보다 어떤 invariant를 어느 entry points가 공유하는지 표로 만듭니다.

  • 외부 CALL·token hook·callback 지점을 모두 표시합니다.
  • 각 지점 직전의 미완료 state changes를 찾습니다.
  • 공유 invariant를 쓰는 모든 public entry를 묶습니다.
  • CEI와 mutex를 독립적으로 테스트합니다.
  • 악성 receiver로 nested callback을 재현합니다.

Pull payment와 실패 처리를 함께 설계합니다

한 transaction에서 여러 사용자에게 ETH를 push하면 한 receiver의 revert가 전체 정산을 막거나 callback 면을 넓힙니다. 사용자별 credit을 먼저 기록하고 각 사용자가 별도 withdraw하게 하는 pull pattern은 외부 호출 범위를 작은 함수로 모읍니다. 그래도 withdraw 자체에는 CEI와 실패 반환 검사가 필요합니다.

Low-level call이 false를 반환했는데 무시하면 잔액만 차감하고 지급은 실패할 수 있습니다. 반대로 실패 시 credit을 다시 더하는 보상 로직이 callback과 얽히면 중복 credit을 만들 수 있습니다. 성공 조건과 revert 경계를 state machine으로 테스트합니다.

Gas 제한을 방어선으로 의존하지 않습니다

transfer의 2,300 gas stipend가 callback을 영원히 막는다는 가정은 opcode gas 변경과 receiver 동작 때문에 견고하지 않습니다. Solidity 문서도 Ether 전송은 code execution을 포함하며 call은 더 많은 gas를 넘겨 재진입 가능성을 연다고 설명합니다. Gas 제한 대신 상태 순서와 명시적 guard를 사용합니다.

테스트에는 동일 함수, 교차 함수, 여러 protocol을 거치는 callback, 실패·성공을 번갈아 내는 receiver를 포함합니다. Trace에서 depth만 세지 말고 각 frame이 읽고 쓴 slot과 외부 자산 이동을 대조해야 실제 invariant 손상을 확인할 수 있습니다.

자주 묻는 질문

nonReentrant modifier 하나면 모든 재진입이 막히나요?

해당 guard를 공유하는 함수 진입만 막습니다. Guard 밖 함수와 다른 계약의 상태, read-only 재진입은 별도 검토가 필요합니다.

View 함수도 재진입 위험이 있나요?

State를 쓰지 않아도 transaction 중간의 불완전한 가격·비율을 외부 protocol이 읽어 의사결정하면 read-only reentrancy 문제가 생길 수 있습니다.

ETH를 transfer로 보내면 안전한가요?

Gas stipend를 영구 방어선으로 의존하면 안 됩니다. CEI, pull payment와 반환값 검사를 적용해야 합니다.

더 깊이 읽기

본문에서 다룬 개념과 확인 절차를 다음 글에서 이어서 살펴보세요.

참고한 원문 자료

자료 확인 기준일 2026.09.27
  1. Solidity Security Considerations — Reentrancydocs.soliditylang.org
  2. OpenZeppelin ReentrancyGuard sourcegithub.com
자료 대조 기록과 확인 범위
출처 수집
원문 링크 2개 제공
핵심 주장 대조
완료 근거가 아직 기록되지 않았습니다.
분야 전문가 검수
별도 완료 기록이 없습니다.

원문 링크와 자료 확인일은 글 전체의 주장 대조나 전문가 검수 완료를 뜻하지 않습니다. 별도 확인이 필요한 절차는 원문의 적용 대상과 최신 안내를 함께 확인해 주세요.

AI 활용 안내

이 글은 초안 구성과 자료 정리에 AI를 활용했습니다. 글에 표시된 출처와 기준일을 함께 확인해 주세요. 별도 검토 정보가 없다면 전문가 검수를 뜻하지 않습니다.

이해를 위한 정보 콘텐츠

이 글은 특정 자산의 매수·매도 또는 수익을 권유하지 않습니다. 자료의 발표 시점과 이후 변경 사항을 함께 확인해 주세요.

편집 원칙 보기 →이 기사 정정 제보 →