Giải phẫu & Khai thác Lỗ hổng Ứng dụng
19/21
Bài 19 / 21~13 phútSSRF & Cloud PivotingMiễn phí… lượt xem

Lỗ hổng REST API: Khai thác BOLA và Mass Assignment

Phân tích hai lỗ hổng phổ biến nhất trong OWASP API Security: Broken Object Level Authorization (BOLA) và Mass Assignment (Auto-binding).

TL;DR: Trong bảng xếp hạng OWASP API Security Top 10, Broken Object Level Authorization (BOLA) luôn giữ vị trí nguy hiểm số 1, chiếm hơn 40% các sự cố rò rỉ dữ liệu qua giao diện lập trình ứng dụng. BOLA xuất hiện khi máy chủ xác thực thành công danh tính người dùng (Authentication) nhưng lại bỏ qua bước kiểm tra quyền sở hữu đối với ID tài nguyên được truyền trong URL. Đi cùng với BOLA là lỗ hổng Mass Assignment (Auto-binding), nơi các framework tự động ánh xạ toàn bộ trường dữ liệu JSON từ client vào đối tượng Entity nội bộ, cho phép người dùng tự thăng cấp quyền quản trị bằng cách gửi thêm một cặp key-value đơn giản.

1. Scenario — API cập nhật hồ sơ biến người dùng thành Admin

Tại một nền tảng tuyển dụng công nghệ, người dùng có thể chỉnh sửa thông tin cá nhân thông qua API:

HTTP
PUT /api/v1/users/profile HTTP/1.1
Host: api.recruit.internal
Authorization: Bearer <valid_user_jwt>
Content-Type: application/json

{
  "full_name": "Nguyen Van B",
  "bio": "Senior Backend Engineer",
  "phone": "0912345678"
}

Tại tầng backend (sử dụng Spring Boot hoặc Node.js TypeORM):

# Vulnerable controller auto-binding body directly to DB model
@app.route('/api/v1/users/profile', methods=['PUT'])
@jwt_required
def update_profile():
    current_user_id = get_jwt_identity()
    user_entity = User.query.get(current_user_id)
    
    # Bug: Directly applying all JSON fields from request to entity
    data = request.get_json()
    for key, value in data.items():
        setattr(user_entity, key, value)
        
    db.session.commit()
    return jsonify({"message": "Cap nhat thanh cong"})

Một ứng viên tò mò sử dụng Burp Suite để quan sát cấu trúc bảng người dùng thông qua endpoint xem thông tin tài khoản:

JSON
{
  "id": 105,
  "email": "[email protected]",
  "full_name": "Nguyen Van B",
  "is_admin": false,
  "role": "candidate",
  "credits": 0
}

Ứng viên này gửi lại request PUT /api/v1/users/profile, bổ sung thêm hai trường nhạy cảm:

JSON
{
  "full_name": "Nguyen Van B",
  "is_admin": true,
  "role": "system_admin",
  "credits": 99999
}

Hàm setattr() trong vòng lặp vô tư gán user_entity.is_admin = True và user_entity.role = 'system_admin'. Cơ sở dữ liệu ghi nhận và ứng viên lập tức trở thành Quản trị viên tối cao của hệ thống.

2. Analogy — Tủ gửi đồ bể bơi và đơn đăng ký thành viên

Hãy phân biệt hai khía cạnh bảo mật qua đời sống hàng ngày:

  • BOLA (Nhầm ngăn tủ đồ): Bạn mua vé vào bể bơi công cộng và được phát một chiếc chìa khóa tủ số 501. Khi vào phòng thay đồ, bạn thấy ổ khóa tủ số 502 bị lỏng. Bạn dùng chìa khóa số 501 của mình vặn thử vào ổ khóa 502. Khóa mở toang và bạn xem được toàn bộ tư trang của người khác. Nhân viên bảo vệ đứng ngoài cửa chỉ kiểm tra xem bạn có mua vé vào cửa không (Authentication hợp lệ), chứ không kiểm tra xem bạn đang mở đúng chiếc tủ thuộc về bạn hay không (Authorization thất bại).
  • Mass Assignment (Tự phong chức trên biểu mẫu): Câu lạc bộ phát cho bạn tờ khai gia nhập chỉ gồm hai dòng: Họ tên và Số điện thoại. Phía dưới có một ô để trống ghi chữ Dành riêng cho Ban Giám đốc: [Chức vụ: Thành viên]. Bạn lấy bút tự gạch chữ Thành viên đi và ghi đè vào: [Chức vụ: Chủ tịch Hội đồng Quản trị]. Cô thư ký nhận đơn, không thèm đọc lại mà nhập thẳng tờ khai vào máy tính. Hệ thống tự động cấp cho bạn quyền của Chủ tịch câu lạc bộ.
Đời sống thực tếCơ chế trong REST API
Mua vé vào bể bơi hợp lệĐăng nhập thành công, sở hữu Bearer JWT token
Mở trộm tủ đồ số 502 bằng chìa 501Thay đổi id=502 trong URL API (Lỗ hổng BOLA)
Tự điền ô dành riêng cho Ban Giám đốcGửi thêm trường is_admin: true trong JSON body
Thư ký nhập thẳng vào hệ thốngFramework tự động bind dữ liệu vào Database Entity (Mass Assignment)

3. Vì sao BOLA là mối đe dọa số 1 của thế giới API?

BOLA không phải là lỗi cú pháp hay lỗi tràn bộ nhớ; nó là một lỗi logic nghiệp vụ (Business Logic Flaw) thuần túy.

Trong bài học trước về mô hình kiểm soát truy cập, bạn đã nghiên cứu:

Nhớ lại —

Ôn lại sự khác biệt giữa kiểm soát truy cập theo vai trò (RBAC) và kiểm soát truy cập mức đối tượng (Object-Level Authorization).

Trong các ứng dụng API hướng microservices, việc phân quyền thường được giao khoán cho API Gateway hoặc middleware kiểm tra Token. Middleware chỉ có khả năng kiểm tra:

  1. Token có hợp lệ và còn hạn không?
  2. Người dùng có vai trò gì (Role: user)?

Tuy nhiên, middleware hoàn toàn không biết tài nguyên GET /api/v1/orders/89201 có thuộc về người dùng đang gọi hay không. Quyền sở hữu này chỉ có thể được kiểm tra khi câu truy vấn chạm tới cơ sở dữ liệu ở tầng Service. Khi lập trình viên viết:

Python
# Flawed query pattern: Fetching strictly by ID without tenant/owner filtering
order = Order.query.get(order_id)
return jsonify(order.to_dict())

Bất kỳ người dùng hợp lệ nào chỉ cần thay đổi số thứ tự order_id là có thể duyệt qua toàn bộ đơn hàng của tất cả khách hàng khác trên hệ thống.

4. Cơ chế hoạt động của Mass Assignment (Auto-binding)

Mô hình Mass Assignment: Tự động bind trường dữ liệu nhạy cảm

Các framework hiện đại (Spring MVC, Ruby on Rails, Django REST, ASP.NET, Express) được thiết kế để tăng tốc độ phát triển. Một trong những tính năng tiện lợi nhất là Object Binding: tự động lấy các trường trong HTTP Request Body ánh xạ vào các thuộc tính (fields) của class:

Java
// Spring Boot vulnerable controller
@PutMapping("/users/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User userUpdate) {
    User existingUser = userRepository.findById(id).orElseThrow();
    // ModelMapper automatically copies ALL matching fields from userUpdate to existingUser!
    modelMapper.map(userUpdate, existingUser);
    userRepository.save(existingUser);
    return ResponseEntity.ok(existingUser);
}

Nếu class User có các thuộc tính nhạy cảm như accountBalance, verificationStatus, role, kẻ tấn công chỉ cần gửi thêm các trường này trong payload JSON để ghi đè dữ liệu.

🐛 Tìm bug trong code này
# E-commerce checkout controller
@app.route('/api/orders/create', methods=['POST'])
@login_required
def create_order():
  data = request.get_json()
  # Developer initializes Order entity directly from user JSON
  new_order = Order(**data)
  new_order.user_id = current_user.id
  
  db.session.add(new_order)
  db.session.commit()
  return jsonify({"order_id": new_order.id}), 201

Thử ngẫmVì sao các bộ lọc WAF truyền thống hoàn toàn bất lực trong việc phát hiện cả hai lỗ hổng BOLA và Mass Assignment?

Bởi vì request gửi lên hoàn toàn là cú pháp JSON hợp lệ, mã token JWT chuẩn xác, không chứa bất kỳ ký tự đặc biệt hay đoạn script SQL/XSS nào; WAF không thể biết trường role hay ID 502 có hợp lệ với logic nghiệp vụ hay không.

5. Pitfalls & Giải pháp khắc phục triệt để

❌ Bẫy thường gặp: Dùng chung một Entity cho cả Input và Database

Nhiều lập trình viên lười biếng sử dụng luôn class Database Entity (như JPA @Entity hoặc SQLAlchemy model) làm tham số nhận dữ liệu đầu vào tại Controller. Thói quen này mở toang cánh cửa cho Mass Assignment.

✅ Giải pháp chuẩn mực: Kiến trúc DTO và Kiểm tra quyền mức bản ghi

Để bảo vệ API toàn diện:

  1. Khắc phục BOLA qua truy vấn có Scoping: Luôn luôn kết hợp ID tài nguyên với ID của người dùng hiện tại trong mệnh đề truy vấn:
    Python
    # SECURE PATTERN: Enforce ownership scoping at query level
    current_user_id = get_jwt_identity()
    order = Order.query.filter_by(id=order_id, user_id=current_user_id).first_or_404()
    
  2. Khắc phục Mass Assignment bằng Data Transfer Object (DTO): Định nghĩa class DTO riêng biệt, chỉ khai báo chính xác các trường mà người dùng được phép sửa đổi:
    Python
    # SECURE PATTERN: Strict DTO schema with Pydantic / Marshmallow
    from pydantic import BaseModel, Field
    
    class UserProfileUpdateDTO(BaseModel):
        full_name: str = Field(..., max_length=100)
        bio: str = Field("", max_length=500)
        phone: str = Field(..., max_length=15)
        # Strictly NO role, NO is_admin, NO balance!
    
    @app.route('/api/v1/users/profile', methods=['PUT'])
    @jwt_required
    def update_profile():
        dto = UserProfileUpdateDTO(**request.get_json())
        user = User.query.get(get_jwt_identity())
        
        # Only update explicitly permitted fields
        user.full_name = dto.full_name
        user.bio = dto.bio
        user.phone = dto.phone
        db.session.commit()
        return jsonify({"message": "Hoan tat"})
    

6. Đào sâu

Tài liệu tham khảo chuyên sâu về bảo mật REST API:

7. Tóm tắt

  • BOLA (Broken Object Level Authorization) xảy ra khi máy chủ không đối chiếu quyền sở hữu của người dùng đối với ID đối tượng trong URL.
  • Mass Assignment phát sinh do cơ chế tự động binding các cặp key-value từ request JSON vào các thuộc tính của Entity đối tượng bên dưới.
  • Cả hai lỗ hổng đều thuộc tầng logic nghiệp vụ, hoàn toàn vô hiệu hóa các hệ thống tường lửa WAF truyền thống.
  • Biện pháp khắc phục chuẩn mực là: luôn ràng buộc user_id trong truy vấn dữ liệu và sử dụng Data Transfer Object (DTO) độc lập để kiểm soát chặt chẽ danh sách trường được phép cập nhật.

8. Tự kiểm tra

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Sự khác biệt cốt lõi giữa BOLA (API1:2023) và BFLA (Broken Function Level Authorization - API5:2023) là gì?
  2. Q2
    Tại sao việc thay thế số nguyên tự tăng (Auto-increment ID như 1, 2, 3) bằng UUID v4 (như 550e8400-e29b-41d4-a716-446655440000) không được coi là một giải pháp khắc phục triệt để BOLA?
  3. Q3
    Trong kiến trúc GraphQL, lỗ hổng Mass Assignment và BOLA biểu hiện như thế nào?
  4. Q4
    Excessive Data Exposure (Rò rỉ dữ liệu thừa) liên quan gì tới Mass Assignment trong vòng đời ứng dụng?
  5. Q5
    Làm thế nào để các bài kiểm thử tự động (Unit Test / Integration Test) phát hiện ra lỗ hổng BOLA trước khi triển khai production?

Bài tiếp theo: Mini-challenge: Khai thác SSRF leo quyền Cloud Metadata

Bài này có giúp bạn hiểu bản chất không?

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Thảo luận & góp ý

Hỏi đáp về bài này

Bài tiếp theo

Mini-challenge: Khai thác SSRF leo quyền Cloud Metadata