Logo blog @ teamserver.xyz
Understanding Web3 Security Risks

Understanding Web3 Security Risks

April 3, 2025
5 min read
Table of Contents

Web3 Vulnerabilities

Web3 applications and smart contracts are prone to unique vulnerabilities due to their transparent, autonomous, and immutable nature. This blog highlights common Web3 vulnerabilities identified during security reviews, along with simple Solidity examples to help you understand each issue.


Code Review Vulnerabilities

1. Reentrancy

This occurs when an external contract repeatedly calls back into the vulnerable contract before the previous execution is completed. This can drain funds if the contract updates balances after sending Ether.

Reentrancy
pragma solidity ^0.8.0;
contract Reentrancy {
mapping(address => uint) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
function withdraw() external {
require(balances[msg.sender] > 0, "No balance");
(bool sent, ) = msg.sender.call{value: balances[msg.sender]}("");
require(sent, "Failed to send Ether");
balances[msg.sender] = 0;
}
}

2. Ownership Takeover

If an initialization function is left unprotected, anyone can call it and take ownership of the contract, potentially allowing them to modify critical settings or withdraw funds.

Ownership Takeover
contract Ownable {
address public owner;
function initialize() public {
owner = msg.sender;
}
}

3. Timestamp Dependence

Using block.timestamp for critical logic (such as randomness or time-based access) is risky because miners can slightly manipulate timestamps, leading to unfair advantages.

Timestamp Dependence
if (block.timestamp % 2 == 0) {
// Winner!
}

4. Gas Limit and Loops

Unbounded loops can exceed the gas limit, causing transactions to fail. Attackers can exploit this to make a function unusable (e.g., preventing fund withdrawals).

Gas Limit and Loops
address[] public users;
function rewardAll() public {
for (uint i = 0; i < users.length; i++) {
payable(users[i]).transfer(1 ether);
}
}

5. Denial of Service (DoS) with Throw

If a contract depends on external calls that can fail (e.g., sending Ether), a single failing transaction can block the execution of a loop, disrupting functionality for others.

Denial of Service (DoS) with Throw
function payUsers(address[] calldata users) public {
for (uint i = 0; i < users.length; i++) {
require(payable(users[i]).send(1 ether));
}
}

6. Transaction Ordering Dependence (TOD)

When contracts rely on transaction order, attackers can use front-running strategies (paying higher gas fees) to manipulate the outcome of auctions, trading, or other priority-based logic.

Transaction Ordering Dependence (TOD)
function bid() public payable {
require(msg.value > currentBid, "Low bid");
currentBidder = msg.sender;
currentBid = msg.value;
}

7. Unchecked External Call

Not checking the return value of call() can lead to security risks. If a call fails but isn’t handled properly, the contract might continue execution with unexpected behavior.

Unchecked External Call
address target = 0xAbC...;
(bool success, ) = target.call(abi.encodeWithSignature("foo()"));
// Not checking success

8. Unchecked Math (Pre-Solidity 0.8.x)

Older versions of Solidity did not include built-in overflow and underflow protection, leading to issues where uint values could wrap unexpectedly.

Unchecked Math (Pre-Solidity 0.8.x)
uint x = 0;
x -= 1; // wraps to 2^256 - 1

9. Unsafe Type Inference

Using var for variable declaration can result in unintended type inference, which might cause overflows or unintended behavior in mathematical operations.

Unsafe Type Inference
var x = 0; // inferred as int, not uint

10. Implicit Visibility Level

Solidity functions default to public if no visibility modifier is specified. This can unintentionally expose internal functions, allowing unauthorized access.

Implicit Visibility Level
function updateOwner() {
owner = msg.sender;
}

Functional Review Vulnerabilities

1. Escrow Manipulation

Weak escrow logic can allow users to withdraw funds multiple times or before they should be able to, leading to unauthorized fund losses.

Escrow Manipulation
mapping(address => uint) public deposits;
function withdraw() public {
require(deposits[msg.sender] > 0);
payable(msg.sender).transfer(deposits[msg.sender]);
}

2. Token Supply Manipulation

If minting logic lacks proper access controls, anyone could increase the total supply of tokens, leading to inflation and loss of value.

Escrow Manipulation
function mint(address to, uint amount) public {
balances[to] += amount;
totalSupply += amount;
}

3. Kill-Switch Mechanism

Contracts with emergency stop functions or selfdestruct() can lead to centralized control or unexpected shutdowns, potentially causing financial loss.

Escrow Manipulation
function emergencyStop() public {
require(msg.sender == owner);
selfdestruct(payable(owner));
}

4. User Balance Manipulation

Allowing arbitrary balance updates without validation can let attackers increase their balance or decrease others’, leading to theft or disruption.

Escrow Manipulation
function setBalance(address user, uint amount) public {
balances[user] = amount;
}

Conclusion

Web3 security is a crucial aspect of smart contract development. Unlike traditional applications, smart contracts operate on decentralized and immutable blockchain networks, making security vulnerabilities particularly costly and often irreversible. The vulnerabilities discussed in this guide demonstrate how small mistakes in Solidity coding can lead to severe financial losses, contract failures, and even complete project shutdowns.

Developers should prioritize secure coding practices by following industry best practices, including:

  • Using modern Solidity versions (0.8.x and above) to benefit from built-in security features like overflow protection.
  • Implementing security design patterns, such as checks-effects-interactions, access control mechanisms, and circuit breakers.
  • Conducting thorough audits by reputable security firms before deployment.
  • Performing rigorous testing using tools like Slither, Echidna, and Foundry for static analysis and fuzzing.
  • Applying best security practices from established resources like OWASP Smart Contract Top 10 to identify and mitigate risks proactively.

Security is an ongoing process, not a one-time fix. With the increasing complexity of decentralized applications (dApps) and DeFi protocols, new attack vectors constantly emerge. Staying updated with security trends, participating in bug bounty programs, and continuously reviewing code for vulnerabilities are essential for maintaining robust smart contracts.

By incorporating these security principles and leveraging insights from past security incidents, developers can build safer and more resilient Web3 applications. Remember: A secure contract is not just about protecting assets—it’s about maintaining user trust and ensuring the longevity of decentralized systems.

Stay safe & keep building securely!

References