Better Programming

Advice for programmers.

Follow publication

Security Issues Plaguing the Web3 Ecosystem

Delving deep into these errors and their best prevention techniques

Samuel Okafor
Better Programming
Published in
13 min readMar 2, 2022

--

Losses as a result of a security breach in the Web3 universe can be overwhelming. I mean, it is always crazy when the financial losses are made public due to the huge amount involved almost every time. While this speaks volumes about the widespread adoption of blockchain technology, it also exposes its vulnerability. The technology is built on the tenets of immutability, decentralization, and transparency but has proven several times over the years to be vulnerable to attacks. Contrary to popular opinions, most of these attacks are barely sophisticated but rather a result of an error in the smart contracts. Most of these attacks are made on the smart contracts, either by finding loopholes or exploiting errors on the solidity code. Errors irrespective of their size or complexity can be fatal while writing smart contracts, and for this reason, security is a huge topic for budding blockchain engineers. Unlike the Web2 industry where an error will most likely lead to an application crash, errors in Web3 tend to be of more devastating financial consequences.

There has been a lot of security issues and instances involving the Web3 ecosystem but it's only right that we take a trip down memory lane to the very first one that gained huge traction which is none other than the famed DAO attack. The DAO was a venture capital fund instantiated on the Ethereum blockchain. DAO is an acronym for Decentralized Autonomous Organization, and as the name implies, the affairs of this organization were not controlled and dictated by a single entity. The objective was to create a transparent and decentralized platform where investors could pool funds together for both commercial and non-profit enterprises. It sought to eradicate the issue of fund mismanagement by a single entity which has been a widespread issue with existing venture capitals by giving powers directly to each investor. Investors received voting rights which they exercised when deciding on projects to invest in. As you would expect a Blockchain-enabled organization to be, everything about the organization was transparent. The codes that enforced its rules and regulations were made public for all to see and in turn led to a system where everyone had a clear idea of everything that was going on. This was revolutionary and a huge revelation at the time which influenced countless more that came after it but as it usually is with new projects, there were always going to be gaps and holes that would have been definitely corrected with time. I can assure you that this was the case with Facebook, Twitter, and the rest of the huge Web2 firms around today. Errors would have just caused their platforms to go down, and their engineers will go on a marathon to get everything back up and running. Unfortunately, this is not a luxury that Web3 firms can afford. When this unfortunate event happened, 3.6 million Ether worth around $50 million at the time was illegally transferred out to a child DAO by the attackers. At this point in time, the Ethereum platform was relatively still on the come up, and the DAO contained roughly 14% of all the Ethereum in circulation. The attacker was able to pull off the heist by discovering a split function in the contract which enabled him to siphon funds to a child DAO. He called it recursively till he amassed a third of all the Ethers held in the parent DAO. It is important to note that the Parent DAO had a constraint that prevented Ether to be moved till after 28 days, therefore this same constraint was inherited by the child DAO which rendered the attacker unable to access the funds right away. This situation bought time for the Ethereum community to decide on the right move to make in order to counter this attack. Due to the scale of this attack and the perceived impact it could have on the platform, Ethereum’s founder Vitalik Butherin deemed it necessary that the platform had to get involved. Two options were suggested, the first being a soft fork and the second being a hard fork of the Ethereum network. The soft fork basically involved adding snippets of code that effectively blacklisted the attacker and as a result prohibited him from moving out the stolen Ether. Before the foundation could proceed with this, a bug was discovered in the code which rendered the option inoperative. So, the hard fork appeared to be the last resort, this process involved reversing the Ethereum network to a time just before the hack occurred, so that the money held in the DAO can be safely transferred out and handed back to the investors. This caused an uproar in the Blockchain community and was considered hypocritical given what Blockchain was expected to stand for, which was immutability and decentralization. Talks about implementing the hard fork triggered a response from the attacker, who wrote a letter threatening the Ethereum foundation and claiming that he did no wrong whatsoever. His letter is attached below.

Despite the heavy backlash, the Ethereum foundation went ahead with the hard fork and in the process recovered the stolen funds. This hard fork resulted in a new Ethereum network that had undergone a form of alteration but the old network was still active and was as a result called the Ethereum Classic. Some staunch believers of the core principles of Blockchain technology stuck with the original network and decided not to get involved with the perceived defiled network. Fast forward years later, despite the uproar from the core blockchain community, it is safe to say that Vitalik and the team made the right decision.

It definitely wasn’t my intention to spend that much time on the DAO attack, as I wanted to highlight as many Web3 security failings as possible but it is a situation we have to make do with given the impact and notoriety of the attack. It won’t be wrong to say that its success gave rise to similar attacks that would happen in the future. We have seen the Web3 attack that arguably had the greatest impact and influence on the future of the ecosystem, it is now time we talk about the biggest heist of all time whose losses dwarfed that of the DAO. The Poly Network hack happened in August 2021 and led to a loss of cryptocurrency worth roughly $610 million. However, after appeals from Poly Network, a promise of $500,000 as a gesture for finding vulnerabilities in their system, and the offer of a role of chief security adviser, the hacker returned the proceeds of his attack. The hacker who was now referred to as “Mr. White Hat” claimed the attack was just intended to reveal the vulnerabilities of Poly Network and to help secure it. The Poly Network was a protocol that enabled the interaction of different blockchain networks. It facilitated the exchange of tokens from one ledger to another. From its inception to the time of the attack, it has successfully transferred $10 billion worth of digital assets between several blockchain networks. To fully comprehend this hack in its entirety, it is paramount that we understand the concepts of function visibility. Functions in solidity can have their visibilities set as private, public, internal, or external. When nothing is specified, it is automatically declared public. So, let’s take a deeper look into the various forms of visibility.

Private functions: Private functions can only be called in the contracts where they are declared. Even inherited contracts are incapable of calling this function. This type of function visibility is always advised because it greatly minimizes the risk of attacks and security issues. This visibility prohibits external access and modification of information stored on the smart contract.

Public functions: Just like the name suggests, these functions are public and can be called from anywhere. They are either called internally or using messages.

Internal functions: Due to its similarities with private functions, this has been a subject of confusion in the past. But the main difference between both is that unlike private functions that can’t be inherited by derived contracts, internal functions are.

External functions: External functions can be accessed by other contracts and transactions but can’t be called internally without the use of “this”, and this separated it from its public counterpart. After deployment, they are part of the contract interface and the vitality of this information is the reason why I had to explain the types of functions to get it across. Errors involving external functions has led to severe losses in the ecosystem, so it is important that engineers are fully aware of such functions and their implementation in the smart contract.

In layman's terms, external functions can be called outside its parent contract, so it makes perfect sense that attackers tend to look up such functions. Earlier, I mentioned that some attacks are successful not as a result of their sophistication, but it is fair to say that the Poly Network exploit was as sophisticated as it can get. In a nutshell, the attacker was able to call an external function in the EthCrossChainManager contract that enabled them to override the public keys.

The external function highlighted above in the code snippet triggered a series of responses that the attacker capitalized on. He was able to pass the public keys to his Ethereum wallet as a parameter. This was done on the “EthCrossChainData” contract which didn’t object due to the fact that this call came from its parent contract. These public keys had the privilege of approving transfers and withdrawals, therefore once the attacker had added his, he went on a withdrawal spree to his address. Attached below are links to the full solidity codes for both smart contracts.

EthCrossChainManager.sol and EthCrossChainData.sol

With the intention of making sure this article doesn’t get voluminous, I will not be going into details for other attacks. On the bright side, the cause of these attacks is all similar and borne out of similar vulnerabilities. These exploits stem from common errors that enable them to thrive. Some examples of equally popular attacks are the Wormhole attack, Mt. Gox, Coincheck, KuCoin, Bitmart among others. So, I will be going ahead by delving deep into these errors and their best prevention techniques.

1. Re-entrancy

This is one of the most devastating attacks in smart contracts. This attack has been responsible for some large-scale theft in the Web3 ecosystem, most notably the DAO attack. This attack takes place when an external call is made to a malicious contract which allows the malicious contract to make recursive calls to the original contract with the sole objective of draining funds stored in the contract. Despite the basic nature of this attack, its consequences over the years have been proven to be of a grave impact. State change after external calls have been made has proven to be a reoccurrence in contracts that have fallen victim to this attack. This enables the exploiters to make repeated calls to the withdrawal function till funds are completely drained or a stack is reached. The state change is intended to halt funds withdrawals and update balances therefore in cases where this is erroneously set after the withdrawal function, the withdrawal function can be called recursively ensuring that this state change is never effected. Below is a code snippet that highlights a state change coming after the external call.

The state change as specified above entails emptying the balance of the caller and in the process ensuring that there are no funds left to withdraw. This is the right thing to do but the position of the state change leaves the smart contract vulnerable to reentrancy attacks. Therefore, the solution would be to ensure that the state is updated before the external call is made.

So we have made the required alterations that will ensure the failure of any sort of reentrancy attack.

2. Integer Underflow and Overflow

This is another issue that can lead to security risks in a smart contract. Unlike others, this flaw is often overlooked but can be equally devastating. Numbers in solidity are declared using UINT(unsigned integers). UINT comes in different sizes, and some examples are uint8, uint16, uint64, uint256. Using smaller uint sizes like the uint8 saves storage and processing cost but also exposes us to the risk of integer overflow. The number of bits an unsigned integer can have is defined by its size, and this basically means that the largest number we can store in an uint8 is 255. We get this by doing (2 ^ 8 - 1). Now if we were to add 1 to 255 in uint8, it will give us 0. This is an integer overflow in a nutshell. In the case of underflow, subtracting 1 from 0 will return 255, and I think it is now clear to see why this could be a huge security concern. Let’s take a look at a smart contract with this issue.

One thing to point out from the code above is that I am using a version of solidity different from other snippets I have included. I had to use v0.7.0 to illustrate this security issue because v0.8.0 upwards has been secured from this security risk. Therefore, the most obvious prevention tip for this issue would be to use the latest version of Solidity because you would have nothing to worry about. But if you fancy older versions of Solidity then using bigger sizes of uint would be the right way to go. It’s also important to know that uint256 is automatically declared when we don't specify the size of the unsigned integer.

3. Self-Destruct

This is a function in Solidity that takes an address as an input. When this function is called, it deletes the bytecode of the contract and deposits the entire Ether in the contract to the specified address. We will be taking a look at a solidity code that gives room for this exploit. This is gotten from the solidity-by-example website, and I will try to break it down to its simplest bit. This contract defines the rules and regulations for a game on the blockchain. In this game, there is a target amount, and players are mandated to send just 1 ether to the contract. When a player sends 1 Ether and the contract balance is equal to the target amount, the entire ether in the contract is withdrawn to his address.

It is clearly specified in the code snippet above that a player can only send 1 Ether at a time with the hope of getting the balance to equal the targetAmount.

But using the selfdestruct function, it is possible to forcefully send Ether to the contract address and cause the balance to exceed the targetAmount. This will cause the rewards in the contract to be locked forever because there is no defined handling for a situation whereby the balance exceeds the targetAmount as this is not expected to happen.

Just as I mentioned earlier, the selfdestruct function deletes the bytecodes of a contract and withdraws the total Ether left in the contract to the address passed through the function.

By getting the contract address for the game, the attacker just has to pass it through the selfdestruct function to forcefully send Ether.

In Solidity, only address cast as payable are given the privilege of receiving Ether, so it is mandatory that the attacker casts the game contract address as payable. So with all this in place, executing the attack function sends Ether to the game contract, causing the balance to exceed the target, and then renders the reward permanently inaccessible.

Just like other attacks, we can take measures to ensure that we leave no room for the occurrence. But it is important to understand the vulnerability of the smart contract that made this attack possible before exploring its preventive measures.

Its vulnerability is a result of the misuse of this.balance. As a result of this, the contract is reliant on total value of Ether contained in the balance. This caused a direct interaction with the contract balance which makes for manipulation.

When the selfdestruct function is called, Ether is sent to the contract address, therefore a situation where the balance in the game is instantiated away from this.balance, renders the actions of selfdestruct ineffective. So, basically, the Ethers sent to the contract address don't add to the balance and have no effect on the outcome of the game. Therefore an attack attempt will basically mean we are taking receipt of free Ethers from our admirers somewhere in the world.

4. Default Visibilities

We surfed over this earlier but didn’t get to fully delve in. As I said, errors connected to this have been a recipe for disaster in the past. Function visibilities can be set as public, private, external, or internal and is automatically declared as public if we fail to specify. This determines whether a function can be called externally by users, derived by other contracts, or strictly internally. Incorrect use of visibilities exposes the function to the threat of external unauthorized access which can possibly spell doom for the Web3 project. This issue comes to life when smart contract developers make the mistake of ignoring visibility specifiers on functions that should be private. We will be seeing a contract that has this flaw to help us understand better.

The contract above is designed to govern an address bounty guessing game and a player must generate an Ethereum address where the last 8 hexes are zero to win. From what we can see, the visibilities for the function above are not specified and therefore set as public. As a result, an attacker can exploit this flaw by calling the public sendWinnings function and stealing the bounty. A popular example of an attack that was a result of this error was the first Parity Multi-sig hack that led to a loss of Ether worth $31M. Making it a habit to always specify the visibility of functions even if it’s a public function is the first step in eradicating attacks that stem from this error. Also, recent versions of Solidity show warnings during compilation for functions that have no specified visibility.

It may seem like we have covered a whole lot but trust me we have barely scratched the surface. The beauty of the Web3 ecosystem is its uncanny ability to remind you that you know nothing every now and then. One message that is evident throughout our journey is how disastrous security issues in Web3 are. The community keeps on exploring and seeking new ways to improve security and avoid heavy losses that have become widespread over the years. Best believe that despite the security failings and losses, Web3 is a revolution that is going to change everything for the better. Like I always say, it’s still the early stages and the best time to start taking part is now. There are various ways to engage and contribute to the Web3 ecosystem, and aside from the accompanying thrill and excitement, it gives us chance to be a part of something special. But despite the widespread adoption, promises, and potentials of the ecosystem, it is undeniable that it has been struck a fair share of attacks that have left an indelible mark and there are no signs that we have seen the last of them.

--

--

Samuel Okafor
Samuel Okafor

Written by Samuel Okafor

Blockchain engineer and writer for the Web3 ecosystem.

No responses yet

Write a response