iT邦幫忙

0

Day 14:存取控制錯誤(身份驗證與授權)

  • 分享至 

  • xImage
  •  

到目前為止,我們學了多種技術漏洞(像重入、溢位等),今天要談的是「最常被忽略」但最危險的一種——權限錯誤(Access Control Bug)。
這類錯誤不是因為數學錯,而是因為誰可以做什麼事沒有定義好。結果往往是一行程式,毀了一整個專案。

🔹 基本概念:身份驗證(Authentication)與授權(Authorization)
• 身份驗證:你是誰?(確認發送交易的人身份)
• 授權:你能做什麼?(確認這個人有沒有權限執行該操作)

在智能合約中,這兩者通常透過**地址(address)與函數修飾器(modifier)**控制。
如果邏輯沒寫對,就會變成「誰都能改 owner」、「誰都能提錢」的災難。

🔹 常見錯誤類型
類型 說明 範例
❌ 缺少權限修飾器 忘記onlyOwner或onlyAdmin function withdraw() public {
msg.sender.transfer(address(this).balance); }
⚠️ 使用 tx.origin 判斷身份 可被釣魚合約欺騙 require(tx.origin == owner);
❌ 多人共享權限混亂 管理多重角色時未分層 Admin / Owner 權限重疊或錯誤分配
⚠️ 沒有初始化 owner 部署時忘記設定 owner,導致任何人可取得控制權 owner 未在 constructor 初始化

🔹 tx.origin vs msg.sender 差異
屬性 說明
msg.sender 目前這筆呼叫的直接來源(可能是使用者或另一個合約)
tx.origin 交易最初發起者(永遠是人類使用者帳戶)

contract A {
address public owner;
constructor() { owner = msg.sender; }

function secureWithdraw() public {
    require(msg.sender == owner, "Not owner"); // ✅ 安全
}

function insecureWithdraw() public {
    require(tx.origin == owner, "Not owner"); // ❌ 可被釣魚攻擊
}

}

contract Attacker {
A victim;
constructor(address _victim) { victim = A(_victim); }

function attack() public {
    victim.insecureWithdraw(); // tx.origin 為使用者,但 msg.sender 為 attacker
}

}

解釋:
駭客建立惡意合約誘導使用者互動(例如簽送 U、mint NFT),在背後呼叫 victim 的 insecureWithdraw()。
由於 tx.origin 是使用者,檢查會通過,造成資金外流。

🔹 權限控制正確寫法

Solidity 提供 modifier 讓我們簡化權限檢查邏輯:

pragma solidity ^0.8.0;

contract SecureVault {
address public owner;

constructor() {
    owner = msg.sender;
}

modifier onlyOwner() {
    require(msg.sender == owner, "Not owner");
    _;
}

function withdraw(uint amount) public onlyOwner {
    payable(owner).transfer(amount);
}

}

🔹 進階多角色權限模型(OpenZeppelin)

實務上常使用 OpenZeppelin 的 AccessControl 進行角色分層:

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

contract MultiRole is AccessControl {
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

constructor() {
    _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}

function mint(address to) public onlyRole(MINTER_ROLE) {
    // mint token
}

}

這樣可以:
• 明確區分角色權限
• 支援多重管理者
• 更容易進行審計與升級

🔹 常見檢查清單(Audit Checklist)

✅ 是否所有敏感操作都有權限保護?
✅ 是否只使用 msg.sender 進行身份驗證?
✅ 部署時是否初始化了所有關鍵變數(例如 owner)?
✅ 是否使用 OpenZeppelin 等成熟模組?
✅ 是否考慮權限轉移、撤銷等場景?

這類權限錯誤往往是最「無辜卻最致命」的漏洞。
開發者以為只是少一行 onlyOwner,結果可能導致整個資金池被提光。
我覺得在寫智能合約的過程中,應該養成一個「零信任」思維:

永遠假設沒人值得信任,連自己都不例外。

權限邏輯應該明確、最小化、模組化。當安全和方便衝突時,優先選擇安全。


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言