Post Mortem Report of FEG Dex
Summary
On May-15-2022 08:22:49 PM +UTC, the FEG Dex experienced a flurry of flashloan attacks, leading to approximately $1.3 million loss. The attacker found a critical vulnerability in the swapToSwap() function that takes the user input “path” parameter without any validation. Furthermore, this function will approve the contract that resides on the “path” parameter to spend the FEG Wrapped BNB in the current address. As a result, the attacker gained unlimited allowance and drained all the FEG Wrapped BNB within the FEGexPro Contract.
Related Addresses/Transactions
Attacker Address:
0x73b359d5da488eb2e97990619976f2f004e9ff7c
Attacker Contract:
0x9a843bb125a3c03f496cb44653741f2cef82f445
FEGexPro Contract:
0x818E2013dD7D9bf4547AaabF6B617c1262578bc7
Transaction Hash: 0x77cf448ceaf8f66e06d1537ef83218725670d3a509583ea0d161533fda56c063
Attack Flow
Prelude
The attacker borrowed 915.84 BNB via flashloan from PancakeSwap and changed 114.49 BNB to 115.65 FEG Wrapped BNB(fBNB).
Next, the attacker created 10 contracts that will be used in later attack.
Exploit
The attacker deposited 115.65 fBNB into the FEGexPro Contract by calling the depositInternal(address, uint256) function. View the source code from lines 649 - 654.
Step over line 654, we can see that _balance2[msg.sender] increases from 0 to 114.598.
Subsequently, the attacker called the swapToSwap(address path, address asset, address to, address amt) function. As we can see, the “path” parameter is taken without any validation, even though it approves the contract that resides on the address “path” to spend the fBNB of the FEGexPro Contract. View the source code in line 682. The attacker utilized this vulnerability and passed its own contract address that contained malicious code. As a consequence, the FEGexPro Contract incorrectly approved the attacker contract to spend 114.598 fBNB.
From lines 685 - 687, we can infer that the attacker contract implements the "swap" and "FEgexPair" interfaces to fake a valid contract.
Instead of transferring fBNB immediately, the hacker called the depositInternal(address, uint256) function again and deposited only 1 Wei into this contract. The variable "bef" defined in line 651 stayed constant during these two function calls. The trick is that the expression "IERC20(Main).balanceOf(address(this))" stayed unchanged since the hacker did not spend the allowance, so the variable "aft" remained unchanged too. Therefore, the variable "finalAmount" was still 114.598. In consequence, the attacker deposited 1 Wei and _balance2[msg.sender] increased from 0 to 114.598.
Next, the attacker again called the swapToSwap(address path, address asset, address to, address amt) function, but the "path" parameter was one of the contract addresses created in the Prelude section. Therefore, the FEGexPro Contract blindly approved the "path" parameter to spend 114.598 fBNB.
The attacker repeatedly called the depositInternal(address, uint256) and swapToSwap(address, address, address, address) function, and all attacker-controlled contracts got 114.598 fBNB allowances.
Take-Home Lesson
You should never trust user input directly, especially if the user input is anonymous. Remember the two golden rules: never trust user input, and always check data as it moves from an untrusted to a trusted domain. (Howard and LeBlanc, 2003)
References:
Howard, Michael, and David LeBlanc. Writing Secure Code. Microsoft Press, 2003.