Understanding Access Control Issues in Web2 and Web3

Introduction

Access control can be defined as a data security process that regulates who or what can view or use resources in a computing environment. It is a fundamental concept in security that is crucial for protecting sensitive data, preventing unauthorized access, and ensuring the integrity of applications.

Web 2, commonly called the traditional web, and Web 3, which encompasses blockchain-based decentralized applications (dApps), face access control challenges. In Web 2, access control issues often stem from flawed implementations of authentication and authorization mechanisms. In Web 3, the decentralized nature of blockchain systems and smart contracts introduces new complexities and challenges in managing access control.

Access control mainly involves two key components:

  • Authentication: Verifying the identity of a user through various methods such as passwords, tokens, or biometrics.

  • Authorization: Determining what actions or resources the authenticated user is allowed to access.

Access control ensures that users can only interact with the parts of the system they are permitted to, preventing unauthorized access and potential misuse.

Access Control: Web2 vs. Web3

Access Control in Web2

In Web2 applications, access control is typically implemented on the server side using frameworks and libraries that handle authentication and authorization logic. Common access control issues in Web2 include:

  • User Authentication involves verifying the user’s identity (e.g., via username/password, OAuth2, or JWT tokens).

  • Authorization enforcing rules to determine what actions the authenticated user can perform (e.g., role-based access control (RBAC) attribute-based access control (ABAC) or discretionary access control (DAC)).

Common Access Control Issues in Web2

Despite being a well-understood concept, access control in Web2 is often misconfigured, leading to vulnerabilities such as:

  1. Users can access resources or perform actions they shouldn’t be able to.

  2. Attackers manipulate object identifiers (e.g., primary keys) to access unauthorized data.

  3. A user with limited privileges gains elevated access (e.g., a regular user becomes an admin).

Example: Broken Access Control in Web2

Scenario: An e-commerce website allows users to view their orders via the endpoint /orders/{order_id}. However, the application fails to validate whether the order_id belongs to the currently authenticated user.

Vulnerable Code Example:

@app.route('/orders/<order_id>')
def get_order(order_id):
    try:
        order = db.get_order_by_id(order_id)
    except OrderNotFoundError:
        return jsonify({"error": "Order not found"}), 404
    return jsonify(order)

In this scenario, an attacker can enumerate order_id values to access other users’ orders, as the application lacks proper ownership checks.

Exploitation: An attacker can enumerate order_id values to access other users’ orders:

GET /orders/12345
GET /orders/12346

Remediation: Implement strict ownership checks to ensure users can only access their own orders:

@app.route('/orders/<order_id>')
def get_order(order_id):
    try:
        order = db.get_order_by_id(order_id)
        if order.user_id != current_user.id:
            return jsonify({"error": "Unauthorized"}), 403
    except OrderNotFoundError:
        return jsonify({"error": "Order not found"}), 404
    return jsonify(order)

By properly validating user ownership, the application can prevent unauthorized access to other users’ data.


Access Control in Web 3

In Web3, access control is often implemented through smart contracts. Unlike Web 2, where servers enforce access policies, Web3 decentralized applications (dApps) rely on blockchain-based mechanisms. The two main approaches are:

  1. Role-Based Access Control (RBAC): Specific addresses (wallets) are granted roles to perform privileged actions (e.g., admin, moderator).

  2. Permissionless vs. Permissioned Systems: While smart contracts are inherently permissionless, developers often implement custom permission systems to restrict access to sensitive functions.

Common Access Control Issues in Web3

  • Role Mismanagement: Misconfigurations allow unauthorized addresses to access admin-only functions.

  • Function Reentrancy: Exploiting access control by re-entering functions in an unintended manner (e.g., the infamous DAO hack).

  • Private Key Exploitation: Access control fails when attackers gain access to the private key of privileged addresses.

Example: No Access Control in Web3

Scenario: A smart contract allows anyone to call a function that updates critical settings, leading to unauthorized changes.

Vulnerable Smart Contract Code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Example {
    address public owner;
    mapping(address => uint256) public balances;

    constructor() {
        owner = msg.sender;
    }

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint256 amount) public {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        balances[msg.sender] -= amount;

        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
    }

    function changeOwner(address newOwner) public {
        owner = newOwner;
    }

    function emergencyWithdraw(address account) public {
        require(msg.sender == owner, "Only owner can withdraw");
        payable(account).transfer(address(this).balance);
    }
}

Exploit: An attacker can call the emergencyWithdraw function to drain the contract’s balance by changing the owner to their address as the changeOwner function has no access control checks to restrict who can change the owner. The attacker can then call the emergencyWithdraw function to withdraw the contract’s balance to their address.

A simple transaction sequence to exploit this vulnerability:

  1. Call changeOwner with the attacker’s address.

  2. Call emergencyWithdraw to drain the contract’s balance.

from web3 import Web3

web3 = Web3(Web3.HTTPProvider("<<RPC_URL>>"))

assert web3.is_connected(), "Failed to connect to the network"

contract_address = "0x1234..."

contract_abi = [
    ...
]

contract = web3.eth.contract(address=contract_address, abi=contract_abi)

private_key = "0x1234..."

attacker_address = web3.eth.account.from_key(private_key).address

transaction = contract.functions.changeOwner(attacker_address).buildTransaction({
    "nonce": web3.eth.get_transaction_count(account.address),
    "gas": 1000000,
    "gasPrice": web3.to_wei("1", "gwei"),
    "from": attacker_address,
    "chainId": 1337
})

signed_txn = web3.eth.account.sign_transaction(tx, private_key)

tx_hash = web3.eth.send_raw_transaction(signed_tx.raw_transaction)

receipt = web3.eth.wait_for_transaction_receipt(tx_hash)

transaction = contract.functions.emergencyWithdraw().buildTransaction({
    "nonce": web3.eth.get_transaction_count(account.address),
    "gas": 1000000,
    "gasPrice": web3.to_wei("1", "gwei"),
    "from": attacker_address,
    "chainId": 1337
})

signed_txn = web3.eth.account.sign_transaction(tx, private_key)

tx_hash = web3.eth.send_raw_transaction(signed_tx.raw_transaction)

receipt = web3.eth.wait_for_transaction_receipt(tx_hash)

Remediation: Implement access control checks to restrict sensitive functions to authorized addresses only. For example, the changeOwner function should be restricted to the contract owner:

Fixed Smart Contract Example:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Example {
    address public owner;
    mapping(address => uint256) public balances;

    constructor() {
        owner = msg.sender;
    }

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint256 amount) public {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        balances[msg.sender] -= amount;

        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
    }

    function changeOwner(address newOwner) public {
        // Only the current owner can change the owner
        require(msg.sender == owner, "Only owner can change owner");
        owner = newOwner;
    }

    // also, only the owner can withdraw the balance in their account
    function emergencyWithdraw() public {
        require(msg.sender == owner, "Only owner can withdraw");
        payable(owner).transfer(address(this).balance);
    }
}

Access Vectors for Access Control Failures

Common Attack Vectors: Web2

  • Attackers guess or enumerate resource identifiers to access unauthorized data.

  • Users gain elevated access by exploiting misconfigured access controls.

  • Manipulating object identifiers to access unauthorized resources.

  • Bypassing authentication mechanisms to impersonate other users.

Common Attack Vectors: Web3

  • Exploiting smart contract vulnerabilities to gain unauthorized access.

  • Stealing private keys to impersonate privileged addresses.

  • Performing unauthorized transactions or contract manipulations.

  • Exploiting permissionless systems to gain unauthorized access.

  • Re-entering functions to manipulate access control logic.

Conclusion

Access control issues are a significant threat in both Web2 and Web3 systems. In Web2, vulnerabilities often arise from misconfigured authentication and authorization mechanisms, while Web3 introduces new challenges due to the transparency and decentralization of blockchain systems.