OWASP Top 10 - 存取控制失效

OWASP Top 10 - Broken Access Control

什麼是存取控制失效?

存取控制失效(Broken Access Control)是 OWASP Top 10 2021 排名第一的網路安全風險。當應用程式未能正確限制使用者對資源的存取權限時,攻擊者可以存取未經授權的功能或資料。

存取控制負責確保使用者只能在其權限範圍內執行操作。當這些控制機制失效時,可能導致:

  • 未經授權存取敏感資料
  • 修改或刪除其他使用者的資料
  • 執行超出權限的功能
  • 權限提升至管理員等級

常見漏洞類型

1. IDOR(不安全的直接物件參照)

IDOR(Insecure Direct Object Reference)是最常見的存取控制漏洞之一。當應用程式直接使用使用者提供的輸入來存取物件時,攻擊者可以透過修改參數來存取其他使用者的資料。

漏洞範例:

1
2
3
4
5
# 原始請求 - 查看自己的訂單
GET /api/orders/12345

# 攻擊者修改訂單 ID
GET /api/orders/12346

易受攻擊的程式碼:

1
2
3
4
5
@app.route('/api/orders/<order_id>')
def get_order(order_id):
    # 危險:未驗證使用者是否有權限存取此訂單
    order = Order.query.get(order_id)
    return jsonify(order.to_dict())

修復後的程式碼:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@app.route('/api/orders/<order_id>')
@login_required
def get_order(order_id):
    order = Order.query.get(order_id)

    # 驗證訂單是否屬於當前使用者
    if order.user_id != current_user.id:
        abort(403)  # 禁止存取

    return jsonify(order.to_dict())

2. 權限提升漏洞

權限提升分為兩種類型:

垂直權限提升: 一般使用者獲得管理員權限 水平權限提升: 使用者存取其他同級使用者的資料

漏洞範例 - 隱藏的管理功能:

1
2
<!-- 前端隱藏管理連結,但後端未驗證權限 -->
<a href="/admin/users" style="display:none;">管理使用者</a>

易受攻擊的程式碼:

1
2
3
4
5
6
7
// Node.js Express 範例
app.post('/admin/users/delete', (req, res) => {
    // 危險:僅依賴前端隱藏,未驗證後端權限
    const userId = req.body.userId;
    User.deleteById(userId);
    res.json({ success: true });
});

修復後的程式碼:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 加入角色驗證中介軟體
const requireAdmin = (req, res, next) => {
    if (!req.user || req.user.role !== 'admin') {
        return res.status(403).json({ error: '權限不足' });
    }
    next();
};

app.post('/admin/users/delete', requireAdmin, (req, res) => {
    const userId = req.body.userId;
    User.deleteById(userId);
    res.json({ success: true });
});

3. 路徑穿越攻擊

路徑穿越(Path Traversal)允許攻擊者存取應用程式目錄外的檔案。

攻擊範例:

1
2
3
4
5
# 原始請求
GET /download?file=report.pdf

# 攻擊請求
GET /download?file=../../../etc/passwd

易受攻擊的程式碼:

1
2
3
4
5
@app.route('/download')
def download_file():
    filename = request.args.get('file')
    # 危險:直接使用使用者輸入
    return send_file(f'/var/www/uploads/{filename}')

修復後的程式碼:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import os
from werkzeug.utils import secure_filename

@app.route('/download')
def download_file():
    filename = request.args.get('file')

    # 清理檔案名稱
    safe_filename = secure_filename(filename)

    # 建構完整路徑並驗證
    base_path = '/var/www/uploads'
    file_path = os.path.realpath(os.path.join(base_path, safe_filename))

    # 確保路徑在允許的目錄內
    if not file_path.startswith(base_path):
        abort(403)

    if not os.path.exists(file_path):
        abort(404)

    return send_file(file_path)

4. 缺少功能層級存取控制

當應用程式未對每個功能端點實施適當的權限檢查時,攻擊者可以直接存取受限功能。

易受攻擊的 API 設計:

1
2
3
4
5
6
# 危險:API 端點缺少權限驗證
@app.route('/api/admin/settings', methods=['POST'])
def update_settings():
    settings = request.json
    SystemSettings.update(settings)
    return jsonify({'status': 'updated'})

修復後的程式碼:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from functools import wraps

def require_permission(permission):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            if not current_user.has_permission(permission):
                return jsonify({'error': '權限不足'}), 403
            return f(*args, **kwargs)
        return decorated_function
    return decorator

@app.route('/api/admin/settings', methods=['POST'])
@login_required
@require_permission('admin:settings:write')
def update_settings():
    settings = request.json
    SystemSettings.update(settings)
    return jsonify({'status': 'updated'})

測試方法

手動測試步驟

  1. 識別存取控制點

    • 列出所有 API 端點和功能
    • 標記需要驗證的資源
  2. 測試 IDOR 漏洞

    • 修改 URL 中的 ID 參數
    • 嘗試存取其他使用者的資源
  3. 測試權限提升

    • 使用低權限帳號存取管理功能
    • 修改請求中的角色或權限參數
  4. 測試路徑穿越

    • 在檔案參數中注入 ../ 序列
    • 嘗試存取系統敏感檔案

自動化測試工具

1
2
3
4
5
6
7
8
# 使用 Burp Suite 進行自動化掃描
# 使用 OWASP ZAP 進行被動掃描

# 使用 ffuf 進行端點發現
ffuf -u https://target.com/FUZZ -w /path/to/wordlist.txt

# 使用 nuclei 進行漏洞掃描
nuclei -u https://target.com -t exposures/

防護措施

1. 實施最小權限原則

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Permission:
    READ = 'read'
    WRITE = 'write'
    DELETE = 'delete'
    ADMIN = 'admin'

class Role:
    GUEST = [Permission.READ]
    USER = [Permission.READ, Permission.WRITE]
    ADMIN = [Permission.READ, Permission.WRITE, Permission.DELETE, Permission.ADMIN]

2. 在伺服器端驗證所有請求

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def verify_resource_access(user, resource):
    """
    在每個請求中驗證使用者對資源的存取權限
    """
    # 檢查資源所有權
    if resource.owner_id != user.id:
        # 檢查是否有共享權限
        if not resource.is_shared_with(user):
            raise PermissionDenied("無權存取此資源")

    return True

3. 使用間接物件參照

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import hashlib
import secrets

def generate_secure_token(resource_id, user_id):
    """
    產生安全的資源存取令牌
    """
    secret = secrets.token_hex(32)
    token = hashlib.sha256(
        f"{resource_id}:{user_id}:{secret}".encode()
    ).hexdigest()

    # 將令牌與資源映射儲存在資料庫
    TokenMapping.create(token=token, resource_id=resource_id, user_id=user_id)

    return token

4. 記錄存取控制失敗事件

1
2
3
4
5
6
7
8
9
import logging

security_logger = logging.getLogger('security')

def log_access_denied(user, resource, action):
    security_logger.warning(
        f"存取被拒絕 - 使用者: {user.id}, 資源: {resource.id}, 動作: {action}, "
        f"IP: {request.remote_addr}, 時間: {datetime.now()}"
    )

安全檢查清單

  • 所有 API 端點都有適當的權限檢查
  • 敏感操作需要重新驗證身份
  • 使用 RBAC(角色基礎存取控制)或 ABAC(屬性基礎存取控制)
  • 記錄所有存取控制失敗事件
  • 定期審查存取控制策略
  • 實施速率限制防止暴力攻擊
  • 使用安全的隨機令牌而非可預測的 ID

參考資料

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy