Ability to transfer burned tokens, and later blocking TimeLock contract
Summary
There is an ability to transfer burned tokens.
Vulnerability Detail
The main problem is with the _isAuthorized function, not enough validation.
Firstly, I will introduce the functions, and in the second part I will explain how the exploit can be performed
CouncilMember.sol has a custom approve function, which adds an approved spender to the _tokenApproval mapping.
function approve(
address to,
uint256 tokenId
)
public
override(ERC721Upgradeable, IERC721)
onlyRole(GOVERNANCE_COUNCIL_ROLE)
{
_tokenApproval[tokenId] = to;
emit Approval(ERC721Upgradeable.ownerOf(tokenId), to, tokenId);
}
Then custom _isAuthorized function will be called if an approved spender would like to "spend" a token by calling transferFrom from OZ library
Let's discuss how it can be used to perform an exploit.
Alice - ERC721 token original holder
Bob - Ailce's friend
Step 1
Alice adds Bob to the _tokenApproval mapping, to allow Bob to transfer Alice's token. By calling approve function
Step 2
Alice to go GOVERNANCE_COUNCIL_ROLE role holder and asks to burn her token.
Then GOVERNANCE_COUNCIL_ROLE role holder calls burn function to withdraw all TELCOINs related to the ERC721 token and send an ERC721 token to 0 address
Step 3
Since Bob is approved to spend token with tokenId (which was Alice's token in step1), Bob can call the transferFrom function and transfer just the burned token to himself from the 0 address.
Because transferFrom has no check the from address is no 0 address. and the only check it has is to check that _isAuthorized function returns True. Which will return True in this case, because approve is attached to tokenId not to token holder address.
Impact
The token that Bob transferred from 0 address, will not receive Telcoins during token distribution.
But the main impact is that there are no functions for admins to take this approval from Bob on that ERC721 token.
This will allow Bob to pass the onlyCouncilMember check in the executeTransaction.sol contract to call functions such as executeTransaction or challengeTransaction.
The challengeTransaction can change proposedTransactions[transactionId].challenged = true;
So no one will be able to proposeTransaction and executeTransaction, because during the challengePeriod Bob can call challengeTransaction and "reject" the proposeTransaction
But if admins would like to change the challengePeriod to 0, and proposeTransaction and executeTransaction in one block, mev bot could plug their proposeTransaction and execute it before admins, to drain the tokens
Clean the _tokenApproval related to tokenId on every owner change of an ERC721 token.
OR add more check in _isAuthorized functoin
Strausses
high
Ability to transfer burned tokens, and later blocking TimeLock contract
Summary
There is an ability to transfer burned tokens.
Vulnerability Detail
The main problem is with the
_isAuthorized
function, not enough validation.Firstly, I will introduce the functions, and in the second part I will explain how the exploit can be performed
CouncilMember.sol
has a customapprove
function, which adds an approved spender to the_tokenApproval
mapping.Then custom
_isAuthorized
function will be called if an approved spender would like to "spend" a token by callingtransferFrom
from OZ libraryLet's discuss how it can be used to perform an exploit.
Alice - ERC721 token original holder Bob - Ailce's friend
Step 1 Alice adds Bob to the
_tokenApproval
mapping, to allow Bob to transfer Alice's token. By callingapprove
functionStep 2 Alice to go
GOVERNANCE_COUNCIL_ROLE
role holder and asks to burn her token. ThenGOVERNANCE_COUNCIL_ROLE
role holder callsburn
function to withdraw allTELCOIN
s related to the ERC721 token and send an ERC721 token to 0 addressStep 3 Since Bob is approved to spend token with
tokenId
(which was Alice's token in step1), Bob can call thetransferFrom
function and transfer just the burned token to himself from the 0 address.Because
transferFrom
has no check thefrom
address is no 0 address. and the only check it has is to check that_isAuthorized
function returnsTrue
. Which will returnTrue
in this case, because approve is attached totokenId
not to token holder address.Impact
The token that Bob transferred from 0 address, will not receive Telcoins during token distribution.
But the main impact is that there are no functions for admins to take this approval from Bob on that ERC721 token. This will allow Bob to pass the
onlyCouncilMember
check in theexecuteTransaction.sol
contract to call functions such asexecuteTransaction
orchallengeTransaction
. ThechallengeTransaction
can changeproposedTransactions[transactionId].challenged = true;
So no one will be able to
proposeTransaction
andexecuteTransaction
, because during thechallengePeriod
Bob can callchallengeTransaction
and "reject" theproposeTransaction
Code Snippet
https://github.com/sherlock-audit/2024-01-telcoin/blob/main/telcoin-audit/contracts/sablier/core/CouncilMember.sol#L304-L311
Tool used
Manual Review
Recommendation
But if admins would like to change the
challengePeriod
to 0, andproposeTransaction
andexecuteTransaction
in one block, mev bot could plug theirproposeTransaction
and execute it before admins, to drain the tokensClean the
_tokenApproval
related totokenId
on every owner change of an ERC721 token. OR add more check in_isAuthorized
functoinDuplicate of #35