# 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
setFeeRate(9999) देखता है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 हैं:
tx.origin use करेंएक छोटी mistake = millions lost। Security में कोई shortcut नहीं है।