해당 내용을 간단히 요약하면 precompile된 contract에서 delegatecall에 대한 처리를 하지 않아 생긴 취약점이라고 할 수 있다. 취약점을 발견한 pwning.eth는 해당 취약점이 적용되는 다른 프로젝트들을 찾아보았다.
취약점은 Moonriver, Moonbeam에서 발견되었으며, 이는 Aurora에서 발견된 취약점과 매우 유사하다.
취약점은 balances-erc20, assets-erc20
moonbeam/precompiles/balances-erc20/src/lib.rs
fn approve(handle: &mut impl PrecompileHandle) -> EvmResult<PrecompileOutput> {
handle.record_cost(RuntimeHelper::<Runtime>::db_write_gas_cost())?;
handle.record_log_costs_manual(3, 32)?;
// Parse input.
let mut input = handle.read_input()?;
input.expect_arguments(2)?;
let spender: H160 = input.read::<Address>()?.into();
let amount: U256 = input.read()?;
// Write into storage.
{
let caller: Runtime::AccountId =
Runtime::AddressMapping::into_account_id(handle.context().caller);
let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender);
// Amount saturate if too high.
let amount = Self::u256_to_amount(amount).unwrap_or_else(|_| Bounded::max_value());
ApprovesStorage::<Runtime, Instance>::insert(caller, spender, amount);
}
log3(
handle.context().address,
SELECTOR_LOG_APPROVAL,
handle.context().caller,
spender,
EvmDataWriter::new().write(amount).build(),
)
.record(handle)?;
// Build output.
Ok(succeed(EvmDataWriter::new().write(true).build()))
}
별도의 delegatecall에 대한 재제가 없었기 때문에 aurora때 있었던 취약점이 그대로 존재한다.
위 코드의 erc20은 moonbeam의 기본 토큰으로 사용되기 때문에 moonbeam 환경의 모든 지갑에 대해서 취약점이 존재한다. 해당 precompile contract의 주소는 0x0000000000000000000000000000000000000802이고, 토큰의 이름은 MOVR이다.
이 취약점으로는 직접적인 공격을 하기보다는 phising contract를 만들어 피해자가 이에 airdrop 등으로 접근하도록 하거나, 많은 자산을 가지고 있는 Contract의 또다른 취약점을 통해 그 Contract의 자산을 빼앗을 수 있다.
후자의 경우를 통해서 이 취약점을 트리거 할 수 있다.
Moonwell project의 Mglimmer 컨트랙트(0x6a1A771C7826596652daDC9145fEAaE62b1cd07f)에 취약점이 있다.
Mglimmer 컨트랙트에서 redeemUnderlying 함수를 호출하면, doTransferOut 함수를 호출하게 되는데 이 때 argument의 to에 로 msg.sender가 들어가게 된다.
function doTransferOut(address payable to, uint amount) internal {
/* Send the Glimmer, with minimal gas and revert on failure */
(bool success, ) = to.call.value(amount)("");
require(success, "Transfer failed");
}
doTransferOut에서 to 주소에 콜하기 때문에 to 주소 즉 msg.sender 컨트랙트의 receive 함수를 잘 만든다면 앞서 발견한 취약점을 트리거 할 수 있게 된다. pwning.eth는 공격 Contract를 다음과 같이 작성했다.
해당 공격을 수행하면 MGlimmer 컨트랙트에 있는 모든 자산이 공격자의 Contract에 approve되기 때문에 사실상 모든 자산을 빼앗았다고 보아도 무방하다.
이 취약점은 Aurora가 수정했던 것과 유사하게 engine 자체에서 delegatecall로 호출되는 것을 금지했다.
precompiles/utils/src/precompileset.rs
impl<P: StatefulPrecompile, S: PrecompileSet> PrecompileSet for ChainedPrecompile<P, S> {
#[inline]
fn execute(&self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
// Move forward the chain if this is not the correct address.
if handle.code_address() != self.address {
return self.chain.execute(handle);
}
// Check DELEGATECALL
if !self.allow_delegate && handle.code_address() != handle.context().address {
return Some(Err(revert(
"cannot be called with DELEGATECALL or CALLCODE",
)));
}
위의 함수에서 Check Delegatecall 조건문을 추가해 아예 delegatecall에 대한 접근을 차단했다.