Skip to main content

Chương 6: Access Control — Kiểm soát truy cập

Khái niệm

Access Control (Kiểm soát truy cập) là cơ chế quyết định ai được làm gì trong hệ thống. Đây là lỗ hổng số 1 trong OWASP Top 10 2021 (A01: Broken Access Control).

Mức độ nguy hiểm: Rất cao — Broken access control cho phép hacker truy cập dữ liệu/chức năng không thuộc về họ.

Ba thành phần liên kết:

  • Authentication: Bạn là ai?
  • Session Management: Theo dõi bạn qua các requests
  • Access Control: Bạn được phép làm gì?

Phân loại Access Control

Vertical Access Control (Phân quyền dọc)

Giới hạn chức năng theo role (vai trò):

Admin → tất cả chức năng
Manager → quản lý user, xem reports
User → chỉ profile của mình
Guest → xem public content

Lỗ hổng: Vertical Privilege Escalation — user thường truy cập được chức năng admin.

Horizontal Access Control (Phân quyền ngang)

Giới hạn truy cập vào dữ liệu theo owner:

User A → chỉ xem account của User A
User B → chỉ xem account của User B

Lỗ hổng: Horizontal Privilege Escalation (IDOR) — User A xem được dữ liệu của User B.

Context-Dependent Access Control

Giới hạn theo trạng thái/ngữ cảnh:

Không cho phép sửa cart sau khi đã thanh toán
Không cho phép hủy order sau khi đã ship

IDOR — Insecure Direct Object Reference

IDOR (Insecure Direct Object Reference — Tham chiếu Đối tượng trực tiếp không an toàn) là lỗ hổng phổ biến nhất trong access control.

Xảy ra khi app dùng user-supplied input để truy cập object trực tiếp mà không kiểm tra quyền.

Ví dụ IDOR cơ bản

GET /api/users/profile?userId=1337 HTTP/1.1
Authorization: Bearer <token_of_user_1337>

Response: {"id": 1337, "name": "Alice", "email": "alice@example.com"}

Hacker thay đổi userId:

GET /api/users/profile?userId=1 HTTP/1.1
Authorization: Bearer <token_of_user_1337>

Response: {"id": 1, "name": "Admin", "email": "admin@example.com"}

Không có kiểm tra "user 1337 có quyền xem profile của user 1 không?" → IDOR.

IDOR trong các dạng khác

IDOR trong path:

GET /api/orders/ORD-001234
→ thử: GET /api/orders/ORD-000001

IDOR trong POST body:

POST /api/update-profile
{"userId": 1337, "email": "new@email.com"}
→ thay: {"userId": 1, "email": "hacked@email.com"}

IDOR trong file download:

GET /download?file=invoice_1337.pdf
→ thử: GET /download?file=invoice_1.pdf

IDOR qua Indirect Reference:

GET /api/messages?thread=abc123
→ Nếu thread ID predictable hoặc enumerable

IDOR với UUID — Không phải silver bullet

UUID: 550e8400-e29b-41d4-a716-446655440000
→ Random, không đoán được
→ Nhưng nếu server vẫn không check ownership → vẫn IDOR!

Test: Chia sẻ UUID của resource của bạn với account khác
→ Nếu account khác truy cập được → IDOR dù dùng UUID

Vertical Privilege Escalation

Unprotected Admin Functions

Admin panel tại: /admin
→ Không có authentication check
→ Chỉ "ẩn" khỏi navigation menu
→ Hacker biết URL → access ngay

Tìm hidden endpoints:

# Dùng feroxbuster/ffuf để brute force paths
ffuf -w /usr/share/wordlists/dirb/common.txt \
-u https://example.com/FUZZ \
-mc 200,301,302,403

# Xem robots.txt - thường tiết lộ admin paths
curl https://example.com/robots.txt

# Xem JavaScript source - thường chứa API endpoints
grep -r "admin\|dashboard\|internal" static/js/

Parameter Tampering

GET /account?role=user HTTP/1.1

→ Thử: GET /account?role=admin HTTP/1.1
POST /login HTTP/1.1
{"username": "alice", "password": "pass123"}

Response: {"userId": 1337, "role": "user", "token": "..."}

→ Nếu app tin vào role trong response mà không verify server-side
→ Sửa role=admin trong subsequent requests

Platform Misconfiguration

Một số framework cho phép restrict theo HTTP method:

// Sai: restrict GET nhưng không restrict POST
@GetMapping("/admin/users")
@PreAuthorize("hasRole('ADMIN')")
public List<User> getUsers() { ... }

// Admin function cũng accessible qua:
POST /admin/users ← không có annotation → không bị restrict!

URL path override:

GET /admin/users HTTP/1.1
→ 403 Forbidden

GET /users HTTP/1.1
X-Original-URL: /admin/users ← một số framework override path

→ 200 OK → access bypassed!

Broken Access Control qua HTTP Method Override

POST /api/delete-user HTTP/1.1
X-HTTP-Method-Override: DELETE

→ Một số servers map POST + X-HTTP-Method-Override → DELETE
→ Bypass firewall rules chỉ block DELETE

Kịch bản tấn công: IDOR Mass Account Takeover

Target: e-commerce site với 100,000 accounts

1. Login với account hacker (ID: 50000)
2. Request profile: GET /api/profile?id=50000
3. Thay id: GET /api/profile?id=50001 → thành công!
4. Viết script:

for id in range(1, 100001):
response = requests.get(f"/api/profile?id={id}",
cookies={"session": hacker_session})
save_profile_data(response.json())

5. Dump toàn bộ 100,000 profiles bao gồm email, phone, address
6. Bán data breach hoặc dùng để phishing

Cách phát hiện

□ Thay đổi ID trong URL/parameters → truy cập được resource người khác?
□ Xóa/thay đổi Authorization header → vẫn có response?
□ Thay đổi role/admin parameter → có thay đổi quyền không?
□ Access admin URL trực tiếp khi logged in với user thường
□ Thử methods khác (POST/PUT/DELETE) trên endpoint chỉ allow GET
□ Thêm X-Original-URL, X-Rewrite-URL, X-Forwarded-For headers
□ Thử path với trailing slash, uppercase, encoded chars
□ Robots.txt, sitemap.xml có tiết lộ hidden paths không?
□ JavaScript source có chứa admin endpoints không?

Tool hữu ích:

  • Autorize (Burp extension): Tự động replay mọi request với session của user khác để detect IDOR
  • 403 Bypasser (Burp extension): Tự động thử bypass 403

Phòng chống

1. Server-Side Authorization Check

# SAI: tin vào user-supplied ID
@app.route('/api/profile')
def get_profile():
user_id = request.args.get('userId') # Từ client
return db.get_user(user_id)

# ĐÚNG: dùng ID từ session (server-controlled)
@app.route('/api/profile')
@login_required
def get_profile():
user_id = g.current_user.id # Từ server-side session
return db.get_user(user_id)

# ĐÚNG: nếu cần user specify ID, luôn check ownership
@app.route('/api/orders/<int:order_id>')
@login_required
def get_order(order_id):
order = db.get_order(order_id)
if order.user_id != g.current_user.id:
abort(403) # Forbidden
return order

2. Deny by Default

# Middleware check access cho mọi request
def check_access(resource_type, resource_id, action):
user = g.current_user

# Default: deny
if not has_permission(user, resource_type, resource_id, action):
abort(403)

# Áp dụng cho tất cả routes
@app.before_request
def enforce_access_control():
if is_protected_endpoint(request.endpoint):
resource = get_resource_from_request()
check_access(resource.type, resource.id, request.method)

3. RBAC (Role-Based Access Control)

PERMISSIONS = {
'admin': ['users:read', 'users:write', 'orders:read', 'orders:write'],
'manager': ['orders:read', 'orders:write'],
'user': ['profile:read', 'profile:write', 'orders:read'],
}

def has_permission(user, permission):
return permission in PERMISSIONS.get(user.role, [])

@app.route('/api/users')
@require_permission('users:read')
def list_users():
return db.get_all_users()

4. Indirect Reference Maps

Thay vì expose database ID trực tiếp, dùng một mapping:

# Mỗi user có mapping riêng của họ
user_resource_map = {
'order_1': 'db_order_id_1337',
'order_2': 'db_order_id_1338',
}

# User chỉ thấy reference key của riêng họ
GET /api/orders/order_1 → mapped to actual order 1337 của user đó

Góc nhìn DevOps

Kubernetes RBAC — không liên quan đến Web RBAC nhưng tư duy tương tự:

# Least privilege: chỉ đọc pods trong namespace cụ thể
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
# Không có "create", "delete", "update"

API Gateway — Enforce access control ở gateway level:

# Kong Gateway: restrict endpoint theo role
plugins:
- name: acl
config:
allow:
- admin-group
hide_groups_header: true

Audit Logging — Ghi lại ai truy cập gì:

# Mọi access vào sensitive resource đều cần log
def audit_log(user, action, resource, status):
logger.info({
"timestamp": datetime.utcnow().isoformat(),
"user_id": user.id,
"username": user.email,
"action": action,
"resource": resource,
"status": status,
"ip": request.remote_addr,
"user_agent": request.user_agent.string
})

# Alert khi có access pattern bất thường
# VD: 1 user request 1000 profiles trong 60 giây → IDOR attempt

CI/CD Security Gate:

# GitHub Actions: chạy DAST test để detect IDOR
- name: Run IDOR Tests
run: |
# Test với hai accounts khác nhau
python tests/security/test_access_control.py \
--user1-token $USER1_TOKEN \
--user2-token $USER2_TOKEN

Tóm tắt

  • Broken Access Control là lỗ hổng #1 OWASP — phổ biến và nguy hiểm.
  • IDOR: user truy cập resource của user khác bằng cách thay ID — kiểm tra ownership mọi lúc.
  • Vertical escalation: user thường truy cập chức năng admin.
  • Luôn implement access control ở server-side, không tin client.
  • Deny by default: nếu không có permission explicit → từ chối.
  • Dùng RBAC để quản lý quyền có hệ thống.
  • Audit log mọi access vào sensitive resources.
  • Dùng Autorize extension để test access control tự động.

Câu hỏi ôn tập

  1. IDOR là gì và tại sao dùng UUID thay vì integer ID không tự động phòng chống IDOR?
  2. Sự khác biệt giữa horizontal và vertical privilege escalation là gì?
  3. Tại sao "deny by default" là nguyên tắc quan trọng hơn "allow by default"?
  4. Làm thế nào để dùng Burp Suite Autorize để test access control tự động?
  5. Mô tả cách implement RBAC đơn giản trong một REST API.