이오스 벳의 Transfer 해킹이 14일 3:00 UTC에 발생했습니다. 관련 내용은 아래와 같습니다.
https://medium.com/@eosbetcasino/eosbet-transfer-hack-statement-31a3be4f5dcf
이오스벳을 비롯해 대부분의 contract 가 비슷한 로직을 사용중입니다. 저희쪽에도 비슷한 코드가 있어서 같은 위험이 있었습니다. 다행이 해커가 저희의 계정을 공격하기전 prospectors.io 가 해당 이슈를 리포팅해주고 저희 EOS를 대피시켜서 문제를 공격을 피해갈 수 있었습니다.
저희는 14일 20:00 UTC에 해당 이슈를 공유받았습니다. (한국시간 기준 15일 오전 5시) 오전에 이슈를 대응하고 해당 이슈를 한국 커뮤니티에도 전파했습니다. 다른 팀이 대응할 시간을 만들기 위해 이 이슈에 대한 내용을 조금 늦게 공유드립니다.
Overview of the Attack
EOS Bet 에서 이미 언급되었지만 이쪽에서 한번 더 간단히 설명드리겠습니다.
문제가 된 코드입니다.
#undef EOSIO_ABI
#define EOSIO_ABI( TYPE, MEMBERS ) \
extern "C" { \
void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
if( action == N(onerror)) { \
/* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \
eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \
} \
auto self = receiver; \
if( code == self || code == N(eosio.token) || action == N(onerror) ) { \
TYPE thiscontract( self ); \
switch( action ) { \
EOSIO_API( TYPE, MEMBERS ) \
} \
/* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
} \
} \
}
EOSIO_ABI(knights, .... (transfer) )
저희를 비롯해서 대부분의 컨트랙트에서 eosio.token 컨트랙트의 transfer이벤트를 받기 위해 사용하던 방식입니다. "code == N(eosio.token)" 를 통해서 해당 event가 contract에서 처리될 수 있게합니다. EOSIO_ABI 에서 transfer를 노출해서 eosio.token 의 require_recipient함수가 저희쪽 컨트랙트의 transfer를 호출할 수 있게합니다.
문제는 ABI로 transfer action을 노출했기 때문에 유저가 transfer action을 콜할 수 있다는데 있습니다. 이렇게 되면 contract는 입금후 이벤트가 왔다고 생각합니다. 실제로는 입금이 되지 않았지만 이미 입금이 된것 처럼 동작합니다. 그결과 저희 게임을 예로 들면 buyer에게 아무것도 입금을 받지 않고 seller에게 해당 eos를 지급하게 됩니다.
변경된 코드입니다.
#define EOSIO_ABI( TYPE, MEMBERS ) \
extern "C" { \
void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
auto self = receiver; \
TYPE thiscontract( self ); \
if( action == N(onerror)) { \
/* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \
eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \
} \
if( code == self ) { \
if (action != N(transfer)) {\
switch( action ) { \
EOSIO_API( TYPE, MEMBERS ) \
} \
/* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
}\
} \
else if (code == N(eosio.token) && action == N(transfer) ) {\
execute_action(&thiscontract, &knights::transfer);\
}\
} \
}
transfer가 self scope로 실행될수 없게 막았습니다. transfer action은 eosio.token 의 이벤트로만 처리됩니다.
A Note to the Community
이슈를 공유해주고 안전하게 EOS를 대피시켜준 prospectors.io 께 감사드립니다. 피해를 막고 EOS 커뮤니티 발전에 노력해주는 모습이 무척 인상적이였습니다. 덕분에 저희는 피해를 입지 않고 무사히 이 이슈를 피해갈 수 있었습니다. 이들의 노력이 없이 저희가 먼저 공격을 당했다면 해당 이슈를 파악하는데 시간이 오래 걸렸을 것 같습니다. 해당 이슈 파악과 대응 그리고 내용을 신속히 공유해주신 커뮤니티 멤버분들께 감사의 말씀을 전합니다.
EOS Knights는 앞으로 이와 같은 문제를 막기위해 내부 테스트와 코드리뷰를 좀더 강화할 예정입니다. 또한 contract에 EOS가 많이 쌓이지 않게 컨트랙을 개선할 예정입니다.
감사합니다.
개발 하는것도 그걸 이용하는것도 엄청난 머리싸움이네요 ㄷㄷ
관심은 생기지만 배울려면 10년 걸릴거 같네요...
이오스나이츠 잘 즐기고 있는데 감사드립니다 ㅎㅎ
보냈다고 낚시가 된다니ㄷㄷ