DEV Community

Cover image for Developer’s Guide To Web3 Security
Akshat for Openware

Posted on

Developer’s Guide To Web3 Security

Learn About The Industry’s Best Practices, Security Roles, And How To Approach Web3 Security As a Developer

Web3 has finally entered the mainstream consumer space; and its effect is undeniably strong. Even the big tech realizes this, as it has started building the supporting infrastructure. This moment also marks a major shift in the tech landscape as we transition from the company owned Web2, to the user-owned Web3 model.

But as with any new technology, Web3 comes with a whole new set of challenges, and security remains a big issue. According to Certik– a leading Web3 security firm, DeFi has lost more than $2 billion in the first two quarters of 2022 alone, which just goes to show how bad the situation is.

Cyber attacks have become increasingly common and there has been a parallel surge in demand for security professionals in the industry. Since Web3 is still in its nascency, this presents itself as a perfect opportunity for people who are looking to get a headstart in this space.

How Secure is Web3?

Security in Web3 revolves around two central ideas, decentralization and strong encryption. These features are native to the blockchain technology, which is a self-protecting database that resists mutation due to its design. But unlike traditional databases, a blockchain has several layers of protection.

Every piece of data on a blockchain is a transaction, and to verify its authenticity a blockchain uses a ‘Consensus Mechanism’. This algorithm requires all the miner nodes on a blockchain to reach a common ground for a transaction before it can get recorded. Additionally, every transaction is asymmetrically encrypted, and its ownership can be justified.

The security of a blockchain is established on one simple fact; that the computing power required to create fake transactions has to be greater than the combined processing power of all participating miner nodes. As the blockchain gets bigger and computers naturally get faster, the cost of brute-force or data corruption attacks on a blockchain becomes impossibly high.

But even with this security, Web3 systems are still constantly at the receiving end of cyber attacks. So much so, that it has painted an overall grim picture for some people in the community. And this raises the question, is Web3 really more secure than Web2? The short answer is Yes.

The reasoning behind this is quite simple; as most of the security breaches that we see in the blockchain world today (with the exception of some) are not related to the functioning of the blockchains themselves. These attacks mostly occur on application layer, or from newer technologies built around the blockchain that facilitate cross-chain communication.

Web3 Security Vectors

The list of attack vectors in Web3 is long and constantly growing; and for the sake of this article, we’ll limit the scope to only those pathways which depend upon the functioning of Decentralized Applications

Smart Contract Vulnerabilities

Smart Contract Vulnerabilities

Although Web3 has been around for a while now, the ecosystem for writing smart contracts is immature. Writing blockchain apps requires a deep understanding of smart contract programming languages, and an in-depth knowledge of blockchain networks. And due to the absence of a mature ecosystem, developing enterprise-grade blockchain apps is quite difficult.

The EVM is still experimental in nature, and bugs are easier to induce than they are to detect. A good example of this would be the re-entrancy attack which only takes the programmers to incorrectly structure the sequence of operation for the vulnerability to take effect. SWC registry records some of the most common vulnerabilities which we’ll cover in a little detail later.

Programming vulnerabilities are the leading security concern even today. Smart Contracts ideally need to go through several stages of security audits, before they are considered ready for production.

DeFi Semantics

Semantics Based Attacks Are Hard To Detect

Web3 provides a native ecosystem for finance as cryptocurrencies are embedded right into its framework. However, this has also created problems for developers and businesses as now attackers have an easier, and more direct access to it. Additionally, the functioning of a DApp is directly dependent upon the liquidity of the environment; which, as of today, is quite poor.

This allows more sophisticated, semantic-based attacks to occur. One of the simplest forms of this attack is market manipulation in DEXs to create artificial pumps and dumps. Attackers can make use of features of DeFi like lending, borrowing, flash-loans and combine them together in ways to attack the entire system.

The price oracle manipulation attack also comes under this category, where attackers use the weakness of on-chain oracles to manipulate prices either through flash-loans or borrows. This is now used to artificially increase the prices which leads to liquidations and a steep fall in the price of the token.

Compared to smart contract weakness based attacks, these are harder to detect. And to fully understand and assess risk, businesses need to hire experts who understand the high-level semantics of a DeFi application.

Blockchain Trilemma

Blockchain Trilemma

Unlike the other two weaknesses, Blockchain Trilemma is related to the weakness of blockchain and its surrounding technologies. And it refers to the notion that blockchain systems cannot simultaneously be highly scalable, secure and decentralized. Until recent innovations, this has more or less held true especially in the case of Layer–1 networks.

The idea originates from the CAP theorem, which was formulated in the 1980s. And according to the theorem, amongst security, scalability and decentralization; a distributed system can only provide two of the features at once. Blockchain being a distributed system, follows this theorem.

Centralization of resources improves speed, but it also puts all responsibility on a single entity which reduces security. On the other hand, decentralization improves security, but slows down a network considerably. Most of the high TPS Layer–1 networks still suffer from this problem. The trilemma is further proven right in the case of CEXs and DEXs, where each have their benefits over the other.

However, recent innovations in the blockchain space have created a pathway for Layer–1 networks to scale while keeping their decentralization and security intact. Layer–2 scaling solutions like state-channels, sidechains and nested chains have been able to greatly improve the scalability of Layer–1 networks without harming their security.

Ensuring Security in An Unstable Environment

Web3 gets attacked often because it is still in its elementary phase, and these attacks are more like growing pains. Opposed to Web2 applications, which follow the conventional client-server architecture, the rules and frameworks concerning the security of Web3 apps, are still under development.

Moreover, since there’s no formal Web3 consortium or a central standards organization, a lot of development around the security of Web3 applications is scattered and distributed amongst several independent organizations. As such, securing Web3 applications currently is more challenging than Web2.

Even some of the most popular products like Binance, Uniswap, tornado, and Harmony face regular attacks. While ensuring complete security in this environment is unrealistic, we can get close to it by following simple rules; Given below are the steps you can take as a developer to secure your Decentralized Apps.

Following The Design Standards

Web3 companies have different solutions for a single problem, and a lot of them are new, and largely untested in real world environments. This is bad from a security standpoint and puts the business and its customers at risk.

However the risk can be reduced significantly by adhering to the coding standards, and following some rules. Colloquially speaking, design pattern is a general reusable solution to a given problem; whose security is well established.

When it comes to writing smart contracts, there are design patterns which deal with different aspects of a DeFi application such as, optimizing gas costs, safely handling transactions and securely managing storage. Smart Contract developers can make use of the SWC registry as their go-to handbook for writing coherent, and secure code. We’ve discussed a few of those below.

Withdrawal Pattern

Handling transactions is one of the most trivial but sensitive tasks, and there are several ways to do it. Solidity provides send, transfer and call functions for transferring ether through a smart contract.

Although all three of these functions are secure and can be used for making transfers, in practice, there are certain quirks about making transactions. We’ll use the smart contract below (taken from Solidity Docs) to serve our example.

pragma solidity ^0.8.4;

contract SendContract {
    address payable public richest;
    uint public mostSent;
    error NotEnoughEther();
    constructor() payable {
        richest = payable(msg.sender);
        mostSent = msg.value;
    }

    function becomeRichest() public payable {
        if (msg.value <= mostSent) revert NotEnoughEther();
        // This line can cause problems (explained below).
        richest.transfer(msg.value);
        richest = payable(msg.sender);
        mostSent = msg.value;
    }
}

Enter fullscreen mode Exit fullscreen mode

Source : Solidity Docs – Withdrawal Pattern (Become The Richest Contract)

The following contract makes a direct transfer to the receiver, and this puts the entire burden of transaction on the sender. Which means, if the transaction fails, it would stop the functioning of our contract, and if any of the other users have their funds in this DApp, they would forever be locked.

Luckily for us, the fix is quite simple, our goal here is to not bear the responsibility of the transaction.

function becomeRichest() public payable {
        if (msg.value <= mostSent) revert NotEnoughEther();
        pendingWithdrawals[richest] += msg.value;
        richest = msg.sender;
        mostSent = msg.value;
    }

    function withdraw() public {
        uint amount = pendingWithdrawals[msg.sender];
        // Remember to zero the pending refund before
        // sending to prevent re-entrancy attacks
        pendingWithdrawals[msg.sender] = 0;
        payable(msg.sender).transfer(amount);
    }

Enter fullscreen mode Exit fullscreen mode

As can be seen above, the simplest fix is creating a function which allows the fund receiver to withdraw their amount from a mapping which holds the information of their withdrawal. Withdrawal Pattern provides a safe way to transfer ether so that it does not block the functioning of a smart contract in-case of a failure.

Avoiding Re-entrancy

Re-entrancy is a vulnerability that arises from the way we structure code in a smart contract. It works by stopping the execution of a payable function by trapping it inside a recursive call. It can be better understood using the example below.

mapping (address => uint) private userBalances;

function withdrawBalance() public {
    uint amountToWithdraw = userBalances[msg.sender];
    (bool success, ) = msg.sender.call.value(amountToWithdraw)(""); // At this point, the caller's code is executed, and can call withdrawBalance again
    require(success);
    userBalances[msg.sender] = 0;
}

Enter fullscreen mode Exit fullscreen mode

Source: Consensys.org – Reentrancy

The withdrawBalance function allows the users to withdraw their funds, however, it updates the user fund allocation after making the transfer. This means a user can drain this contract of all its funds by trapping eth transfer inside a recursive call. And this is done through a fallback function which calls the withdrawBalance method recursively.

Reentrancy Explained

This vulnerability can drain a contract of all its funds, and is a classic example of how smart contract programming is different from Web2 development and design. A simple fix would be to update the state variable before the transfer takes place, as in the example we’ve provided below.

function withdrawBalance() public {
    uint amountToWithdraw = userBalances[msg.sender];
    userBalances[msg.sender] = 0;
    require(amountToWithdraw > 0);
    msg.sender.call.value(amountToWithdraw)(""); // At this point, the caller's code is executed, and can call withdrawBalance again

}
Enter fullscreen mode Exit fullscreen mode

Locking Unlocked or Floating Pragma

Pragma is a compiler directive which tells EVM about the versions of Solidity our smart contract can execute on. Unlocking the pragma means the smart contract is open to execute on untested as well as experimental versions of solidity. And this can introduce fatal bugs to our application.

pragma solidity ^0.8.0;
// Should be written as
pragma solidity 0.8.25;
Enter fullscreen mode Exit fullscreen mode

The first line suggests that all the versions of the solidity compiler starting from version 8, can be used for executing this contract. This has been fixed in the third line, where the pragma is locked. At the time of writing this article, Solidity version eight remains the standard.

Unencrypted Data on-chain

A common misconception amongst beginner blockchain developers is that the contents of a private marked variable are hidden, however, that’s not the case. Private in the context of blockchains and smart contracts means that the variable is inaccessible to other contracts.

The data of all the variables of a contract is stored on the blockchain and can be viewed by anyone. Although data storage contracts exist, they’re not generally built for storing sensitive information. And the developers must ensure that any sensitive data stored on a blockchain is encrypted.

Preparing For Failure

Blockchain technology is experimental, and it is bound to fail. Even flawlessly written code is susceptible to vulnerabilities and underlying architecture problems – which surface up from time to time. In the world of Web2, such situations would usually be handled through patches or hot-fixes; but since a blockchain works differently, security needs to be implemented by design.

What this means is that we need to make our smart contracts tolerant of failures. And this is important because a blockchain is an immutable database, and a smart contract that has once been deployed, cannot be deleted. There are three aspects to failure control which a smart contract needs to follow to gracefully deal with such issues.

Pausing the contract – This works like a circuit breaker for when security has been breached. It freezes the functioning of the application and ensures no further exploitation takes place.

Rate limiting – As the name suggests, it means limiting the maximum usage of the different metrics in our app. This includes fund withdrawals, cooldown time, and successive withdrawals.

Upgrade path – Designing the smart contracts to be upgradable is an absolute must. This also means having an effective upgrade plan and a procedure for introducing improvements or bug-fixes.

Failure control protocols are built into all apps before they are rolled out for production, and it is a life saver in situations where there is a catastrophic failure or vulnerability in the system. The recent Binance bridge attack and the handling of the situation goes to show how important a failure control procedure can be.

On October 6th, the Binance bridge was drained for as much as $500 million. These funds were then moved across wallets on the smart chain network to disperse and make it hard to track. Binance prevented any further exploitation of their bridge, firstly by pausing their smart chain network, and then introducing new changes with the fork which solved the previous vulnerabilities.

Designing Apps on Layer–3 Blockchains

Layer–1 represents the first layer of blockchain and it manages all the essential tasks of the network. Because of the many operations that this tier has to manage, it faces issues regarding scalability. These problems are mitigated through Layer–2 scaling solutions such as state-channels, sidechains and plasma-networks.

Layer–2 networks work by forming an arrangement which decouples acknowledgement and settlement of data. This boosts the network speed and also introduces interoperability. But despite offering higher speeds Layer–2 networks have failed to gain mainstream popularity, as they do not inherit the liquidity of their parent chains. Good liquidity triumphs over gains in speed and apps continue to be developed on Layer–1 networks.

Over the years, this has created many problems for businesses. The absence of a mature ecosystem has made the development of blockchains apps complex and expensive. On one end, developers can reap the benefits of high liquidity, and on the other they can make use of high scalability and interoperability to offer smoother user experiences.

These problems are now solved by Layer–3 networks, which perform the task of connecting, pooling liquidity of Layer–1 & 2 networks. And in doing so, Layer–3 networks get rid of the redundancy that plague blockchain development. Layer–3 acts as an application layer and it simplifies the development of decentralized apps.

Layer–3 protocols like the Yellow Network bring the much needed reliability and security to Web3. By providing deep liquidity, high interoperability and scalability, Layer–3 creates an ideal working environment for DApps. Deep liquidity offers protections from price manipulations while the SDK provides a simpler interface for making apps interoperable.

The Web3 Security Stack

Security is a broad field and it requires knowledge of a diverse set of skills. Although there are many languages and tools to cover, you can get started using just a few of these. And depending on your goals and area of interest, your tech stack can have different variations of the one we have mentioned here.

Blockchain Programming Languages

Based on their use, blockchain programming languages can be divided into two categories. Application programming languages are used for writing smart contracts and developing decentralized apps. Whereas Low-Level languages are used for the development of blockchain networks.

Go – Imagine C, but with a modern syntax, memory safety, structured typing and garbage collection; well that’s exactly what Go is. Go was developed at Google as a “modern C” . It is used for building fast and reliable modern blockchain systems.

Go’s low level capabilities allow the developers to have fine-grain hardware control, while its modern syntax and features boost productivity and make long term maintenance of blockchain products easy.

Rust – Rust was developed by the Mozilla corporation to be the modern C++. It is a popular contender to Go for blockchain development as it offers similar features and is in use in major chains such as Solana and PolkaDot.

But unlike Go, Rust has controllable memory management. Developers can harness this feature to make extremely fast and efficient blockchain systems. Additionally, Rust is also in popular use as a smart contract programming language due to its easy to learn syntax.

Python – Unless you’re living under a rock, Python should be no stranger. It is a general purpose programming language with a simple syntax, which has been designed keeping code readability in mind.

It has tons of libraries and can be used to write smart contracts for blockchains like AlgoRand. It is also heavily used in smart contract vulnerability tools such as Mythril and Slither which we’ve discussed in the coming sections.

JavaScript – JavaScript is the native language of the internet, and it is here to keep that title. JavaScript offers first class support for blockchain development and has been a direct inspiration for languages like solidity.

It has a small learning curve and by far the best support for blockchain app development. Together with npm and the NodeJs ecosystem, JavaScript is a powerhouse of tools which you can use to easily bootstrap a blockchain app in seconds.

Solidity – When it comes to writing smart contracts, Solidity is arguably the most popular programming language. It is native to the Ethereum ecosystem and is supported by other major blockchains such as Avalanche and Binance.

It bears many similarities with the curly-bracket languages like Java and JavaScript and is fairly easy to grasp. Another thing to note, is that amongst all smart contract programming languages, Solidity has the best support and the most mature ecosystem of libraries and frameworks.

Vyper – Vyper is a relatively new pythonic language which has recently gained traction. It has an easy syntax and its compilation targets the EVM. Vyper was developed to make blockchain programming less complex, and to eliminate the bugs that arise because of the code structures in Solidity.

Motoko – Although less popular, Motoko is used for application development for the ICP blockchain. The Internet Computer is a blockchain that aims to reform the centralized internet. It is a decentralized cloud computing platform where motoko is used to “model” protocols and apps.

Yul – Yul is an Intermediate EVM based language which was designed to make optimisations on Solidity code. Its primary purpose is to improve the gas experience and produce more efficient target bytecode which executes on the EVM.

Security Analysis Tools

Security analysis tools are the swiss army knife in the world of infosec, and are of great help to security engineers and developers alike. Unlike programming languages, these tools have a smaller learning curve and are fairly easy to use.

Slither Developed by Crytic, it is the most popular free tool used for static analysis of solidity code. It’s written completely in python and features a vast array of vulnerability detectors. Slither also has its own API which the developers can use to write custom vulnerability analysers.

Mythril It is a part of the Mythx tool suite provided by Consensys for enterprises. But unlike Mythx, it is completely free of use. It supports symbolic analysis and SMT solving for smart contracts. Mythril works on bytecode and supports multiple EVM compatible blockchains.

Echidna Echidna is another famous tool developed by Crytic. It is written in Haskell and is used for Fuzz-testing Solidity contracts. It runs a series of tests against smart contracts to test their security in real-world scenarios. Echidna is modular in nature and can be extended with custom tests.

Manticore Manticore is strictly a symbolic execution tool which is used to explore all the possible states a smart contract can reach within the range of its capacity. It also records inputs which lead a contract to a given state in case of errors.

All these tools are a part of the Ethereum security toolbox, which presents them in a singular docker image. This makes it all the more convenient to set up and work with them collectively rather than installing them as separate packages.

Blockchain Development Frameworks

Outside the realm of Web3, blockchain systems have a ton of uses and have been in popular use by the industry for over a decade. Since developing a blockchain system from scratch takes time and is quite expensive, the industry has collaborated and come up with solutions for developing custom blockchains using pre-existing tools and frameworks.

Hyperledger Fabric – Hosted by the Linux Foundation, Hyperledger Fabric is a permissioned blockchain platform built for the enterprise. It has a modular plug-and-play architecture and is in use by companies such as Walmart and Sony. It offers a ton of pre-built consensus mechanisms and configurations, along with the ability to add custom features on the go.

Ethereum – Ethereum is the world’s first and arguably also the most popular blockchain development framework. It offers the most advanced set of features, and a Turing complete bytecode interpreter called the EVM. Ethereum forms the basis of modern blockchain networks today and has a thriving community.

EOSIO– EOSIA is a blockchain development framework that’s been developed to meet high demands. It is a developer-friendly and highly configurable platform which is used for developing high-throughput permissioned blockchain networks. EOSIA uses C++ for smart contracts and has great community support.

Web3 Security Roles

Security is about connecting the dots, protecting systems and creating dependable recovery plans. Compared to development, security roles are more serious, and given the current scarcity of talent in Web3, the demand is high. A lot of these roles have overlapping responsibilities and this makes transitioning from one role to another quite easy.

Smart Contract Auditor –As the name suggests, your job is to analyze smart contracts, detect vulnerabilities and bullet-proof the code before it goes for production. Additionally, some companies might also require you to mentor fellow engineers over the best security practices.

Blockchain Security Engineer –As a security engineer your job is to secure the entire end to end blockchain. This includes auditing smart contracts, securing blockchain infrastructure, creating custom security tools as well as conducting in-depth research on the latest attacks.

Security Lead – Similar to the security engineer role, your job is to secure the entire blockchain infrastructure. But this goes well beyond that, as you are also required to create custom infosec plans, and make important decisions related to the security of the business and its clients.

Resources To Learn And Practice Web3 Security

Creating a portfolio for security roles is slightly tricky as this area requires a lot of experience, and is usually not very beginner friendly. Luckily for us, there are plenty of resources online where we can practice and testify our skills for free. Moreover, a lot of these platforms offer bug-bounty programs to incentivise learning and boost participation

Damn Vulnerable DeFi – It is a hacking war-game where users/participants go through a series of smart contracts (equivalent of levels) with the purpose to either stop their functioning, drain them of funds or cause other anomalies.

Ethernaut – Similar to Damn Vulnerable DeFi, Ethernaut is a hacking wargame inspired by overthewire. It also has a historical catalog of some of the biggest attacks which appear as levels.

Immunefy – Backed by Chainlink and Sushiswap, Immunefy is a Web3 bug bounty program founded in 2020. It offers some of the biggest bounties from major Web3 orgs that are updated frequently.

HackenProof – Hackenproof is Hacken’s bug hunting program. Hacken is a leading name in Web3 security, and just like Immunefy, Hackenproof offers huge bounties and is associated with big orgs like FTX and CoinGecko.

If you prefer a structured way of learning then there are plenty of DAOs and Web3 cohorts which offer tutored sessions. They also have an active community which will help you along the way at every step.

Buildspace – Buildspace is perhaps one of the most famous DAOs. Whether you’re experienced, just starting out or have cool ideas to work on, Buildspace is the perfect place to start with Web3 as quickly as possible.

Learn Web3 – With an active community of over 50k members (and growing), Learn Web3 is the fastest growing DAO. It offers curated learning paths and sponsored tracks to help accelerate your learning.

Closing Thoughts

Security is of paramount importance to every business and in order to become truly disruptive and viable, Web3 needs to become more mature. Ultimately, the success of Web3 is tied to how secure it is. And just like the internet, our requirements have to evolve and adapt to this new decentralized and transparent web.

Since Web3 is extremely fast paced, to keep ourselves secure we need to stay ahead of the curve. As a security professional, you need an abstract understanding as well as nuts-and-bolts knowledge of the technology at hand. And while there’s no secret sauce to achieving complete security, an appetite for learning definitely helps.

Top comments (0)