Platform: Code4rena
Start Date: 17/03/2023
Pot Size: $36,500 USDC
Total HM: 10
Participants: 98
Period: 3 days
Judge: leastwood
Total Solo HM: 5
Id: 223
League: ETH
Rank: 26/98
Findings: 3
Award: $128.33
π Selected for report: 1
π Solo Findings: 0
25.8317 USDC - $25.83
The Bio Protocol allows users to mint Bio NFTs that represent user's bio. Once NFT is minted anyone can trigger tokenURI
to retrieve JSON data with the bio and generated svg image. Example JSON content (decoded from Base64):
{"name": "Bio #1", "description": "Test", "image": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIG1lZXQiIHZpZXdCb3g9IjAgMCA0MDAgMTAwIj48c3R5bGU+dGV4dCB7IGZvbnQtZmFtaWx5OiBzYW5zLXNlcmlmOyBmb250LXNpemU6IDEycHg7IH08L3N0eWxlPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBkb21pbmFudC1iYXNlbGluZT0ibWlkZGxlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj48dHNwYW4geD0iNTAlIiBkeT0iMjAiPlRlc3Q8L3RzcGFuPjwvdGV4dD48L3N2Zz4="}
The issue is that function Bio.tokenURI
is vulnerable to JSON injection and it is possible to inject "
character into bio completely altering the integrity of JSON data. User sends following data as bio
:
Test\", \"name\": \"Bio #999999999
This is will result in following JSON data (new name
key was injected):
{"name": "Bio #1", "description": "Test", "name": "Bio #999999999", "image": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIG1lZXQiIHZpZXdCb3g9IjAgMCA0MDAgMTAwIj48c3R5bGU+dGV4dCB7IGZvbnQtZmFtaWx5OiBzYW5zLXNlcmlmOyBmb250LXNpemU6IDEycHg7IH08L3N0eWxlPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBkb21pbmFudC1iYXNlbGluZT0ibWlkZGxlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj48dHNwYW4geD0iNTAlIiBkeT0iMjAiPlRlc3QiLCAibmFtZSI6ICJCaW8gIzk5OTk5OTk5OTwvdHNwYW4+PC90ZXh0Pjwvc3ZnPg=="}
Depending on the usage of Bio Protocol by frontend or 3rd party applications this will have serious impact on the security. Some examples of the attacks that can be carried:
image
to completely different one.image
to external URL
.Manual Review / Foundry / VSCode
It is recommended to properly encode user's bio to make sure it cannot alter returned JSON data.
#0 - c4-judge
2023-03-28T00:40:07Z
0xleastwood marked the issue as primary issue
#1 - c4-judge
2023-03-28T00:41:24Z
0xleastwood marked the issue as selected for report
#2 - c4-judge
2023-03-28T00:41:42Z
0xleastwood marked the issue as unsatisfactory: Overinflated severity
#3 - c4-judge
2023-03-28T00:42:19Z
0xleastwood marked the issue as selected for report
#4 - c4-sponsor
2023-03-29T19:24:48Z
OpenCoreCH marked the issue as disagree with severity
#5 - OpenCoreCH
2023-03-29T19:27:43Z
Good point that will be fixed, have some doubts about the severity. The attacks that are mentioned on the frontend in various issues (XSS / external resources) should be handled there in any case because the payload of a NFT should not be considered trusted. And if a user manipulates his own NFT, the impact is limited to him.
#6 - 0xleastwood
2023-03-30T20:11:49Z
Good point that will be fixed, have some doubts about the severity. The attacks that are mentioned on the frontend in various issues (XSS / external resources) should be handled there in any case because the payload of a NFT should not be considered trusted. And if a user manipulates his own NFT, the impact is limited to him.
It doesn't seem like this sort of tokenURI()
sanitisation can be made within the contract but instead must be handled by front-ends right?
#7 - OpenCoreCH
2023-03-30T20:18:22Z
Good point that will be fixed, have some doubts about the severity. The attacks that are mentioned on the frontend in various issues (XSS / external resources) should be handled there in any case because the payload of a NFT should not be considered trusted. And if a user manipulates his own NFT, the impact is limited to him.
It doesn't seem like this sort of
tokenURI()
sanitisation can be made within the contract but instead must be handled by front-ends right?
I implemented some sanitisation in the mean time (escaping all characters that need to be escaped for the JSON / SVG) using Solady's escape functions. But that's mainly to ensure that the produced JSON / SVG will always be valid (and the user does not accidentally produce an invalid one by choosing some characters in his bio). I think perfect sanitisation on-chain is not feasible in practice and the frontends should generally treat this payload as untrusted and sanitise it. Otherwise, you could also actively deploy malicious NFTs to e.g. target NFT marketplace users.
#8 - 0xleastwood
2023-03-30T20:26:48Z
Completely agree. I think medium severity would make more sense then.
#9 - c4-judge
2023-03-30T20:27:02Z
0xleastwood changed the severity to 2 (Med Risk)
25.8317 USDC - $25.83
It is possible to inject bio
that is a valid javascript code into generated on-chain SVG image. Attacker might pass following payload as bio
:
<script>alert(1234)</script>
which will result in generation of SVG image with the code:
<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 400 100"><style>text { font-family: sans-serif; font-size: 12px; }</style><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle"><tspan x="50%" dy="20"><script>alert(1234)</script></tspan></text></svg>
SVG image like that included anywhere on a website will trigger javascript code of the attacker (in this case alert(1234)
) that can compromise browser of every user viewing the image.
Following scenario is particularly dangerous:
Manual Review / Foundry / VSCode
It is recommended to properly encode user's bio to make sure it will not alter SVG image and inject Javascript code.
#0 - c4-judge
2023-03-28T03:53:15Z
0xleastwood marked the issue as duplicate of #212
#1 - c4-judge
2023-03-30T20:26:59Z
0xleastwood changed the severity to 2 (Med Risk)
#2 - c4-judge
2023-04-11T19:31:38Z
0xleastwood marked the issue as satisfactory
79.7314 USDC - $79.73
Function buy
of Tray
contract allows buying specified amount of trays. Algorithm used for generating tiles per tray is deterministic to allow users to see what trays he/she will receive. This is also stipulated in the documentation README.md:
The 7 tiles per tray are then generated according to a deterministic algorithm. A user can therefore precompute which trays he will get.
Implementation of such a feature could be presented to the user in the frontend by displaying trays will tiles that will be minted if the user buys it.
The issue is that the call to Tray.buy
can be frontrunned making this assumption incorrect and resulting with user spending $NOTE
tokens on trays with tiles she didn't expect/want. Moreover this might happen also in the situation where there are no malicious actors but only two users buying trays at the same time.
Manual Review / VSCode
It is recommended to add _lastHash
parameter to Tray.buy
function and compare it with the current saved lastHash
. In case these values are different the transaction should be reverted.
#0 - c4-judge
2023-03-28T18:52:07Z
0xleastwood marked the issue as duplicate of #121
#1 - c4-judge
2023-04-10T13:28:25Z
0xleastwood changed the severity to 2 (Med Risk)
#2 - c4-judge
2023-04-11T19:55:09Z
0xleastwood marked the issue as satisfactory
#3 - c4-judge
2023-04-11T20:03:22Z
0xleastwood marked the issue as duplicate of #121
#4 - c4-judge
2023-04-12T00:55:00Z
0xleastwood marked the issue as duplicate of #130
π Selected for report: Sathish9098
Also found by: 0xAgro, 0xSmartContract, 0xdaydream, 0xnev, Awesome, Aymen0909, BRONZEDISC, Bauchibred, Deathstore, Diana, IceBear, Jerry0x, Kresh, Matin, Rolezn, Stryder, T1MOH, Udsen, adriro, alejandrocovrr, atharvasama, codeslide, cryptonue, descharre, igingu, jack, joestakey, libratus, lukris02, luxartvinsec, nadin, nasri136, reassor, scokaf, shark, slvDev, tnevler
22.7749 USDC - $22.77
_mint
instead of _safeMint
revenueAddress
is privatenftCharacters
Low
Bio Protocol documentation README.md states that bio
has to be shorter than 200 characters:
Any user can mint a Bio NFT by calling Bio.mint and passing his biography. It needs to be shorter than 200 characters.
but the implementation allows bio
with 200 characters and only reverts if the bio is longer than 200 characters:
if (bytes(_bio).length == 0 || bytes(_bio).length > 200) revert InvalidBioLength(bytes(_bio).length);
Manual Review / VSCode
It is recommended to either fix documentation to reflect that bio can be maximum of 200 characters or adjust implementation and revert if bio length is 200 or more characters.
Low
Functions Tray.buy
and Namespace.fuse
do not follow checks-effects-interactions pattern which might lead to reentrancy attacks.
Tray.sol
:
Namespace.sol
:
Manual Review / VSCode
It is recommended to first set the effects of Tray.buy
and Namespace.fuse
functions and then charge the cost in the end of execution via SafeTransferLib.safeTransferFrom
.
Low
Changing critical addresses such as ownership should be a two-step process where the first transaction (from the old/current address) registers the new address (i.e. grants ownership) and the second transaction (from the new address) replaces the old address with the new one. This gives an opportunity to recover from incorrect addresses mistakenly used in the first step. If not, contract functionality might become inaccessible.
Tray.sol
:
Namespace.sol
:
Manual Review / VSCode
It is recommended to implement two-step process for changing ownership.
Low
Functions Tray.buy
and Namespace.fuse
allow buying specified amount of trays or fusing by paying with $NOTE
token. The issue is that the address of $NOTE
token can be changed at any time by the owner which might cause issue if the user approved contract Tray
or Namespace
to spend any other token than $NOTE
. Owner might frontrun user and change the address of $NOTE
token to address of the token user didn't intend to spend.
Tray.sol
:
Namespace.sol
:
Manual Review / VSCode
It is recommended to add _noteAddr
parameter to Tray.buy
and Namespace.fuse
and revert if _noteAddr
is different than note
address.
_mint
instead of _safeMint
Non-Critical
Functions Bio.mint
and ProfilePicture.mint
are using _mint
internal function instead of _safeMint
to mint ERC721 tokens. This might lead to issue if the function is triggered by another smart contract that does not support ERC721 tokens and tokens will get stuck in Bio
or ProfilePicture
contracts.
Bio.sol
:
ProfilePicture.sol
:
Manual Review / VSCode
Consider using _safeMint
instead of _mint
but make sure to not expose contracts to reentrancy attacks.
Non-Critical
The Profile Picture Protocol allows minting multiple PFP NFTs pointing to the same underlying NFT. This might cause issues if there will be any authorization mechanism implemented by 3rd party protocol that uses PFP NFTs.
Manual Review / VSCode
Consider adding check that will prohibit minting new PFP NFTs in case there is already one owned by the user.
Non-Critical
Function ProfilePicture.mint
allows minting PFP NFTs of another PFP NFTs. Since ProfilePicture.tokenURI
points to the tokenURI
of the underlying NFT it creates chain of NFTs. This might cause issues if there will be any authorization mechanism implemented by 3rd party protocol that uses PFP NFTs.
Manual Review / VSCode
Consider adding check that will prohibit minting PFP NFTs of the PFP NFTs.
revenueAddress
is privateNon-Critical
Storage variable revenueAddress
of Tray
and Namespace
contracts is private which makes it difficult to query its value.
Tray.sol
:
Namespace.sol
:
Manual Review / VSCode
It is recommended to make revenueAddress
public so it will be easy to query its value and add transparency to the protocol.
nftCharacters
Non-Critical
Function Namespace.burn
allows burning specified Namespace NFT. It deletes tokenToName
and nameToToken
storage variables and then triggers internal _burn
function. The issue is that its missing deleting nftCharacters
.
Manual Review / VSCode
It is recommended to delete nftCharacters
for the specified Namespace NFT in Namespace.burn
function.
Non-Critical
Multiple contracts are missing natspec comments which makes code more difficult to read and prone to errors.
Bio.sol
:
@return
param - https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-bio-protocol/src/Bio.sol#L40-L43ProfilePicture.sol
:
@return
param - https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-pfp-protocol/src/ProfilePicture.sol#L67-L70Tray.sol
:
@return
param - https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L117-L119@return
param - https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L191-L195@return
param - https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L200-L203_drawing
function - https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L245@return
param - https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L276Namespace.sol
:
@return
param - https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Namespace.sol#L88-L90Utils.sol
:
@return
param - https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Utils.sol#L69-L73@return
param - https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Utils.sol#L219-L222@return
param - https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Utils.sol#L263-L270Manual Review / VSCode
It is recommended to add missing natspec comments.
Non-Critical
Protocols Bio, Profile Picture and Namespace are missing pause functionality that could be used in case of security incidents and would block executing selected functions while the contracts are paused.
Namespace protocol:
Bio protocol:
Profile picture protocol:
Manual Review / VSCode
It is recommended to add functionality for pausing contracts and go through all publicly/externally accessible functions to decide which one should be forbidden from running while the contracts are paused.
#0 - c4-judge
2023-04-11T15:58:00Z
0xleastwood marked the issue as grade-b