이전 기사에서 강조한 바와 같이, Awesome Uniswap v4 Hooks 저장소[1]에 있는 프로젝트의 30% 이상이 취약점을 보유하고 있습니다. 여기서 언급하는 취약점은 Uniswap v4 상호작용에 특화된 것임을 주목할 필요가 있습니다. 따라서 이 기사에서는 다음 두 가지 관점에서 안전한 훅 상호작용 로직을 면밀히 살펴보겠습니다:
- 결함 있는 접근 제어
- 부적절한 입력 유효성 검사
각 범주에 대해, 취약점 분석으로 시작하여 대응하는 개념 증명(PoC)을 제공함으로써 잠재적인 악용 가능성을 시연할 것입니다. 이후 잠재적인 완화 전략에 대한 논의가 이어질 것입니다.
결함 있는 접근 제어
일반적으로 Uniswap v4의 훅과 관련된 상호작용은 훅이 로커(locker)로 작동하여 풀에서 작업을 수행하기 위해 PoolManager에서 잠금을 획득하는지 여부에 따라 분류할 수 있습니다. 적절한 접근 제어가 필요한 두 가지 주요 상호작용 시나리오는 다음과 같습니다:
- 훅-PoolManager 상호작용: 공식 콜백 함수와
PoolManager간의 상호작용을 포함합니다. 콜백 함수에는 8개의 풀 액션 콜백(즉,initialize,modifyPosition,swap,donate)과 잠금 콜백(즉,lockAcquired)이 포함됩니다.
- 훅-내부 상호작용: 훅 컨트랙트(로커로 작동) 내에서 발생하는 상호작용과 관련됩니다.
훅-PoolManager 상호작용은 비교적 간단합니다. 여기서 훅은 순수하게 훅으로 작동하여 8개의 풀 액션 콜백을 수락합니다. 훅의 로직은 관련 풀에 영향을 미치지 않으므로, 훅과 풀 사이에 자금 흐름이 없습니다. 콜백 함수가 제공하는 파라미터는 필요한 스토리지를 수정하거나 중요한 함수 파라미터로 사용됩니다. 핵심 고려 사항은 콜백 파라미터가 조작될 수 있는지 여부입니다.
훅-내부 상호작용은 다소 더 복잡합니다. 실제로 많은 훅 프로토타입은 순수한 훅 이상의 역할을 합니다. 일부 개발자는 훅이 사용자를 위한 자금 관리 기능을 제공할 수 있도록 합니다. 이러한 기능이 훅 컨트랙트에 구현되지 않을 수도 있지만, 이 맥락에서 이들을 집합적으로 훅으로 간주할 수 있습니다. 이러한 경우, 훅은 사용자 자금을 수락하고 유동성 관리나 스왑과 같은 풀 작업을 수행합니다. 이는 컨트랙트가 PoolManager로부터 잠금을 획득해야 하므로, 훅이 로커가 됨을 의미합니다.
Uniswap 재단은 이 상황을 고려하여 훅 템플릿에 함수를 통합했습니다. 구체적으로, BaseHook 템플릿은 다음과 같이 잠금 콜백으로 lockAcquired 함수를 제공합니다:
function lockAcquired(bytes calldata data) external virtual poolManagerOnly
returns (bytes memory) {
(bool success, bytes memory returnData) = address(this).call(data);
if (success) return returnData;
if (returnData.length == 0) revert LockFailure();
// if the call failed, bubble up the reason
/// @solidity memory-safe-assembly
assembly {
revert(add(returnData, 32), mload(returnData))
}
}
커스텀 로직을 실행하기 위해, lockAcquired는 data 바이트를 수락하고 해당 data를 사용하여 자신에 대한 저수준 호출을 수행합니다. data는 훅의 비즈니스 로직에 따라 달라지며 사용자가 조작할 수 있어, lockAcquired에 의해 트리거되는 훅-내부 상호작용으로 인한 보안 문제가 발생할 수 있습니다. 훅 설계는 매우 유연하여 이 상황에서 가능한 모든 시나리오를 다룰 수 없습니다. 여기서 주요 초점은 잠금을 획득하는 훅과 그 이후의 내부 상호작용입니다. 다른 잠재적인 비즈니스 로직을 깊이 다루면 이 논의가 너무 복잡해질 수 있습니다.
두 시나리오 모두에서, 이러한 함수들이 명확한 상호작용 주체를 가지고 있다는 점을 고려할 때, 잠재적인 악용으로 이어질 수 있는 결함 있는 접근 제어를 해결하는 것이 우선순위입니다. 이후 소섹션에서는 각 시나리오를 순차적으로 살펴보고, 더 안전한 상호작용 로직을 보장하기 위해 필요한 접근 제어에 대해 논의할 것입니다.
취약점 분석
접근 제어는 많은 프로젝트에서 매우 효율적이고 간단한 보안 솔루션으로 작동합니다. 함수가 특정 주체에 의해 호출되도록 설계된 경우, 접근 제어를 포함해야 합니다. 접근 제어의 가장 잘 알려진 예는 OpenZeppelin 라이브러리의 Ownable 컨트랙트로, 권한 있는 함수를 컨트랙트 소유자만 호출할 수 있도록 요구합니다. 위에서 논의한 두 시나리오가 이러한 유형의 제어에 적합한 사례임이 분명합니다.
훅-PoolManager 상호작용: PoolManager와의 안전한 상호작용을 위해, 훅은 이러한 콜백 함수에 필요한 접근 제어를 적용해야 합니다. 구체적으로, 이러한 콜백은 다른 계정이 아닌 PoolManager에 의해서만 호출 가능해야 합니다. 그러한 제어를 설정하지 못하면 이러한 민감한 인터페이스가 악의적인 행위자의 잠재적인 악용에 노출될 수 있습니다.
8개의 풀 액션 콜백 외에도, PoolManager로부터 잠금을 획득한 후 커스텀 로직을 실행하는 잠금(즉, lockAcquired) 콜백도 이 문제를 해결해야 합니다.
훅-내부 상호작용: 훅-내부 상호작용에 관련된 함수들도 특정 호출자에 의해 호출되도록 설계되어 있습니다. 앞서 언급한 바와 같이, 이 시나리오는 두 단계를 포함합니다. 먼저, 로커의 lockAcquired 함수가 PoolManager에 의해 호출되며, 이는 함수가 msg.sender를 PoolManager로 요구해야 함을 나타냅니다. 그 다음, 훅은 함수 호출을 적절히 디스패치합니다. BaseHook의 설계에 따르면, 이는 훅 자신에 대한 저수준 호출로 구현됩니다. 이는 해당 함수들이 external로 정의되어야 하고 호출자를 훅의 주소로 제한해야 함을 나타냅니다.
Awesome Uniswap v4 Hooks 저장소에 나열된 예시 중 하나인 Stop Loss Order[2]를 예로 들어보겠습니다:
유니스왑 V4 풀에 직접 통합되어, 스탑 로스 주문은 온체인에 게시되고 afterSwap() 훅을 통해 실행됩니다. 실행을 보장하기 위해 외부 봇이나 행위자가 필요하지 않습니다.
afterSwap 콜백 함수를 살펴보겠습니다:

분명히, 위의 함수는 민감한 작업을 수행하도록 설계되었습니다. 그러나 결함 있는 접근 제어로 인해, 악의적인 행위자가 인수(예: key 및 params)를 조작하여 예상치 못한 동작을 일으킬 수 있습니다. 예를 들어, afterSwap 콜백은 PoolManager에서 스왑이 이미 이루어졌다는 가정 하에 작동할 수 있습니다. 이후 현재 가격이나 수집된 스왑 수수료와 같은 필수 상태 정보를 기록하기 위한 조치를 시작할 수 있습니다. 그러나 afterSwap이 PoolManager에서의 호출을 엄격히 제한하지 않는다면, 악의적인 행위자가 params 파라미터를 위조하여 기록된 상태가 왜곡될 수 있습니다.
악용 & PoC
단순화를 위해 기본적인 PoC를 사용하여 이 접근 제어 문제를 설명하겠습니다. 일반적으로, 훅의 beforeInitialize는 PoolKey 타입의 파라미터를 수락하며, 이는 hooks 필드에 이 훅 주소를 포함해야 합니다(PoolManager가 이 필드를 사용하여 호출할 훅 주소를 결정하기 때문입니다).
스크린샷은 DiamondHookPoC[3]에서 볼 수 있는 결함 있는 접근 제어를 가진 훅의 악용을 시연하는 PoC를 제공합니다.
beforeInitialize 콜백 함수에 대한 접근 제한이 없는 경우, 악의적인 행위자는 이 함수에 임의의 poolKey를 제공할 수 있습니다. 훅은 이 poolKey의 훅이 현재 훅 주소와 일치하는지 확인하지 않습니다.

이 시나리오에서의 악용이 훅에 재정적 손실을 초래하지 않을 수 있음을 주목하는 것이 중요하지만, 그럼에도 불구하고 보호되지 않은 콜백 함수를 통해 훅의 상태가 어떻게 조작될 수 있는지를 극명하게 보여줍니다.
완화 방법
훅-PoolManager 상호작용의 보안을 보장하기 위해, 훅 콜백과 잠금 콜백 모두 PoolManager에만 접근을 제한해야 합니다.
다행히 Uniswap v4는 v4-periphery 저장소[4]의 BaseHook을 통해 모범 사례를 제공합니다.
BaseHook은 PoolManager에서의 호출만을 엄격하게 제한하는 poolManagerOnly 수정자를 제공합니다:
/// @dev Only the pool manager may call this function
modifier poolManagerOnly() {
if (msg.sender != address(poolManager)) revert NotPoolManager();
_;
}
이 수정자는 민감한 훅 및 잠금 콜백에 적절한 접근 제어를 적용하는 데 효과적으로 사용될 수 있습니다.
반면에, 훅-내부 상호작용의 존재는 BaseHook에서 지정한 lockAcquired 콜백을 통해 호출되는 중요한 상태 변경 함수들이 임의로 호출 가능하지 않아야 함을 요구합니다.
이 요구 사항을 충족하기 위해, BaseHook은 selfOnly 수정자를 제공합니다. 이 수정자는 선언된 함수의 접근성을 훅 자체로만 제한하여, 외부 컨트랙트가 악의적인 목적으로 이러한 민감한 함수를 직접 호출하는 것을 금지합니다.
/// @dev Only this address may call this function
modifier selfOnly() {
if (msg.sender != address(this)) revert NotSelf();
_;
}
요약하면, BaseHook을 상속함으로써 커스텀 훅은 이러한 내장 접근 제어 수정자와 콜백을 활용하여 적절한 접근 제어를 적용할 수 있습니다.
부적절한 입력 유효성 검사
v4-periphery[4]의 BaseHook은 더 안전한 상호작용 로직을 위한 솔루션을 제공하며, 훅 개발자들이 활용할 수 있습니다. 그러나 우리는 기존 훅에서 새로운 공격 벡터의 가능성을 열어두는 부적절한 사용 사례를 계속 관찰하고 있습니다.
기본적으로, 훅은 PoolManager의 initialize 함수를 통해 모든 풀이 등록되도록 허용합니다. 그러나 훅이 등록 풀의 기초 자산을 유효성 검사하지 못하면, 악의적인 사용자가 위조 토큰을 포함하는 풀을 등록하여 토큰의 transfer 함수를 통해 훅에 재진입할 수 있습니다.
이 취약점은 훅 자체가 악의적인 로직을 실행하지 않을 수 있기 때문에 미묘합니다. 그러나 훅이 PoolManager를 호출할 때, PoolManager와 악의적인 풀의 기초 자산 간의 상호작용이 PoolManager의 take 함수를 통해 공격자에게 제어 흐름을 넘길 수 있습니다.
/// @inheritdoc IPoolManager
function take(Currency currency, address to, uint256 amount) external override
noDelegateCall onlyByLocker {
_accountDelta(currency, amount.toInt128());
reservesOf[currency] -= amount;
currency.transfer(to, amount);
}
본질적으로, 취약점은 훅 사용자가 상호작용하려는 등록 풀에 대한 부적절한 유효성 검사에서 비롯됩니다. 구체적인 예시를 통해 이 취약점을 자세히 살펴보고 잠재적인 완화 전략에 대해 논의하겠습니다.
취약점 분석
Take Profits Hook[5]은 Awesome Uniswap v4 Hooks에 나열된 훅입니다:
이 예시에서, 우리는 사용자가 '이익 실현' 포지션을 설정할 수 있는 훅을 구축합니다. 예를 들어, ETH/DAI 풀에서 현재 1 ETH = 1500 DAI인 경우, "1 ETH = 2000 DAI일 때 내 ETH를 모두 팔아라"는 이익 실현 주문을 설정할 수 있으며, 이는 자동으로 실행됩니다.
이 훅의 _handleSwap 함수를 살펴보겠습니다. 이 함수는 잠금을 획득한 후 이익 실현 주문을 처리하기 위해 스왑을 수행합니다.
![그림 3: Take Profits Hook의 _handleSwap 함수[5]](https://assets.blocksec.com/frontend/blocksec-strapi-online/take_profit_handle_Swap_36133dc1fe.jpg)
이 함수가 어떠한 접근 제어 수정자로도 보호되지 않는다는 것을 알 수 있습니다. 그러나 250번 줄은 이 함수가 PoolManager로부터 잠금을 획득한 후에만 호출될 수 있도록 접근을 효과적으로 제한합니다. 그렇지 않으면, 운영자가 가장 최근의 로커가 아니기 때문에 poolManager.swap이 실패할 것입니다. 다시 말해, 등록된 풀이 유효성 검사된다면 _handleSwap은 특정 순서로 호출되어야 합니다. 불행히도, 훅은 그러한 유효성 검사를 구현하지 않습니다.
이 결함 있는 구현으로 인해, 훅은 재진입 공격에 취약합니다. 이 취약점은 공격자가 사용자가 예치한 자금을 사용하여 임의의 스왑을 강제로 실행할 수 있게 합니다.
악용 & PoC
구체적으로, 공격은 다음 단계를 통해 시작될 수 있습니다:
- 공격자는 위조 토큰으로 악의적인 풀을 등록하고, Take Profits Hook을 풀의 훅으로 지정합니다.
- 공격자는 훅을 통해 악의적인 풀에 이익 실현 주문을 설정합니다.
- 공격자는 악의적인 풀에서 스왑을 수행하여,
afterSwap콜백에서fillOrder를 트리거하여 공격자의 이익 실현 주문을 처리합니다. - 훅은 잠금을 요청하기 위해
PoolManager의lock함수를 호출하고,lockAcquired콜백에서_handleSwap함수를 호출합니다. _handleSwap함수에서, 토큰 전송이 위조 토큰 컨트랙트의 악의적인 로직을 트리거하여_handleSwap함수에 재진입합니다._handleSwap은 접근 제한이 없는 외부 함수이기 때문에 이것이 가능합니다. 잠금이 이미 획득되었으므로, 공격자는 훅이 충분한 기초 자산을 보유하고 있는 한, 훅이 임의의 풀에서 임의의 스왑을 실행하도록 강제할 수 있습니다. 그런 다음 공격자는 다른 사용자를 희생시켜 이익을 얻기 위해 스왑을 샌드위치할 수 있습니다.
다음의 상세 다이어그램은 공격의 흐름을 설명합니다.

앞서 언급한 바와 같이, 훅 자체는 악의적인 로직을 호출하지 않습니다. 유일한 실수는 훅이 신뢰할 수 없는 토큰 풀이 PoolManager 컨트랙트에 등록되는 것을 방지하지 않는다는 것입니다. 간접적으로, 위조 토큰 컨트랙트의 악의적인 로직은 토큰 전송 작업을 통해 호출되며, 이는 신뢰할 수 없는 외부 호출의 일종이기도 합니다.
완화 방법
부적절한 입력 유효성 검사로 인한 잠재적 공격을 완화하기 위한 세 가지 실행 가능한 접근 방식이 있습니다:
-
적절한 접근 제어.
BaseHook의 구성 요소를 활용함으로써, 훅은 함수 접근성을 엄격하게 관리할 수 있습니다. 이는 임의의 계정이 민감한 함수를 호출하는 것을 방지합니다. -
재진입 잠금. 위의 공격 시나리오에서, 이 접근 방식은 악의적인 토큰 로직이 민감한 함수에 재진입하는 것을 의심할 여지 없이 방지할 수 있습니다. 그러나 일부 경우에, 훅 설계는 훅 자체가 재진입 가능해야 합니다. 구체적으로, 훅이 일부 풀 액션을 실행해야 할 때, 이러한 액션을 완료하기 위해
PoolManager가 콜백에 재진입할 수 있도록 허용해야 합니다. 재진입 잠금은 이러한 의도된 기능을 손상시킬 수 있습니다. -
화이트리스트 접근 방식. 이는 권한 있는 관리자가 훅에서 승인된 풀을 화이트리스트에 추가하도록 요구합니다. 관리자는 화이트리스트에 등록된 풀이 잠재적 위험을 초래하지 않도록 보장합니다. 그러나 제한 사항은 훅 사용자가 훅을 통해 제한된 수의 관리자 승인 풀에서만 작업을 실행할 수 있다는 것입니다. 화이트리스트 접근 방식은 보안을 향상시키지만, 훅의 기능을 심각하게 제한합니다.
훅의 보안과 사용성을 균형 있게 조화시키는 완벽한 솔루션을 찾는 것은 어렵습니다. 여러 완화 접근 방식을 논의하는 동안, 개발자들은 훅 설계에서 트레이드오프를 신중하게 고려해야 할 것입니다. 목표는 의도된 기능을 유지하면서 잠재적 위험을 최대한 완화하는 것이어야 합니다. 또한, 우리의 논의는 Uniswap v4 기능과 관련된 상호작용에 특화된 취약점만을 다룹니다. 실제 응용 프로그램은 의심할 여지 없이 더 포괄적일 것입니다. 항상 컨트랙트의 모든 코드 줄을 이해하고, SAFU를 유지하세요!
결론
이 기사에서, 우리는 훅 상호작용 로직에서 발생하는 취약점을 탐구하며, 특히 결함 있는 접근 제어와 부적절한 입력 유효성 검사의 두 가지 시나리오에 집중합니다. 상세한 취약점 분석을 제시하고, PoC와 함께 잠재적인 악용을 설명하며, 잠재적인 완화 전략을 논의합니다. 우리는 이러한 통찰이 훅의 안전한 개발과 사용에 기여하고, 취약점 탐지에 대한 향후 노력을 안내할 수 있다고 믿습니다.
참고 문헌
[2] Stop Loss Order
[3] DiamondHookPoC
[4] v4-periphery
[5] Take Profits



