지난주(2026/10/05 - 2026/10/11)에는 보안 사고 1건이 발생했으며, 추정 피해액은 총 약 $542K입니다.
| 날짜 | 사고 | 유형 | 추정 피해액 |
|---|---|---|---|
| 2026/10/06 | MakerDaoBot | 접근 제어 부재 | ~$542K |
이번 주 주요 사고: MakerDaoBot
2026년 10월 6일, 공격자는 Ethereum의 기존 MakerDAO 청산 봇인 MakerDaoBot의 접근 제어 부재를 악용해 약 $542K 상당의 ETH 200개를 인출했습니다. 취약한 함수는 누구나 외부 컨트랙트를 지정해 봇의 잔액에 대한 제어 권한을 부여하고 해당 컨트랙트의 코드를 실행할 수 있도록 했습니다. 공격자는 이 경로를 이용해 오래된 담보 경매 4건을 정산하고, 회수된 ETH를 단일 트랜잭션으로 이체했습니다.
배경
MakerDaoBot은 기존 청산 시스템에서 사용하던 담보 경매 컨트랙트 Flipper를 통해 MakerDAO의 기존 ETH-A 담보 경매에 참여했습니다. 이 봇은 Vat 권한을 관리하고, ETH와 DAI를 예치 및 인출하며, 청산 작업을 처리했습니다.
Vat은 MakerDAO의 내부 원장입니다. 담보 유형별 각 주소의 사용 가능한 담보를 gem[ilk][address]에, 내부 DAI 잔액을 dai[address]에 기록하며, 별도의 지갑이나 계정 컨트랙트는 없습니다. 따라서 MakerDaoBot의 Vat 잔액은 봇 컨트랙트 자체의 주소에 기록된 원장 항목을 의미합니다.
hope(usr)를 호출해도 해당 잔액이 이체되지는 않습니다. 이 호출은 can[msg.sender][usr]를 1로 설정해 usr가 Vat의 wish() 권한 검사를 통과하고 msg.sender에 기록된 잔액을 조작할 수 있게 합니다. flux()와 move() 같은 함수는 내부 담보 또는 DAI를 이체할 때 이 검사를 사용합니다.
mapping(address => mapping (address => uint)) public can;
function hope(address usr) external note { can[msg.sender][usr] = 1; }
function nope(address usr) external note { can[msg.sender][usr] = 0; }
function wish(address bit, address usr) internal view returns (bool) {
return either(bit == usr, can[bit][usr] == 1);
}
mapping (bytes32 => mapping (address => uint)) public gem; // [wad]
mapping (address => uint256) public dai; // [rad]
function flux(bytes32 ilk, address src, address dst, uint256 wad) external note {
require(wish(src, msg.sender), "Vat/not-allowed");
gem[ilk][src] = sub(gem[ilk][src], wad);
gem[ilk][dst] = add(gem[ilk][dst], wad);
}
function move(address src, address dst, uint256 rad) external note {
require(wish(src, msg.sender), "Vat/not-allowed");
dai[src] = sub(dai[src], rad);
dai[dst] = add(dai[dst], rad);
}
취약점 분석
근본 원인은 0x6839...5546에 있는 MakerDaoBot 컨트랙트의 0x8804d1de() 함수에 접근 제어가 없었다는 점입니다. 이 함수는 varg0을 외부 컨트랙트 주소(이하 어댑터)로 취급하고, 해당 주소의 drip(), vat(), 이후 join()을 호출했습니다. 봇은 vat()을 통해 Vat 주소를 얻은 뒤 어댑터가 자신의 Vat 잔액을 조작할 수 있는지 확인했습니다. 권한이 없으면 hope(varg0)를 호출해 권한을 부여한 다음, 동일한 어댑터의 join()을 호출했습니다.

같은 컨트랙트의 다른 권한 있는 진입점은 작업을 실행하기 전에 ds-auth 검사를 사용했습니다. 아래 비교에서는 보호된 진입점 두 곳이 bytes4 셀렉터와 msg.sender를 권한 검사 헬퍼에 전달하고, 검사가 실패하면 ds-auth-unauthorized로 실행을 되돌리는 것을 볼 수 있습니다. 0x8804d1de() 함수에는 이에 상응하는 호출자 검사가 없었습니다.

따라서 누구나 0x8804d1de() 함수를 통해 Vat 권한을 받을 주소를 선택할 수 있었습니다. 봇은 hope()를 통해 해당 주소에 권한을 부여하고 즉시 그 주소의 join() 함수를 호출했습니다. 이 외부 호출 중에도 위임 권한이 유지되어, 선택된 주소의 코드가 봇의 내부 담보 및 DAI 잔액을 조작할 수 있었습니다.
공격 분석
다음 분석은 트랜잭션 0xbb6940...f3a88c을 기반으로 합니다.
- 1단계: 공격자의 EOA가 자신이 배포한 공격 컨트랙트를 호출했습니다. 이 컨트랙트는 악성 어댑터를 배포한 뒤, 해당 어댑터 주소를 인자로 MakerDaoBot의
0x8804d1de()함수를 호출했습니다. 봇은 Vat의can매핑에서 기존 위임 권한이 없음을 확인하고hope()를 호출해, 어댑터가 봇 주소에 기록된 잔액을 조작할 수 있도록 했습니다.

- 2단계: MakerDaoBot이 어댑터의
join()함수를 호출했습니다. 위임 권한이 활성화된 동안 악성 코드는 종료된ETH-AFlipper의deal(uint256 id)을 호출해 경매 1457, 1458, 1459, 1460을 정산했습니다. 이어서 어댑터는 Vat 권한을 사용해ETH-A담보 200단위를 MakerDaoBot에서 자신에게 옮기고, ETH Join을 통해 인출하여WETH200개를 받은 뒤 공격 컨트랙트로 이체했습니다.

- 3단계: 공격 컨트랙트는
WETH200개를 언래핑하고 당시 약 $542K 상당의ETH200개를 공격자의 EOA로 보냈습니다.

결론
근본 원인은 MakerDaoBot의 접근 제어 부재였으며, MakerDAO의 Vat 권한 시스템 자체의 결함은 아니었습니다. 공개 함수로 누구나 외부 컨트랙트를 선택하고, 봇의 내부 잔액에 대한 제어 권한을 부여한 뒤 해당 컨트랙트의 코드를 실행할 수 있었습니다. 공격자는 이 경로를 이용해 기존 경매 4건을 정산하고 ETH 200개를 인출했습니다.
자산에 대한 제어 권한을 위임하는 모든 함수는 작업을 시작할 수 있는 주체와 권한을 부여받을 수 있는 외부 컨트랙트를 모두 제한해야 합니다. 민감한 연동 경로에는 다른 권한 있는 함수와 동일한 권한 제어를 적용하고, 명시적으로 승인된 컨트랙트만 허용해야 합니다.
Web3를 위한 최고의 보안 감사 서비스
출시 전에 설계, 코드, 비즈니스 로직을 검증하세요
BlockSec 소개
BlockSec은 블록체인 보안과 암호화폐 규제 준수를 위한 종합 서비스를 제공하는 기업입니다. 프로토콜과 플랫폼의 전체 수명주기에 걸쳐 고객이 코드 감사(스마트 컨트랙트, 블록체인 및 지갑 포함)를 수행하고, 공격을 실시간으로 차단하며, 사고를 분석하고, 불법 자금을 추적하고, AML/CFT 의무를 이행할 수 있도록 돕는 제품과 서비스를 개발합니다.
BlockSec은 권위 있는 학회에서 여러 편의 블록체인 보안 논문을 발표하고, DeFi 애플리케이션의 제로데이 공격 여러 건을 보고했으며, 다수의 해킹을 차단해 2천만 달러가 넘는 자산을 구하고 수십억 달러 규모의 암호화폐를 보호했습니다.
-
공식 웹사이트: https://blocksec.com/
-
공식 Twitter 계정: https://twitter.com/BlockSecTeam



