Auditing the 'Flux' Smart Token Contract: Major Vulnerability Found, $5,000 Bug Bounty Denied With Team Also Refusing to Acknowledge or Patch (Part One)


15 min read
Auditing the 'Flux' Smart Token Contract: Major Vulnerability Found, $5,000 Bug Bounty Denied With Team Also Refusing to Acknowledge or Patch  (Part One)

This title, while lengthy, accurately summarizes (precisely) the full nature of my interaction with the 'Flux' token contract from investigation / analysis, to eventual discovery of a relevant vulnerability, divulging said vulnerability to the relevant "project leader" and, finally, denial of the vulnerability entirely by said project leader (more than likely for no reason other than an unwillingness to forfeit the bounty that they had created for such a discovery & subsequent revelation).

Specifically, the project leader for Flux / Datamine  "DAM" made it clear that:

  1. They are aware that the vulnerability we found has been exploited 'in the wild' before and allows for an attacker to drain the entire smart contract, should anyone with the knowledge (for which there are many) choose to do so.
  2. The project has no intentions on patching the vulnerability / disclosing it / or even attempting to address it.
  3. Even if the project disagrees with our assessment of the vulnerability (which is fair), it was made clear that the project would not seek any free, knowledgeable second opinions from smart contract experts such as Consensys and/or OpenZeppelin. When prompted for a reason for this blatant refusal none was given.
  4. The project had and has no intentions on honoring the $5,000 bug bounty that they offered publicly (despite the fact that our discovery, subsequent report, and additional proposed solution + mitigation[s] would be denied the promised bug bounty for no greater reason other than the team simply not wanting to honor the bug bounty they voluntarily created).

We made it clear to the team that, while disappointed in their failure to honor their word (and fulfill their pledge they promised publicly for the 'bug bounty'), we were more concerned about the gaping vulnerability / exploit that remained (and remains) in the code at the time of publishing.

We made it clear that - if nothing else - the team should at least patch the vulnerability, even said action is done in lieu of delivering promised payment to Librehash for its prompt discovery and revelation of a demonstrable exploit in the smart contract code (along with a bonus solution to mitigate said vulnerability). This option was duly refused.

What Are 'Bug Bounties'? I'm Confused

If you're new to the world of 'bug bounty' hunting / white hat 'hackers' / etc., then much of what was stated in the introductory section of this research report may have felt foreign to you - but don't worry! We won't leave you behind if you're still interested in figuring out exactly what the hell is going on (because its a lot).

To facilitate that end, this report was written with the 'lay person' in mind (although there are certain technical concepts that cannot be reduced beyond a certain level of 'simplicity').

Disclaimer: We are fully aware that it is typically bad practice to divulge the details of a newly discovered vulnerability to the public before the relevant project's team has had a chance to review said vulnerability and make the necessary fixes / amendments. However, in this instance, the team made it clear that they would not budge on their obstinate refusal to make even a modest attempt at reviewing the divulged vulnerability - even in spite of our insistence that they do so regardless of their intentions to honor the bug bounty or not (while it does suck that we were shafted out of the $5,000, we didn't want the team to avoid patching the vulnerability for the sake of 'saving face' and not paying us - since making such an amendment would essentially be an admission, in itself, that we had positively identified such a vulnerability).

Background

This project (and its bounty) came to our attention via a private message sent by one of our Discord members.

We'll keep that user's identity private (as it isn't relevant to this particular report), but the full text of what they sent to us is re-posted below for reference purposes:

"I saw your latest announcement in discord and thought this bounty may be of interest. If you want to take it up I can link you up with the developer. Although it’s pretty tight timeline now.. shouldn’t be an issue for you though :point_down:
Hey everyone, since there were no takers on our bug bounty and no bugs were reported in FLUX smart contract we'll be offering a substantially higher reward.""
"We are offering $ 5,000.00 USD bounty to anyone who is able to identify a major exploit/security vulnerability disclosure involving our smart contracts, namely the ability to gain extra FLUX rewards or withdraw DAM tokens that do not belong to their account. This bounty will expire after failsafe ends (~July 3rd)."
"It might take you just 10 minutes to look over our contract and see something super obvious. Find an exploit, send me a direct message and you get $ 5,000.00 in crypto of your choice (ETH/BTC/USDT/USDC etc.) :pray:"
"FLUX Smart Contract: https://etherscan.io/address/0x469eDA64aEd3A3Ad6f868c44564291aA415cB1d9#code
FLUX White Paper: https://github.com/Datamine-Crypto/white-paper/blob/master/docs/datamine-smart-contracts.md"

A screenshot of this message (as it appeared in our Discord chat) can be found below as well:

Following this message, the user sent us a link to the project's Discord channel as well as a directive to speak to a user named, 'HodlforJesus' on this particular server to relay whatever bug / vulnerability we had found (in the instance we did manage to find something).

That message is re-published below for reference purposes:

(note: The Discord link in the screenshot is 'expired' at the time of writing, but we were able to join the Discord channel before said link expired)

We Are Still in Flux's Discord Server

At the time of writing, we are still in that referenced Discord channel.

Below is a screenshot of the Discord server that was taken in 'real time' during the compilation of this Ethereum Token smart contract audit report:

(This will become more significant later on in the report).

Preparing for the Audit

We normally don't engage in smart contract audits, admittedly - but it was that very fact that motivated us to perform one in this instance (as well as an opportunity to receive $5,000 because - why not? Isn't that what bounties are there for?).

However, like any research task - one must 'set the stage' before barreling in head first.

Dissecting the Directive

Going back to the message that one of our users sent us regarding the bug bounty opportunity, the discovered bug needed to fit under the following criteria to be considered eligible:

A) Identifying a "major exploit/security vulnerability...involving [our] smart contracts"

B) In relation to 'A', they specified that the vulnerability should be one that would allow a would-be attacker to, 'gain extra FLUX rewards or withdraw DAM tokens that do not belong to their account.'

Beyond the date given as a deadline (which was red flag #1 because there's no logical reason for why anyone would want to put a deadline on something like this - think about it), there were no other directives.

Isolating the Relevant Contracts

Fortunately, the user in question that sent us the directive also gave us links to the token smart contract and its accompanying whitepaper (useful if we need to assess the intended design / function of the token in order to argue that the discovered anomaly - if discovered - is one that does, in fact, allow the smart contract to be manipulated in a way other than intended).

The relevant links (whitepaper and smart contract) are reposted below for convenience:

Flux Whitepaper = https://github.com/Datamine-Crypto/white-paper/blob/master/docs/datamine-smart-contracts.md

Flux Smart Contract = https://etherscan.io/address/0x469eDA64aEd3A3Ad6f868c44564291aA415cB1d9#code

Breaking Down the Smart Contract

In our experience, most vulnerabilities in smart contracts are unrelated to the proposed functionality of the token itself (i.e., some scheme like 'Balancer', for instance), and more often rooted in poor coding, typos, careless errors, and perhaps even downright laziness on the part of the developers (because the error may have been something that would have likely been caught if they had taken the time to simply 'double check' the code that was written or at least send it to a remedial 3rd-party auditing / approval firm that can help spot the 'obvious' mistakes).

As we'll see here, it appears that this smart contract's failure is borne from a sheer lack of competence and (perhaps) integrity on the part of the team. And if you deem that assessment to be a bit 'heavy-handed', it is likely that you will change your opinion by the end of this report.

Visiting Etherscan

Before we can perform any sort of analysis on the contract, we need to lay eyes on it first.

So let's follow the Etherscan link that was given to us: https://etherscan.io/address/0x469eDA64aEd3A3Ad6f868c44564291aA415cB1d9#code

Following the link above should bring users to a screen that appears as follows:

From here, users only need to scroll down slightly before they are presented with the full text of the Ethereum smart contract for this token - as promised:

Copy / Paste and Extract

At this point, we're simply going to copy / paste the contract elsewhere so that we can isolate it for analysis.

The first thing we're going to do is throw this code in a smart contract 'linter' (AKA: a static code analysis tool for Solidity-based [smart contract coding language] code snippets [contracts, in this case]).

Using 'SmartDec'

One of the blessings of the Ethereum ecosystem currently (and there aren't many, so count those blessings) - is the plethora of resources available online (and for free) for those that wish to delve deeper into specific smart contracts (or perhaps 'test' their own before official deployment onto the blockchain).

Fortunately, 'SmartDEC' provides such a tool (which is why we're using them ; on top of the fact that their tool does an amazing job).

Where to Access This Tool

URL = https://tool.smartdec.net

Upon clicking the link above, readers should arrive at the following page:

Below (on that same webpage) are further specifications breaking down exactly how the tool functions (and is used):

So, without further ado - let's put it to the test (this is where the copying that smart contract code is going to pay off because all that's needed here is for us to simply 'paste' the code we copied from the source on Etherscan for the relevant contract).

Disclaimer: As with any static code analysis, the purpose of this tool is to help us quickly parse through the smart contract in question and hopefully draw our attention to potentially problematic / troublesome parts of the code - which we'll need to further analyze manually.

Assessing the Static Code Analysis Tool's Results

In order to save users the trouble of having to dig through a bunch of screenshots, we wrapped the smart contract analysis results within an 'iFrame' so that readers can interactively assess the results within this report itself (if being accessed from 'librehash.org', home of the official Librehash blog).

See below:

Also, the link to said static code analysis can be found here: https://tool.smartdec.net/scan/198a50f0deb64a53b71ccadfd09c6cab (better to visit the link in order to get a deep dive overview of the tool's results when ran against this smart contract).

Results

After this tool is ran, it produces a list of various lines of code that triggered the linter for various reasons (which can be parsed through on the right-hand side of the screen via the URL directly, as seen above).

Most of the results that are generated here are relatively harmless (preferential suggestions and potential conflicts / issues in the code itself).

However, there were a few areas of the code that the smart contract linter identified that were a legitimate cause for concern.

Use of the 'Approve' Function

As we'll see in the screenshot below from the linter, it correctly identified a major no-no in modern smart contract construction - which is the reckless deployment of the 'approve' function (see below):

The relevant code is re-published below for edification:

 function approve(address spender, uint256 value) public override returns (bool) {
        address holder = _msgSender();
        _approve(holder, spender, value);
        return true;
    }

   /**
    * @dev See {IERC20-transferFrom}.
    *
    * Note that operator and allowance concepts are orthogonal: operators cannot
    * call `transferFrom` (unless they have allowance), and accounts with
    * allowance cannot call `operatorSend` (unless they are operators).
    *
    * Emits {Sent}, {IERC20-Transfer} and {IERC20-Approval} events.
    */
    function transferFrom(address holder, address recipient, uint256 amount) public override returns (bool) {
        require(recipient != address(0), "ERC777: transfer to the zero address");
        require(holder != address(0), "ERC777: transfer from the zero address");

        address spender = _msgSender();

        _callTokensToSend(spender, holder, recipient, amount, "", "");

        _move(spender, holder, recipient, amount, "", "");
        _approve(holder, spender, _allowances[holder][spender].sub(amount, "ERC777: transfer amount exceeds allowance"));

        _callTokensReceived(spender, holder, recipient, amount, "", "", false);

        return true;
    }

The Issue With the 'Approve' Function as Deployed

To gain a better understanding of what the 'big deal' is here, we need to first 'zoom out' for a second and detail a few facts about this smart contract.

Fact Number One: This is Not an 'ERC20' Smart Contract

Surprise, surprise (not really).

We're so used to every smart contract that we refer to (and come across) being an ERC20 smart contract (which refers to a specific standardization for tokenization on the Ethereum protocol), but this case is a bit different.

This contract is instead an 'ERC777' contract.

Some Quick Reference Links RE: ERC777 Smart Contract Standard:

  1. Background Information on Ethereum Token Standards (namely the erc20 + erc777 token standards) = https://hackernoon.com/erc777-is-the-new-token-standard-replacing-the-erc20-fd6319c3b13
  2. Information Specific to the ERC777 Standardization (per GitHub discussions shelling out said differences): https://github.com/ethereum/EIPs/issues/777
  3. Official GitHub Repo for the ERC777 Token Standard on Ethereum (worth referring to on a continued basis if needed): https://github.com/0xjac/ERC777

Relevance to This Analysis

It is important to note that the contract does not adhere to the ERC20 smart contract token standard because this dictates that we must look at a different framework in order to assess the stability / security / comprehensiveness of design of the smart contract in question.

The Apple Doesn't Fall From the Tree Though

Ironically, this piece of code that we're examining looks at the portion of code that specifically 'interfaces' with the ERC20 standard.

This can even be seen in the 'code notes' within the smart contract itself, which we'll look at in one second.

But first - let's address the elephant in the room.

The Entire 'Foundational' Code for the Smart Contract Was Plagiarized From 'OpenZeppelin'

The code was so poorly plagiarized (poorly = without even a modest attempt at making an amendment to the code as copied), that the file source that the code was copied from can be found in the opening lines of the smart contract itself (see below):

/**
 *Submitted for verification at Etherscan.io on 2020-06-08
*/

// File: @openzeppelin/contracts/GSN/Context.sol

pragma solidity ^0.6.0;

/*
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with GSN meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
contract Context {
    // Empty internal constructor, to prevent people from mistakenly deploying
    // an instance of this contract, which should be used via inheritance.
    constructor () internal { }

    function _msgSender() internal view virtual returns (address payable) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes memory) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

// File: @openzeppelin/contracts/token/ERC777/IERC777.sol

Notice the last line in the code excerpt above (from the Flux Contract):

// File: @openzeppelin/contracts/token/ERC777/IERC777.sol

This code comment tells us which specific solidity contract (produced by OpenZeppelin) we should seek in order to find the 'base code' from which the Flux Smart Token Contract Was Written.

Finding the Original Smart Contract Code From the Plagiarized Re-purposed Flux Contract Deployment

Finding the original code isn't difficult because it was published publicly by OpenZeppelin (purposefully).

The relevant link exists here: https://docs.openzeppelin.com/contracts/2.x/erc777

Purpose of the Code's Existence

As is made clear in OpenZeppelin's documentation, the code was publicly published to provide a template for developers figuring out how to craft their own ERC777 smart contract (successfully).

Obviously, as such, that means that the code provided is primarily meant to be used as a template and not for production usage (in the way that Flux used it - recklessly).

OpenZeppelin Documentation Explains Interoperability Between the ERC20 and ERC777 Token Standardizations

Clearly.

See below:

Notice the mention of the 'approve' function in the excerpt above.

ERC777 Interoperability With the ERC20 Token Standard is Facilitated via IERC20 (Interfacing Provision)

To understand this portion of the code, we're going to first travel to this link: https://docs.openzeppelin.com/contracts/2.x/api/token/erc20

And scroll down the page until we come across the following:

Embarrassing Oversight by Flux

Somehow, some way - despite the fact that they had to have consulted the OpenZeppelin code in order to plagiarize it to deploy for their Flux Smart Contract, it appears that they missed the clear warning provided in OpenZeppelin's own documentation re: using the 'approve' function actively in the code (in the exact manner that the Flux Smart Contract does).

See Below:

Specifically the accompanying warning in the documentation for this function, states:

"Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender’s allowance to 0 and set the desired value afterwards: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729"

There Are Proposed Mitigations

In the documentation provided by OpenZeppelin for ERC777 smart contract deployment, they proposed a number of options that developers could explore to mitigate the potential vulnerabilities / exploits that Flux's specific (reckless) use of the 'approve' function opens up.

See below:

(Screenshot above proposes the use of two functions, 'decreaseAllowance' and 'increaseAllownace' - deployed as appropriate - to mitigate the potential vulnerabilities created by Flux's use of the 'approval' function in the section of their smart contract that interfaces with the ERC20 token standard).

Flux Did Not Employ Any of the Proposed Mitigations (Unsurprisingly)

Again, as stated above, it appears that the team simply copied an (outdated) ERC777 base contract template that OpenZeppelin had lying around somewhere (the 'aged' statement reinforced by the fact that the code referenced Progma version 0.5 in the original code).

How the 'Approve' Function Could Be Exploited

There are a wealth of resources that explore this issue with great detail (including whitepapers).

Whitepaper Detailing This Specific Vulnerability in Ethereum Smart Contracts

Yes, there is actually a whitepaper that was published not so long ago (in arxiv, albeit) that details the numerous ways in which smart contracts that fail to employ recommended mitigation schemes can be exploited.

The link to said whitepaper can be found here: https://arxiv.org/pdf/1907.00903.pdf

Whitepaper Blasts the Use of the 'Approve' Function ERC20 Smart Contracts

No, we didn't forget that Flux's smart contract utilizes the ERC777 standard - but, as mentioned prior, this portion of the code that we're examining interfaces with the ERC20 smart contract standard to facilitate the execution of the function(s) that are being scrutinized here in this whitepaper.

The paper spares no time in dissecting the issue with the 'approve' function (when deployed in the same reckless manner as it is in the Flux contract), stating:

"The issue concerns ERC20's defined method approve() which was envisioned as a way for token holders to give permission for other users and dapps to withdraw a capped number of tokens."
"The security issue arises when a token holder wants to adjust the amount of approved tokens from N to M (this could be an increase or decrease). If malicious, a user or dapp who is approved for N tokens can front-run the adjustment transaction to first withdraw N tokens, then allow the approval to be confirmed, and withdraw an additional M tokens."

Of course, this is not a theoretical vulnerability - but rather a real one that has been exploited 'in the wild' numerous times, which the paper acknowledges.

Additionally, the paper (published in 2019 - before the release of the Flux smart contract), proposes, "10 mitigations for [the vulnerability]", while finding that "no solution is fully satisfactory", leading the authors to propose, "2 new solutions that mitigate the attack, one of which fully fulfills constraints of the standard, and the second one [which] shows a general limitation in addressing this issue from ERC20's approve method."

GO TOP