Behodler contest - shw's results

Ethereum liquidity protocol powered by token bonding curves.

General Information

Platform: Code4rena

Start Date: 27/01/2022

Pot Size: $90,000 USDC

Total HM: 21

Participants: 33

Period: 7 days

Judge: Jack the Pug

Total Solo HM: 14

Id: 78

League: ETH

Behodler

Findings Distribution

Researcher Performance

Rank: 1/33

Findings: 8

Award: $19,451.47

🌟 Selected for report: 6

🚀 Solo Findings: 3

Findings Information

🌟 Selected for report: shw

Also found by: kirk-baird, pauliax

Labels

bug
3 (High Risk)
resolved

Awards

1789.0736 USDC - $1,789.07

External Links

Handle

shw

Vulnerability details

Impact

Lack of access control on the assertGovernanceApproved function of FlashGovernanceArbiter allows anyone to lock other users' funds in the contract as long as the users have approved the contract to transfer flashGovernanceConfig.amount of flashGovernanceConfig.asset from them.

Proof of Concept

  1. Alice wants to execute a flash governance decision (e.g., disable to the protocol), so she first calls approve on the flashGovernanceConfig.asset to allow FlashGovernanceArbiter to transfer flashGovernanceConfig.amount of assets from her.
  2. An attacker Bob, who listens to the mempool, notices Alice's approve transaction and decides to front-run it. He calls assertGovernanceApproved with sender being Alice, target being any address, and emergency being true.
  3. As a result, Alice cannot execute her flash governance decision, and her funds are locked in the contract for the flashGovernanceConfig.unlockTime period.

Referenced code: DAO/FlashGovernanceArbiter.sol#L60-L81

Only allow certain addresses to call the assertGovernanceApproved function on FlashGovernanceArbiter.

#0 - gititGoro

2022-02-10T00:38:26Z

duplicate of 268

#1 - jack-the-pug

2022-02-16T08:37:32Z

Upgrading to high as the users' funds are at risk.

#2 - gititGoro

2022-03-02T03:01:49Z

The reason I stuck with medium risk is because the user's funds can't be lost in this scenario. Only temporarily locked. If the user unapproves FlashGovernanceArbiter on EYE then they simply have to wait until the unlock period has passed and can withdraw again.

#3 - jack-the-pug

2022-03-02T04:33:22Z

The reason I stuck with medium risk is because the user's funds can't be lost in this scenario. Only temporarily locked. If the user unapproves FlashGovernanceArbiter on EYE then they simply have to wait until the unlock period has passed and can withdraw again.

Agreed. This should be somewhere in between Med and High. If it's just the users' deposits being temporarily locked, then it's definitely a Med. But this one is taking probably all the funds from users' wallets and locking them against their will, easy to pull off by anyone, all at once for all potential victims.

I tend to make it a High so that the future wardens and probably by extent the devs can be more careful with allowances. We have seen so many incidents cause by improper handling of users' allowances.

A transferFrom() with from not being hard-coded as msg.sender is evil.

My fellow wardens, if you are reading this, do not go easy on a transferFrom() that takes an argument as from.

#4 - gititGoro

2022-05-30T04:10:01Z

Findings Information

🌟 Selected for report: shw

Also found by: cccz, danb, wuwe1

Labels

bug
3 (High Risk)
resolved

Awards

1207.6247 USDC - $1,207.62

External Links

Handle

shw

Vulnerability details

Impact

The implementation of the transferAndCall function in ERC677 is incorrect. It transfers the _value amount of tokens twice instead of once. Since the Flan contract inherits ERC667, anyone calling the transferAndCall function on Flan is affected by this double-transfer bug.

Proof of Concept

Below is the implementation of transferAndCall:

function transferAndCall( address _to, uint256 _value, bytes memory _data ) public returns (bool success) { super.transfer(_to, _value); _transfer(msg.sender, _to, _value); if (isContract(_to)) { contractFallback(_to, _value, _data); } return true; }

We can see that super.transfer(_to, _value); and _transfer(msg.sender, _to, _value); are doing the same thing - transfering _value of tokens from msg.sender to _to.

Referenced code: ERC677/ERC677.sol#L28-L29

Remove _transfer(msg.sender, _to, _value); in the transferAndCall function.

#0 - gititGoro

2022-02-10T00:03:37Z

duplicate of issue 1

#1 - jack-the-pug

2022-02-16T09:02:04Z

Dup #1

#2 - jack-the-pug

2022-02-16T09:02:30Z

Making this the main issue

#3 - gititGoro

2022-05-29T04:33:50Z

Findings Information

🌟 Selected for report: shw

Labels

bug
3 (High Risk)
resolved
sponsor confirmed

Awards

6626.1986 USDC - $6,626.20

External Links

Handle

shw

Vulnerability details

Impact

A logic error in the burnFlashGovernanceAsset function that resets a user's pendingFlashDecision allows that user to steal other user's assets locked in future flash governance decisions. As a result, attackers can get their funds back even if they execute a malicious flash decision and the community burns their assets.

Proof of Concept

  1. An attacker Alice executes a malicious flash governance decision, and her assets are locked in the FlashGovernanceArbiter contract.
  2. The community disagrees with Alice's flash governance decision and calls burnFlashGovernanceAsset to burn her locked assets. However, the burnFlashGovernanceAsset function resets Alice's pendingFlashDecision to the default config (see line 134).
  3. A benign user, Bob executes another flash governance decision, and his assets are locked in the contract.
  4. Now, Alice calls withdrawGovernanceAsset to withdraw Bob's locked asset, effectively the same as stealing Bob's assets. Since Alice's pendingFlashDecision is reset to the default, the unlockTime < block.timestamp condition is fulfilled, and the withdrawal succeeds.

Referenced code: DAO/FlashGovernanceArbiter.sol#L134 DAO/FlashGovernanceArbiter.sol#L146

Change line 134 to delete pendingFlashDecision[targetContract][user] instead of setting the pendingFlashDecision to the default.

#0 - gititGoro

2022-05-29T01:46:14Z

Findings Information

🌟 Selected for report: shw

Labels

bug
3 (High Risk)
resolved
sponsor confirmed

Awards

6626.1986 USDC - $6,626.20

External Links

Handle

shw

Vulnerability details

Impact

The LP pricing formula used in the burnAsset function of LimboDAO is vulnerable to flashloan manipulation. By swapping a large number of EYE into the underlying pool, an attacker can intentionally inflate the value of the LP tokens to get more fate than he is supposed to with a relatively low cost.

With the large portion of fate he gets, he has more voting power to influence the system's decisions, or even he can convert his fate to Flan tokens for a direct profit.

Proof of Concept

Below is an example of how the attack works:

  1. Suppose that there are 1000 EYE and 1000 LINK tokens in the UniswapV2 LINK-EYE pool. The pool's total supply is 1000, and the attacker has 100 LP tokens.
  2. If the attacker burns his LP tokens, he earns 1000 * 100/1000 * 20 = 2000 amount of fate.
  3. Instead, the attacker swaps in 1000 EYE and gets 500 LINK from the pool (according to x * y = k, ignoring fees for simplicity). Now the pool contains 2000 EYE and 500 LINK tokens.
  4. After the manipulation, he burns his LP tokens and gets 2000 * 100/1000 * 20 = 4000 amount of fate.
  5. Lastly, he swaps 500 LINK into the pool to get back his 1000 EYE.
  6. Compared to Step 2, the attacker earns a double amount of fate by only paying the swapping fees to the pool. The more EYE tokens he swaps into the pool, the more fate he can get. This attack is practically possible by leveraging flashloans or flashswaps from other pools containing EYE tokens.

The setEYEBasedAssetStake function has the same issue of using a manipulatable LP pricing formula. For more detailed explanations, please refer to the analysis of the Cheese Bank attack and the Warp Finance attack.

Referenced code: DAO/LimboDAO.sol#L356 DAO/LimboDAO.sol#L392

Use a fair pricing formula for the LP tokens, for example, the one proposed by Alpha Finance.

#0 - gititGoro

2022-02-11T03:16:01Z

This is actually a good fate inflation vector especially when combined with the fateToFlan conversion

#1 - jack-the-pug

2022-02-27T07:39:09Z

Good catch! A valid economic attack vector can potentially be exploited using flashloans.

#2 - gititGoro

2022-05-29T00:09:29Z

Findings Information

🌟 Selected for report: camden

Also found by: GeekyLumberjack, kirk-baird, shw

Labels

bug
duplicate
2 (Med Risk)
G (Gas Optimization)

Awards

362.2874 USDC - $362.29

External Links

Handle

shw

Vulnerability details

Impact

The generateFLNQuote is permissionless, meaning that anyone can call this function to update the latestFlanQuotes variables. However, when a token migrates from Limbo to Beholder, Limbo calls the stabilizeFlan function on UniswapHelper, which ensures block times between two latestFlanQuotes should be at least VARS.minQuoteWaitDuration apart.

Therefore, an attacker can continuously call the generateFLNQuote every VARS.minQuoteWaitDuration blocks to prevent tokens from migrating to Beholder, which effectively causes a DoS attack on the system. Although the attacker may not gain direct financial profit by launching such an attack, he might have an incentive to do it if it would cause a reputation loss on the Beholder protocol.

Besides, it might not be a good idea to let anyone update generateFLNQuote since the person can update the quotes when the prices have an advantage to himself (e.g., a higher or lower SCX price than the average). It further leads to an issue where people with conflicting preferences compete for the chance of updating the quote, causing the quotes to be updated too frequently and never be "stablized".

Proof of Concept

Referenced code: UniswapHelper.sol#L138-L145 UniswapHelper.sol#L162 Limbo.sol#L234

Only allow authorized people or contracts to update the quotes.

#0 - gititGoro

2022-02-05T03:04:24Z

The mitigation steps are wrong.

#1 - gititGoro

2022-02-10T04:12:13Z

duplicate of issue 102

#2 - jack-the-pug

2022-02-27T09:21:09Z

Dup #102

Findings Information

🌟 Selected for report: shw

Labels

bug
2 (Med Risk)
disagree with severity
resolved
sponsor confirmed

Awards

1987.8596 USDC - $1,987.86

External Links

Handle

shw

Vulnerability details

Impact

Most of the functions with a governanceApproved modifier call flashGoverner.enforceTolerance to ensure the provided parameters are restricted to some range of their original values. However, in the governanceApproved modifier, flashGoverner.setEnforcement(true); is called after the function body is executed, and thus the changed values are not restricted during the function execution.

An attacker can exploit this bug to change some critical parameters to arbitrary values by flash governance decisions. The effect will last until the community executes another proposal to correct the values. In the meanwhile, the attacker may make use of the corrupted values to launch an attack.

Proof of Concept

  1. An attacker executes a flash governance decision, for example, the adjustSoul function of Limbo, and sets the fps of a soul to an extremely large value.
  2. During the flash governance decision, some of his assets, for example, EYE, are locked in the FlashGovernanceArbiter contract.
  3. He calls claimReward to get his rewards on the corresponding soul (assume that he has staked some number of the token before). Because of the manipulated fps, he gets a large number of Flan tokens as the reward.
  4. Surely, he will lose his EYE tokens because of the malicious flash governance decision. However, as long as the attacker gets large enough Flan tokens, he is incentivized to launch such an attack.

Referenced code: DAO/Governable.sol#L46-L57 Limbo.sol#L380-L381 Limbo.sol#L327-L329 Limbo.sol#L530 Limbo.sol#L628-L630

Rewrite the _governanceApproved function and the governanceApproved modifier as follows:

function _governanceApproved(bool emergency) internal { bool successfulProposal = LimboDAOLike(DAO).successfulProposal(msg.sender); if (successfulProposal) { flashGoverner.setEnforcement(false); } else if (configured) { flashGoverner.setEnforcement(true); flashGoverner.assertGovernanceApproved(msg.sender, address(this), emergency); } } modifier governanceApproved(bool emergency) { _governanceApproved(emergency); _; }

#0 - gititGoro

2022-02-05T03:14:01Z

So this is a vulnerability for the very first execution of flashgovernance decision on a contract, after which it's safe. This is the type of thing that won't be acted upon because it will have gone away by the time the public interacts with Limbo. However, it is technically true so I'm confirming the issue.

#1 - jack-the-pug

2022-02-27T08:06:50Z

Valid finding, but the conditions are quite strict, downgraded to Med.

#2 - gititGoro

2022-04-09T02:35:52Z

FIX https://github.com/Behodler/limbo/pull/13

Note: an additional flash loan bug that wasn't picked up in the audit was fixed.

Findings Information

🌟 Selected for report: shw

Also found by: hyh, jayjonah8

Labels

bug
duplicate
2 (Med Risk)
resolved

Awards

536.7221 USDC - $536.72

External Links

Handle

shw

Vulnerability details

Impact

Most of the proposal contracts have a parameterize function for setting the proposal parameters, and these functions are protected only by the notCurrent modifier. When the proposal is proposed through a lodgeProposal transaction, an attacker can front-run it, modify the proposal parameters, and let the community vote it down. As a result, the person proposing loses his fate deposit.

Proof of Concept

  1. A benign user Alice wants to make a proposal, so she deploys one of the proposal contracts and sets the intended parameters. Her proposal is approved by the ProposalFactory and is ready to be proposed.
  2. Alice calls the lodgeProposal function of ProposalFactory to propose her proposal.
  3. An attacker Bob, who listens to the mempool, notices Alice's transaction and front-runs it. He calls the parameterize function to change the parameters to undesirable ones.
  4. Alice's proposal becomes the current proposal. However, the community rejects the proposal because of the changed parameters, causing Alice to lose her deposit.

Referenced code: DAO/Proposals/BurnFlashStakeDeposit.sol#L25-L37 DAO/Proposals/SetAssetApprovalProposal.sol#L21-L24 DAO/Proposals/ToggleWhitelistProposalProposal.sol#L22-L28 DAO/Proposals/UpdateMultipleSoulConfigProposal.sol#L40-L61 DAO/Proposals/WithdrawERC20Proposal.sol#L26-L32 DAO/ProposalFactory.sol#L74-L78

Only allow the creator of the proposal to modify the parameters.

#0 - gititGoro

2022-02-10T03:44:58Z

duplicate of issue 71

#1 - gititGoro

2022-05-31T22:16:12Z

Findings Information

🌟 Selected for report: kirk-baird

Also found by: shw

Labels

bug
duplicate
1 (Low Risk)

Awards

298.1789 USDC - $298.18

External Links

Handle

shw

Vulnerability details

Impact

The execute function of the WithdrawERC20Proposal contract calls limbo.withdrawERC20, which is, however, not implemented in Limbo but only defined in the LimboLike interface. Therefore, users cannot execute the WithdrawERC20Proposal proposal because the function call to limbo.withdrawERC20 always reverts, and the fate that voters spend in the voting process is wasted.

Proof of Concept

The withdrawERC20 function is not defined in the Limbo contract. Limbo inherits from Governable, but Governable does not define this function either.

Referenced code: DAO/Proposals/WithdrawERC20Proposal.sol#L35

Implement the withdrawERC20 function in the Limbo contract.

#0 - gititGoro

2022-02-11T03:00:17Z

duplicate of 165

#1 - jack-the-pug

2022-02-16T08:29:00Z

Dup #165

Findings Information

🌟 Selected for report: robee

Also found by: 0v3rf10w, 0x1f8b, BouSalman, Dravee, Fitraldys, Ruhum, bobi, cmichel, hyh, p4st13r4, shw

Labels

bug
duplicate
1 (Low Risk)
G (Gas Optimization)

Awards

17.3281 USDC - $17.33

External Links

Handle

shw

Vulnerability details

Impact

Some tokens that are not compliant with the ERC20 standard may return a false from the transfer or transferFrom functions to indicate the transfer failure. Without checking the returned value, the caller may not notice the transfer failure, which causes internal accounting errors.

Some tokens even do not return a boolean value, for example, USDT. In this case, directly calling ERC20.transfer would cause the transaction to revert because of the decoding error on the return value (OpenZeppelin's IERC20 interface expects a return boolean).

Proof of Concept

The following lines of code operates on tokens that can be arbitrary (and thus may not follow the ERC20 standard):

Limbo.sol#L216 UniswapHelper.sol#L230 FlanBackstop.sol#L88 FlanBackstop.sol#L95 DAO/LimboDAO.sol#L22 DAO/LimboDAO.sol#L24 DAO/LimboDAO.sol#L386

Use the SafeERC20 library implementation from OpenZeppelin and call safeTransfer or safeTransferFrom when transferring ERC20 tokens.

#0 - gititGoro

2022-02-09T23:42:03Z

duplicate of issue 43

#1 - jack-the-pug

2022-02-22T14:25:23Z

Dup #37

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter