Platform: Code4rena
Start Date: 09/12/2022
Pot Size: $90,500 USDC
Total HM: 35
Participants: 84
Period: 7 days
Judge: GalloDaSballo
Total Solo HM: 12
Id: 192
League: ETH
Rank: 40/84
Findings: 2
Award: $231.18
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: cccz
Also found by: 0xbepresent, Madalad, unforgiven
230.0301 USDC - $230.03
https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/GovNFT.sol#L19 https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/GovNFT.sol#L124-L165 https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/GovNFT.sol#L189-L204
In GovNFT
, the state variable maxBridge
is not used anywhere in the contract except it's setter function. This means it is either unneeded, or some intended contract functionality is missing.
As far as I can tell, this variable is meant to limit the amount of NFTs that can be bridged in a single transaction. This is likely so that _nonblockingLzReceive
does not revert when the uint array tokenId
is too large, as it contains a for loop iterating over the list and would risk running out of gas.
If this functionality is intended, extra require statements should be added to crossChain
and/or _nonblockingLzReceive
to ensure the amount of NFTs trying to be bridged is below maxBridge
. Otherwise, the state variable and setter function should be removed to save gas.
Either remove the maxBridge
variable, or update one/both of the following functions accordingly:
function crossChain(...) public payable { require(tokenId.length > 0, "Not bridging"); require(tokenId.length < maxBridge, "Too many"); // new line //... }
function _nonblockingLzReceive(...) internal { //... require(tokenId.length < maxBridge, "Too many"); // new line // mint the tokens for (uint i=0; i<tokenId.length; i++) { _bridgeMint(toAddress, tokenId[i]); } emit ReceiveNFT(_srcChainId, toAddress, tokenId); }
#0 - GalloDaSballo
2022-12-20T01:29:27Z
Agree that it's unused, cannot accept as Med though, definitely QA
#1 - GalloDaSballo
2022-12-20T01:29:46Z
NC
#2 - c4-judge
2022-12-20T01:29:53Z
#3 - C4-Staff
2023-01-23T08:27:42Z
Simon-Busch marked the issue as duplicate of #334
#4 - Simon-Busch
2023-01-23T08:28:09Z
Reverted back to M and marked as duplicate 334 as requested by @GalloDaSballo
#5 - c4-judge
2023-01-23T09:10:36Z
GalloDaSballo marked the issue as satisfactory
🌟 Selected for report: 0xA5DF
Also found by: 0xA5DF, 0xNazgul, 0xSmartContract, 0xbepresent, 0xdeadbeef0x, 8olidity, Englave, Faith, HE1M, JohnnyTime, Madalad, Mukund, Ruhum, SmartSek, __141345__, aviggiano, carlitox477, cccz, chaduke, francoHacker, gz627, gzeon, hansfriese, hihen, imare, jadezti, kwhuo68, ladboy233, orion, peanuts, philogy, rbserver, wait, yjrwkk
1.1472 USDC - $1.15
https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/StableVault.sol#L78-L83 https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/StableVault.sol#L44-L51 https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/StableVault.sol#L65-L72
StableVault
allows users to deposit any token allowed by the owner, and they are minted the stable coin (tigUSD
) in return.
However a hacked/malicious owner may, potentially within a single transaction, mint a useless ERC20 token with arbitrarily high initial supply, call listToken
to allow it to be deposited, call deposit
to deposit it, receive the minted stable token, and call withdraw
to use that stable token to withdraw any authentic tokens previously deposited by other users.
In this case users would not be able to withdraw the tokens that they deposited, and would only be able to withdraw the useless ERC20 attack token used by the hacked/malicious owner. The fact that this is possible within a single transaction leaves users with no opportunity to avoid their tokens being stolen.
The following test was passed within test/06.StableVault.js
:
it("Allows hacked/malicious owner to steal funds", async function () { // setup await stablevault.connect(owner).listToken(MockDAI.address); const depositAmount = ethers.utils.parseEther("1000000") // 1M dai await mockdai.connect(owner).transfer(user.address, depositAmount) // user deposits dai await mockdai.connect(user).approve(stablevault.address, depositAmount) await stablevault.connect(user).deposit(mockdai.address, depositAmount) // owner deposits useless token and withdraws dai const uselessERC20 = await ( await ethers.getContractFactory("UselessToken", owner) ).deploy(depositAmount) await uselessERC20.connect(owner).approve(stablevault.address, depositAmount) await stablevault.connect(owner).listToken(uselessERC20.address) await stablevault.connect(owner).deposit(uselessERC20.address, depositAmount) await stablevault.connect(owner).delistToken(uselessERC20.address) await stablevault.connect(owner).withdraw(mockdai.address, depositAmount) // user cannot get his dai back await expect( stablevault.connect(user).withdraw(mockdai.address, depositAmount) ).to.be.revertedWith("ERC20: transfer amount exceeds balance") })
After the attack, the stable token is backed only by uselessToken
, making it effectively worthless.
UselessToken.sol:
pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract UselessToken is ERC20 { constructor(uint256 initialSupply) ERC20("UselessToken", "UT") { _mint(msg.sender, initialSupply); } }
Hardhat
Implement a timelock for listToken()
and delistToken()
, so that users have a window to withdraw their funds before malicious actions become possible.
#0 - GalloDaSballo
2022-12-20T01:31:38Z
Will bulk it under the "Admin can Rug via new asser" and ignore the timelock which we dispute as unfalsifiable
#1 - GalloDaSballo
2022-12-23T18:03:50Z
Coded POC -> wins
#2 - c4-judge
2022-12-23T18:03:53Z
GalloDaSballo marked the issue as primary issue
#3 - TriHaz
2023-01-10T17:09:06Z
We are aware of the centralization risks, owner will be a multisig to reduce centralization until owner becomes the dao later.
#4 - c4-sponsor
2023-01-10T17:09:28Z
TriHaz marked the issue as sponsor acknowledged
#5 - c4-judge
2023-01-15T14:04:00Z
GalloDaSballo marked the issue as duplicate of #377
#6 - c4-judge
2023-01-22T17:34:39Z
GalloDaSballo marked the issue as satisfactory