code4rena-2023-09-centrifuge-m02
[M-02] LiquidityPool::requestRedeemWithPermit transaction can be front run with the different liquidity pool
Summary
서명에 어떤 풀을 청산하려고 permit을 호출하는지에 대한 정보가 없다. 따라서 프론트러닝으로 선수치면 유저가 원하는 것과 다른 풀을 청산시킬 수 있다. 멤풀에 존재하는 유저의 트랜잭션은 실패하게 된다.
Keyword
erc2612, erc20, frontrunning, dos, griefing attack, signature
Vulnerability
LP 컨트랙트의 requestRedeemWithPermit를 호출한다면 서명을 이용하여 investmentManager가 트랜치 토큰을 가져갈 수 있도록 허용하며 투자 청산을 요청한다.
function requestRedeemWithPermit(uint256 shares, address owner, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
public
{
share.permit(owner, address(investmentManager), shares, deadline, v, r, s);
investmentManager.requestRedeem(shares, owner);
emit RedeemRequested(owner, shares);
}트랜치 토큰은 여러 개의 연동된 ERC4626 LP의 share 토큰으로 사용된다. 따라서 동일한 서명을 이용하여 (프론트러닝으로 선수친다면) 다른 LP를 청산시킬 수 있다. permit 서명에는 어떤 풀을 청산하기 위해 approve 하는지에 대한 정보는 포함되지 않기 때문이다.
investmentManager는 다른 모든 LP를 관리하므로, investmentManager가 토큰을 가져갈 수 있도록 허용만 한다면 동일 트랜치 토큰을 사용하는 LP를 청산시키는 게 가능하다.
다음은 ERC20 컨트랙트의 permit 함수이다. 서명에 nonce가 포함되어 재사용을 방지한다. 따라서 공격자가 프론트러닝으로 다른 LP를 청산시키며 nonce를 소모하면 멤풀에 남아있는 유저의 요청은 실패하게 된다.
function permit(address owner, address spender, uint256 value, uint256 deadline, bytes memory signature) public {
require(block.timestamp <= deadline, "ERC20/permit-expired");
require(owner != address(0), "ERC20/invalid-owner");
uint256 nonce;
unchecked {
nonce = nonces[owner]++;
}
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
block.chainid == deploymentChainId ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(block.chainid),
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonce, deadline))
)
);
require(_isValidSignature(owner, digest, signature), "ERC20/invalid-permit");
allowance[owner][spender] = value;
emit Approval(owner, spender, value);
}다음과 같은 시나리오가 가능하다.
- 어떤 유저가 일정량의 트랜치 토큰을 가지고 있다고 가정하자. 또한 동일한 트랜치 토큰을 가진 LP가 여러개 있다고 가정하자. 예를 들어 USDX 풀과 USDY 풀이 있다.
- 유저가
requestRedeemWithPermit를 사용하여 USDX 풀을 청산하려고 한다. 유저가 permit 데이터에 서명하고 트랜잭션을 보낸다. - 공격자가 멤풀에서 이 트랜젝션을 확인하고 서명을 사용하여 USDY 풀에 상환 요청을 할 수 있다. 가스를 조절하여 프론트러닝 한다.
- 프론트러닝으로 의해 서명이 이미 사용되었으므로 nonce가 변동된다. 따라서 유저의 거래는 취소된다.
- 유저가 epoch가 끝날 때까지 공격자의 요청을 취소하지 않으면 이 요청은 실행되며, 유저의 USDX풀 대신 USDY 풀이 청산된다.
이 시나리오에서 유저는 일반적으로 큰 손실을 입지는 않는다. 원치 않는 풀이 청산되었지만 이 자산이 탈취되는 것은 아니기 때문이다. 하지만 USDY가 디페깅되는 상황 등 특수한 상황에는 사용자가 큰 손실을 입을 수 있다.
Impact
유저가 원래 청산하려고 한 풀이 아닌 원치 않는 풀을 청산하게 된다. 유저를 방해한다.
Mitigation
permit 서명에 청산할 LP의 정보를 추가한다.
tags: bughunting, centrifuge, smart contract, solidity, erc2612, erc20, frontrunning, dos, griefing attack, signature, severity medium