sherlock-2023-08-cooler-h04
[H-04] isCoolerCallback can be bypassed
Summary
CoolerCallback 추상 컨트랙트를 상속하여 구체화하지 않은 컨트랙트를 콜백 컨트랙트로 등록할 수 있다. onRepay() 및 onRoll()를 구현하지 않아 채무자가 대출을 상환할 수 없고(트랜잭션 취소 됨), 이를 통해 대여자가 의도적으로 채무 불이행 상태을 만들고 담보 토큰을 가져갈 수 있다.
Keyword
lack of input validation, logic flaw, lending protocol
Vulnerability
clearRequest 함수는 요청받은 대출 요청을 수락하는 함수이다. 대여자가 호출하므로 msg.sender는 대여자이다.
isCallback_ 파라미터를 true로 넘기면 msg.sender(대여자)가 컨트랙트이며, 주요 함수가 호출되었을 때 대여자의 콜백 함수를 호출해달라는 의미이다.
이 때, 대여자 컨트랙트가 CoolerCallback 추상 컨트랙트의 구현체인지 확인하기 위해 isCoolerCallback() 를 이용한다.
function clearRequest(
uint256 reqID_,
bool repayDirect_,
@> bool isCallback_
) external returns (uint256 loanID) {
...
// If necessary, ensure lender implements the CoolerCallback abstract.
@> if (isCallback_ && !CoolerCallback(msg.sender).isCoolerCallback()) revert NotCoolerCallback();
...
}그러나 이 함수는 어떠한 보호 기능도 제공하지 않는다. 대여자가 CoolerCallback 컨트랙트를 상속하지 않고, 단순히 isCoolerCallback() 함수를 구현하고 true를 반환한다면 우회할 수 있다.
예를 들어 다음과 같은 악성 컨트랙트를 작성할 수 있다.
contract maliciousLender {
@> function isCoolerCallback() pure returns(bool) {
return true;
}
function operation(
address _to,
uint256 reqID_
) public {
Cooler(_to).clearRequest(reqID_, true, true);
}
function onDefault(uint256 loanID_, uint256 debt, uint256 collateral) public {}
}대여자가 의도적으로 onDefault() 함수만 구현하고 onRepay() 및 onRoll() 를 구현하지 않는다면 repayLoan() 및 rollLoan() 함수는 항상 실패하게 된다.
이로 인해 채무자는 대출을 상환할 수 없어 채무 불이행 상태가 된다. 대출 불이행 상태가 되면 대여자(공격자)가 claimDefault()를 호출하여 담보를 청구할 수 있다.
또한, 대여자가 대출 소유권 이전을 하는 방식으로도 isCoolerCallback() 를 우회할 수 있다. 처음에는 적법해보이는 컨트랙트로 대출을 허용한 뒤 콜백을 구현하지 않은 컨트랙트나 EOA에게 대출 소유권을 이전했을 때, loan.callback 플래그는 여전히 true로 설정되어 있다.
/// @notice Approve transfer of loan ownership rights to a new address.
/// @param to_ address to be approved.
/// @param loanID_ index of loan in loans[].
function approveTransfer(address to_, uint256 loanID_) external {
if (msg.sender != loans[loanID_].lender) revert OnlyApproved();
// Update transfer approvals.
approvals[loanID_] = to_;
}
/// @notice Execute loan ownership transfer. Must be previously approved by the lender.
/// @param loanID_ index of loan in loans[].
function transferOwnership(uint256 loanID_) external {
if (msg.sender != approvals[loanID_]) revert OnlyApproved();
// Update the load lender.
loans[loanID_].lender = msg.sender;
// Clear transfer approvals.
approvals[loanID_] = address(0);
}Impact
대여자가 담보 토큰을 받기 위해 강제로 채무 불이행 상태로 만들고, 채무자는 담보 토큰을 잃는다.
Mitigation
- 프로토콜에서 신뢰하는 주소에서만 콜백을 허용한다.
- loan.callback이 true로 설정된 경우 대출 소유권 이전 기능을 비활성화 한다.
tags: bughunting, olympus dao, smart contract, solidity, lending protocol, lack-of-input-validation-vul, logic flaw, severity high