Security·8 min का पठन·Solingo द्वारा

Access Control की गलतियाँ — onlyOwner से आगे

Common authorization vulnerabilities जो production contracts में exploit होती हैं। Real cases और fixes।

# Access Control की गलतियाँ — onlyOwner से आगे

Access control bugs सबसे common और expensive vulnerabilities में से हैं। Simple onlyOwner काफी नहीं है — आइए real-world mistakes और solutions देखें।

Mistake #1: Missing Access Control

Vulnerable Code

contract Vault {

mapping(address => uint) public balances;

function withdraw(address to, uint amount) public {

// 🚨 कोई भी call कर सकता है!

require(balances[to] >= amount);

balances[to] -= amount;

payable(to).transfer(amount);

}

}

Real case: $31M Parity Wallet hack (2017) — anyone could become owner।

Fix

function withdraw(address to, uint amount) public {

require(msg.sender == to, "Not authorized");

require(balances[to] >= amount);

balances[to] -= amount;

payable(to).transfer(amount);

}

Mistake #2: tx.origin Instead of msg.sender

Vulnerable Code

contract Wallet {

address public owner;

function transfer(address to, uint amount) public {

require(tx.origin == owner); // 🚨 WRONG!

payable(to).transfer(amount);

}

}

The Attack

contract Attacker {

function attack(Wallet wallet) external {

// Owner को trick करके यह call करवाओ

wallet.transfer(address(this), wallet.balance);

// tx.origin = owner, लेकिन msg.sender = Attacker

}

}

Owner accidentally attacker contract call कर देता है → funds stolen।

Fix

require(msg.sender == owner);  // ✅ CORRECT

Rule: NEVER use tx.origin for authorization।

Mistake #3: Unprotected Initializers

Vulnerable Code

contract Proxy {

address public implementation;

address public admin;

bool public initialized;

function initialize(address _admin) public {

// 🚨 कोई भी पहली बार call कर सकता है!

require(!initialized);

admin = _admin;

initialized = true;

}

}

Real case: Wormhole hack ($325M) — uninitialized implementation।

Fix: OpenZeppelin Initializable

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract Proxy is Initializable {

address public admin;

function initialize(address _admin) public initializer {

admin = _admin;

}

constructor() {

_disableInitializers(); // Implementation को lock करें

}

}

Mistake #4: Default Visibility = Public

Vulnerable Code

contract Token {

mapping(address => uint) balances;

function mint(address to, uint amount) { // 🚨 default = public!

balances[to] += amount;

}

}

Impact: कोई भी unlimited tokens mint कर सकता है।

Fix

function mint(address to, uint amount) external onlyOwner {

balances[to] += amount;

}

Best practice: हमेशा explicitly visibility declare करें।

Mistake #5: Inadequate Role Separation

Vulnerable Code

contract Protocol {

address public owner;

function pause() external {

require(msg.sender == owner);

paused = true;

}

function emergencyWithdraw() external {

require(msg.sender == owner);

// सारे funds निकाल लो

}

function setFees(uint newFee) external {

require(msg.sender == owner);

fees = newFee;

}

}

Problem: Owner compromised होने पर complete control loss।

Fix: Role-Based Access Control

import "@openzeppelin/contracts/access/AccessControl.sol";

contract Protocol is AccessControl {

bytes32 public constant PAUSER_ROLE = keccak256("PAUSER");

bytes32 public constant TREASURER_ROLE = keccak256("TREASURER");

bytes32 public constant GOVERNOR_ROLE = keccak256("GOVERNOR");

function pause() external onlyRole(PAUSER_ROLE) {

paused = true;

}

function emergencyWithdraw() external onlyRole(TREASURER_ROLE) {

// withdrawal logic

}

function setFees(uint newFee) external onlyRole(GOVERNOR_ROLE) {

fees = newFee;

}

}

Benefits:

  • Granular permissions
  • Key compromise = limited damage
  • Team collaboration easier

Mistake #6: Front-Running Admin Functions

Vulnerable Code

contract DEX {

uint public feeRate = 30; // 0.3%

function setFeeRate(uint newRate) external onlyOwner {

feeRate = newRate;

}

function swap(uint amountIn) external {

uint fee = (amountIn * feeRate) / 10000;

// swap logic

}

}

The Attack

  • Attacker mempool में setFeeRate(9999) देखता है
  • Attacker अपना large swap पहले execute करता है
  • Admin transaction execute होता है
  • Attacker huge fees pay करता है
  • Fix: Timelock

    import "@openzeppelin/contracts/governance/TimelockController.sol";
    
    

    contract DEX {

    TimelockController public timelock;

    function setFeeRate(uint newRate) external {

    require(msg.sender == address(timelock));

    feeRate = newRate;

    }

    }

    // Deployment

    TimelockController timelock = new TimelockController(

    2 days, // minimum delay

    [owner],

    [owner]

    );

    Users को 2 days का warning मिलता है — exit कर सकते हैं।

    Mistake #7: Delegate Call to Untrusted Contracts

    Vulnerable Code

    contract Proxy {
    

    function execute(address target, bytes calldata data) external {

    // 🚨 कोई भी contract delegate call कर सकता है!

    (bool success,) = target.delegatecall(data);

    require(success);

    }

    }

    The Attack

    contract Attacker {
    

    function attack() external {

    // Proxy के storage में owner change कर दो

    assembly {

    sstore(0, caller()) // slot 0 = owner

    }

    }

    }

    // Attacker calls:

    proxy.execute(attackerContract, abi.encodeCall(Attacker.attack, ()));

    Fix

    mapping(address => bool) public trustedImplementations;
    
    

    function execute(address target, bytes calldata data) external onlyOwner {

    require(trustedImplementations[target], "Untrusted");

    (bool success,) = target.delegatecall(data);

    require(success);

    }

    Mistake #8: Ownership Transfer Without Acceptance

    Vulnerable Code

    contract Owned {
    

    address public owner;

    function transferOwnership(address newOwner) external {

    require(msg.sender == owner);

    owner = newOwner; // 🚨 typo = permanent loss!

    }

    }

    Risk: newOwner typo है या contract है जो ownership accept नहीं कर सकता।

    Fix: Two-Step Transfer

    contract Owned {
    

    address public owner;

    address public pendingOwner;

    function transferOwnership(address newOwner) external {

    require(msg.sender == owner);

    pendingOwner = newOwner;

    }

    function acceptOwnership() external {

    require(msg.sender == pendingOwner);

    owner = pendingOwner;

    pendingOwner = address(0);

    }

    }

    OpenZeppelin का Ownable2Step exactly यही implement करता है।

    Testing Checklist

    // Foundry tests
    

    function testUnauthorizedAccess() public {

    vm.prank(attacker);

    vm.expectRevert("Not authorized");

    contract.privilegedFunction();

    }

    function testRoleBasedAccess() public {

    vm.prank(admin);

    contract.grantRole(MINTER_ROLE, minter);

    vm.prank(minter);

    contract.mint(user, 100); // Should succeed

    vm.prank(attacker);

    vm.expectRevert();

    contract.mint(attacker, 100); // Should fail

    }

    निष्कर्ष

    Access control mistakes preventable हैं:

  • Always explicit visibility declare करें
  • Never tx.origin use करें
  • Use OpenZeppelin's battle-tested libraries
  • Implement role-based access control
  • Add timelocks for critical functions
  • Test unauthorized access scenarios
  • Audit by professionals
  • एक छोटी mistake = millions lost। Security में कोई shortcut नहीं है।

    Practice में लगाने के लिए तैयार हैं?

    Solingo पर interactive exercises के साथ इन concepts को apply करें।

    मुफ्त में शुरू करें