01 취약점 개요
| 취약점 ID | SC01: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
adminWithdraw → Transact 클릭
→ 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 소유권 이전:
transferOwnership → acceptOwnership (실수 방지) - OpenZeppelin
Ownable2Step 라이브러리 활용 권장 - Slither 정적 분석으로 배포 전 자동 점검
07 결론 및 조치 사항
| 항목 | 내용 | 우선순위 |
|---|
| 즉시 조치 | changeOwner()에 onlyOwner 추가 또는 함수 제거 | 긴급 |
| 단기 조치 | OpenZeppelin Ownable2Step 패턴으로 교체 | 높음 |
| 중기 조치 | Slither CI 파이프라인 통합, 정기 감사 | 보통 |