https://code4rena.com/reports/2023-07-lens
Code4rena
Code4rena is a competitive audit platform that finds more high-severity vulnerabilities, more quickly than any other auditing method.
code4rena.com
https://github.com/code-423n4/2023-07-lens
GitHub - code-423n4/2023-07-lens
Contribute to code-423n4/2023-07-lens development by creating an account on GitHub.
github.com
lens.xyz
Lens Protocol is a composable and decentralized social graph, ready for you to build on so you can focus on creating a great experience, not scaling your users.
www.lens.xyz
[M-01] Identifying publications using its ID makes the protocol vulnerable to blockchain re-orgs
이 프로토콜에서 publications은 publications의 ID와 publisher의 프로필 ID로 구분된다. 예를 들어 유저가 act함수를 실행할 때 publication가 실행할 것은 publicationActedProfileId와 publicationActedId에 의해 결정된다.
그러나 publication ID는 publication의 정보를 기반으로 하지 않기 때문에 re-org가 발생할 경우 사용자가 잘못된 publication에 행동하도록 한다.
예를 들어
다음 트랜잭션이 다른 블록에서 실행된다고 가정하자.
block 1: Alice는 post()를 호출하여 post를 생성한다. publication ID는 20이다.
block 2: Bob은 post에 관심이 많아 해당 post에 PublicationActedId = 20으로 act()를 실행한다.
block 3: Alice는 comment()를 별도로 호출하여 또 다른 Publication을 생성한다. publication ID는 21이다.
여기서 re-org가 발생해서 block1이 삭제된다.
Alice의 comment는 21이 아닌 publication Id 20에 있다.
Bob이 block2에서 호출한 act()는 re-org 된 체인에 적용된다.
그러면 처음에 Bob이 act를 실행한 20에 적용되는 것이 아니라 21이 바뀐 20에 적용된다.
이 시나리오에 따르면 Bob은 처음에 자신이 원했던 것과 다른 것에 act()를 실행하게 된다.
그렇기 때문에 단순히 Id로만 구분을 하지 말고 keccak256 암호화를 사용한 해쉬도 함께 사용하면 동일한 Id에 대해서 잘못 publication 되는 것을 막을 수 있다.
[M-02] tryMigrate() doesn't ensure that followerProfiledId isnt already follwing
FollowNFT.sol에서 tryMigrate() 함수는 V2업그레이드 전에 마이그레이션 하기 위해서 사용한다.
_followTokenIdByFollowerProfileId 및 _followDataByFollowTokenId를 업데이트하여 진행된다.
_followTokenIdByFollowerProfileId[followerProfileId] = followTokenId;
uint48 mintTimestamp = uint48(StorageLib.getTokenData(followTokenId).mintTimestamp);
_followDataByFollowTokenId[followTokenId].followerProfileId = uint160(followerProfileId);
_followDataByFollowTokenId[followTokenId].originalFollowTimestamp = mintTimestamp;
_followDataByFollowTokenId[followTokenId].followTimestamp = mintTimestamp;
_followTokenIdByFollowerProfileId는 새로 지정되는 변수라서 0으로 설정될 것이다. V2업그레이 전에 진행한 유저라면,
이렇게 하면 오래된 follower가 tryMigrate()가 호출되기 전에 follow()를 호출하여 프로필을 다시 가져갈 수 있다.
function follow(
uint256 followerProfileId,
address transactionExecutor,
uint256 followTokenId
) external override onlyHub returns (uint256) {
if (_followTokenIdByFollowerProfileId[followerProfileId] != 0) {
revert AlreadyFollowing();
}
심지어 tryMigrate()를 프로토콜 팀이 호출하면 V2업그레이드 이후에. 악의적인 유저는 여전히 tryMigrate()전에 follow()를 할 수 있다.
어떻게?
1. 마이그레이션 트랜잭션 프런트러닝
2. 그의 프로필을 유지하고 다른 주소에서 NFT를 팔로우하면 tryMigrate가 return 0을 뱉음.
프로필은 같은 프로필을 두 번 follow 할 수 없기 때문에 tryMigrate()는 follow()를 호출한 old follower에 대해서 revertr가 난다. 그러나 _followDataByFollowTokenId[followerProfileId]가 0인지 확인하지 않으므로 tryMigrate()에서는 이를 적용하지 않는다.
결론은 follow() 이후에 tryMigrate()를 호출하면 하나의 profile에 대해 _followCount가 두 번 증가한다.
따라서 사실보다 더 많은 수의 followerCount를 올릴 수 있다. 그래서 tryMigrate() 함수에서 시작할 때
+ if (_followTokenIdByFollowerProfileId[followerProfileId] != 0) {
+ return 0;
+ }
0일 때만 통과하게 해야 한다.
[M-03] Users cannot unfollow if they do not own the FollowNFT of ther followTokenId used for their profile
만약 프로필의 followTokenId 가 wrapped면, 유저는 다음 중 하나에 해당하는 경우에만 unfollow를 할 수 있다.
1. followNFT의 소유자
2. followNFT소유자가 approve 한 사람
FollowNFT컨트랙트 unfollow함수에서 볼 수 있다.
// Follow token is wrapped.
address unfollowerProfileOwner = IERC721(HUB).ownerOf(unfollowerProfileId);
// Follower profile owner or its approved delegated executor must hold the token or be approved-for-all.
if (
(followTokenOwner != unfollowerProfileOwner) &&
(followTokenOwner != transactionExecutor) &&
!isApprovedForAll(followTokenOwner, transactionExecutor) &&
!isApprovedForAll(followTokenOwner, unfollowerProfileOwner)
) {
revert DoesNotHavePermissions();
}
위에서 보이는 것처럼, 오너가 아니거나 approve 받지 않은 유저는 unfollow 할 수 없다. 이는 사용자가 그 followNFT를 소유하지 않고도 followTokenId로 팔로우할 수 있기 때문에 문제가 된다. 예를 들어 followNFT를 가지고 있는 누군가가 approveFollow() 함수를 유저에게 호출한다. 그 유저가 _followWithWrappedToken()으로 approval을 확인하는 followTokenId() 함수에 해당하는 follow() 함수를 호출할 수 있다.
profile의 소유자가 모든 컨트롤 권한을 가지고 있어야 하는데 그것을 못하는 것이다.
따라서 unfollow() 함수에서 unfollowerProfileId의 owner인지도 체크해야 한다.
bool isFollowApproved = _followApprovalByFollowTokenId[followTokenId] == followerProfileId;
address followerProfileOwner = IERC721(HUB).ownerOf(followerProfileId);
if (
!isFollowApproved &&
followTokenOwner != followerProfileOwner &&
followTokenOwner != transactionExecutor &&
!isApprovedForAll(followTokenOwner, transactionExecutor) &&
!isApprovedForAll(followTokenOwner, followerProfileOwner)
) {
revert DoesNotHavePermissions();
}
만약 유저가 unfollow를 원하면 그는 스스로는 불가능하다. 그리고 followNFT의 소유자가 그의 프로필에서 unfollow를 하는 것에 의존해야 한다.
'Audit Report' 카테고리의 다른 글
[Audit Report] QuickSwap - 1 (0) | 2024.02.18 |
---|---|
[Audit Report] Particle Protocol (1) | 2024.02.12 |
[Audit Report] Axelar Network (2) (1) | 2024.01.21 |
[Audit Report] Axelar Network (1) (0) | 2024.01.18 |
[Audit Report] Trader Joe v2 (2) (1) | 2024.01.14 |