SecuFiby TechFi
← 실습으로 돌아가기|SC01 접근 제어 취약점 분석 보고서 — 샘플
📝 빈 양식 (.docx) 다운로드
CRITICAL · SC01:2026
스마트컨트랙트 접근 제어 취약점 분석 보고서
Access Control Vulnerability — VulnerableVault.sol
2026년 6월 18일
Remix IDE + Ganache
CRITICAL
01 취약점 개요
취약점 IDSC01:2026 — Access Control
대상 컨트랙트VulnerableVault.sol
취약 함수changeOwner(address), adminWithdraw()
위험도CRITICAL
영향도 (2024)$953.2M 피해 · OWASP Smart Contract Top 10 1위
취약점 요약소유권 변경 함수(changeOwner)에 접근 제어가 없어 누구나 owner를 교체할 수 있으며, 이후 adminWithdraw()로 전체 잔액을 탈취 가능
02 실습 환경 및 계정 정보
역할계정 주소행동
피해자 (ethstore)0x5B38Da6a701c568545dCfcB03FcB875f56beddC4컨트랙트 배포 · 5 ETH 입금
공격자 (attacker)0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2소유권 탈취 · 전액 인출
03 취약 코드 분석
// ❌ 취약한 컨트랙트 — 접근 제어 없음 contract VulnerableVault { address public owner; constructor() { owner = msg.sender; } // ❌ 누구나 호출 가능 — modifier 없음! function changeOwner(address newOwner) public { owner = newOwner; } // ❌ owner 체크를 우회 당함 function adminWithdraw() public { uint bal = address(this).balance; require(bal > 0, "Empty"); payable(msg.sender).transfer(bal); } }
04 공격 재현 시나리오 (Remix IDE)
  • STEP 1
    금고에 돈 넣기 (피해자)
    Account → ethstore (0x5B3...eddC4) 선택 | Value: 5 ether
    Low level interaction → Transact 클릭 → 컨트랙트 잔액 5 ETH 확인
  • STEP 2
    소유권 탈취 (공격자)
    Account → attacker (0xAb8...35cb2) 선택 | Value: 0 wei
    changeOwner 입력란에 attacker 주소 입력 → Transact 클릭
    → 트랜잭션 성공 (접근 제어 없음)
  • STEP 3
    전액 탈취 (공격자)
    Account: attacker 그대로 | Value: 0 wei
    adminWithdrawTransact 클릭
    → 5 ETH 전액이 attacker 계정으로 이동 — 공격 성공
05 취약점 분석 결과
근본 원인: changeOwner() 함수에 onlyOwner modifier가 없어 외부 계정이 임의로 컨트랙트 소유권을 교체할 수 있음.

공격 경로: 소유권 탈취 → adminWithdraw() 호출 권한 획득 → 전액 인출

영향 범위: 컨트랙트에 예치된 ETH 전액 손실. 원상복구 불가 (블록체인 불변성).
06 방어 방법 및 수정 코드
// ✅ 방어된 컨트랙트 — 2-step 소유권 이전 contract SecureVault { address public owner; address public pendingOwner; modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; } // ✅ onlyOwner → 현재 owner만 호출 가능 function transferOwnership(address newOwner) public onlyOwner { require(newOwner != address(0)); pendingOwner = newOwner; } // ✅ 새 owner가 직접 수락해야 완료 function acceptOwnership() public { require(msg.sender == pendingOwner, "Not pending"); owner = pendingOwner; pendingOwner = address(0); } }
방어 전략:
  • 모든 관리자 함수에 onlyOwner modifier 적용
  • 2-step 소유권 이전: transferOwnershipacceptOwnership (실수 방지)
  • OpenZeppelin Ownable2Step 라이브러리 활용 권장
  • Slither 정적 분석으로 배포 전 자동 점검
07 결론 및 조치 사항
항목내용우선순위
즉시 조치changeOwner()onlyOwner 추가 또는 함수 제거긴급
단기 조치OpenZeppelin Ownable2Step 패턴으로 교체높음
중기 조치Slither CI 파이프라인 통합, 정기 감사보통