Rà soát API theo OWASP API Top 10 trước khi đội bảo mật của khách vào cuộc
Lỗ hổng đứng đầu danh sách OWASP có thể bị khai thác chỉ bằng cách đổi ID trong request, và bạn hoàn toàn có thể tự kiểm tra điều đó trước khi đội bảo mật của khách làm.
- 1Kiểm kê host (API9)Ghi mọi host, phiên bản; gỡ API cũ, bỏ dữ liệu production khỏi staging
- 2Tráo ID (API1 BOLA)Token user B đọc đối tượng của user A, kết quả phải là 403 hoặc 404
- 3Rà xác thực và cấu hìnhKhông chỉ dựa vào API key, không giữ cấu hình mặc định
- 4Soi thông báo lỗiLỗi chung chung, không lộ stack trace; input sai bị schema chặn
- 5Thử rate limitGửi 50 request vào endpoint login, phải thấy 429
- 6Nhìn ra ngoài (API7, API10)Thử URL nội bộ ở tham số webhook, validate dữ liệu từ API bên thứ ba
Kiểm kê trước, tráo ID ngay sau đó, rồi mới nhìn ra các API bên ngoài mà hệ thống gọi tới.
Đồ hoạ: FDE Times
Tóm tắt nhanh
- BOLA đứng số một trong OWASP API Top 10 2023 và có thể bị khai thác chỉ bằng cách đổi ID đối tượng trong request, vì thế nên chạy test với hai tài khoản từ sớm.
- Kiểm kê host, gỡ phiên bản API cũ và không dùng dữ liệu production ở staging (API9) là việc nên làm trước khi viết thêm test, vì lỗi ở đây dễ nối với các lỗi khác.
- SSRF (API7) và Unsafe Consumption (API10) hay bị bỏ sót vì chúng nằm ở chỗ API của bạn gọi ra ngoài.
Một request GET /api/v1/orders/1002, gửi bằng token của người dùng không sở hữu đơn hàng 1002, trả về 200 kèm đầy đủ địa chỉ giao hàng. Đó là toàn bộ cuộc tấn công.
OWASP xếp lỗi này, Broken Object Level Authorization (BOLA), ở vị trí số một trong API Security Top 10 2023, và mô tả cách khai thác đúng như vậy: kẻ tấn công chỉ cần thao túng ID của đối tượng trong request.
Với một FDE, tuần trước khi đội bảo mật của khách bắt đầu pentest hệ thống bạn vừa triển khai là lúc đáng lo nhất. Thử hình dung họ tìm ra BOLA trong mười phút đầu: mọi thảo luận về tính năng sẽ dừng lại, và niềm tin bạn xây suốt nhiều tuần cũng lung lay theo. Vậy thì hãy tự làm pentester trước họ một bước.
Hướng dẫn này đi qua một buổi rà soát thực tế: bạn sẽ có một file kiểm kê API, một test BOLA tự động, vài lệnh kiểm tra rate limit, thông báo lỗi và SSRF, cùng một danh sách việc cần làm với những API mà hệ thống của bạn gọi ra ngoài.
Cần chuẩn bị gì trước khi bắt đầu?
Bạn cần quyền truy cập môi trường staging (tuyệt đối không thử trên production khi chưa được khách cho phép), hai tài khoản test thuộc hai người dùng hoặc hai tenant khác nhau, cùng curl và Python có thư viện requests và pytest. Mở sẵn mục lục OWASP API Security Top 10 2023 để đánh dấu từng mục, từ API1 đến API10, như một checklist.
Các đoạn code dưới đây chỉ để minh hoạ và đã được đơn giản hoá. Endpoint /api/v1/orders/{id}, biến TOKEN_A, TOKEN_B và host staging.example.com là giả định, bạn cần thay bằng hệ thống thật của khách.
Bước 1: bạn có biết mình đang phơi ra bao nhiêu API không?
Với API9 Improper Inventory Management, OWASP khuyên bước đầu tiên là kiểm kê toàn bộ API host và ghi lại những thông tin quan trọng của từng host. Không ai bảo vệ được một endpoint mà mình không biết là nó tồn tại. Hãy tạo một file inventory ngay trong repo:
# api-inventory.yaml (minh hoạ)
- host: api.example.com
env: production
versions: [v2]
auth: oauth2
- host: staging.example.com
env: staging
versions: [v1, v2]
data: anonymized
Sau đó soi lại từng dòng theo hai câu hỏi. Có phiên bản cũ nào vẫn đang chạy không? Curity nói thẳng rằng không được để phiên bản API cũ tiếp tục hoạt động. Host non-production nào đang chứa dữ liệu thật? OWASP khuyên tránh dùng dữ liệu production cho các bản triển khai non-production.
Kiểm tra: mỗi host đều có chủ sở hữu, môi trường và phiên bản được ghi rõ. Thử hình dung staging ở trên vẫn mở v1 và v1 không có kiểm tra phân quyền mới. Khi đó pentester chẳng cần tấn công v2, họ chỉ việc đi cửa sau.
Bước 2: tráo ID và xem API có chặn không
Đây là bước quan trọng nhất. Bạn dùng token của user B để đọc một đối tượng thuộc về user A:
# Order 1001 thuộc user A
curl -s -o /dev/null -w "%{http_code}\n" \
-H "Authorization: Bearer $TOKEN_B" \
https://staging.example.com/api/v1/orders/1001
Kết quả mong đợi là 403 hoặc 404. Nếu nhận 200, bạn đã tìm ra BOLA. OWASP khuyến nghị viết test để đánh giá cơ chế phân quyền, vì vậy hãy biến lệnh tay ở trên thành test chạy được trong CI:
# test_bola.py (minh hoạ, đã đơn giản hoá)
import os, requests
BASE = "https://staging.example.com/api/v1"
def test_user_b_cannot_read_user_a_order():
r = requests.get(f"{BASE}/orders/1001",
headers={"Authorization": f"Bearer {os.environ['TOKEN_B']}"})
assert r.status_code in (403, 404)
Hãy để ý con số 1001. Nếu ID tăng tuần tự thì 1002, 1003 là những thứ ai cũng đoán được, nên OWASP khuyên dùng ID ngẫu nhiên, khó đoán. Dù vậy, ID khó đoán chỉ là lớp thứ hai.
Curity nhấn mạnh rằng kiểm tra phân quyền cấp đối tượng phải có mặt ở mọi hàm truy cập nguồn dữ liệu, còn OWASP REST Security Cheat Sheet yêu cầu dịch vụ không công khai kiểm soát truy cập ở từng endpoint.
Kiểm tra: lặp lại test cho mọi endpoint có {id} trong đường dẫn, kể cả PUT và DELETE.
Bước 3: API key có đang gánh quá nhiều không?
Mở phần cấu hình xác thực ra và tìm những endpoint chỉ được bảo vệ bằng một API key tĩnh. Cheat Sheet của OWASP cảnh báo không được chỉ dựa vào API key để bảo vệ tài nguyên nhạy cảm, quan trọng hoặc có giá trị cao.
Nếu endpoint xuất dữ liệu khách hàng chỉ cần một header X-API-Key, hãy đánh dấu nó vào báo cáo của bạn trước khi đội bảo mật làm việc đó.
Cũng trong bước này, bạn rà luôn cấu hình. Curity khuyên không nên dựa vào cấu hình mặc định. Cách làm thực tế là mở từng file config của framework, gateway và server, rồi với mỗi giá trị chưa ai chủ động đặt, hỏi xem nó có thật sự cần bật trên môi trường của khách hay không, như thể bạn đang review code của người khác.
Bước 4: lỗi trả về có đang kể quá nhiều không?
Gửi một request hỏng có chủ đích rồi xem response:
curl -s -H "Authorization: Bearer $TOKEN_A" \
-H "Content-Type: application/json" \
-d '{"quantity": "abc"}' \
https://staging.example.com/api/v1/orders
Cheat Sheet yêu cầu trả thông báo lỗi chung chung và tránh tiết lộ chi tiết thất bại khi không cần thiết. Nếu response chứa stack trace, tên bảng hay đường dẫn file trên server, bạn đã đưa cho pentester một tấm bản đồ.
Câu lệnh trên còn kiểm tra luôn chuyện validate: Bright Security coi validate và làm sạch đầu vào là tuyến phòng thủ đầu tiên, vì vậy "abc" cần bị schema chặn từ cửa với một lỗi 400 gọn gàng.
Bước 5: gửi dồn dập và đếm số 429
Cheat Sheet khuyên trả về 429 Too Many Requests khi request đến quá nhanh. Hãy thử trên endpoint đăng nhập, nơi brute-force hay nhắm tới:
for i in $(seq 1 50); do
curl -s -o /dev/null -w "%{http_code}\n" \
-X POST https://staging.example.com/api/v1/login \
-d '{"user":"test","password":"wrong"}'
done | sort | uniq -c
Kết quả 50 401 có nghĩa là không có giới hạn nào, kẻ tấn công cứ thế thử mật khẩu mãi. Kết quả bạn muốn thấy là một vài 401 rồi chuyển sang 429. Bright Security mô tả API gateway như một điểm vào duy nhất cho toàn bộ lưu lượng API, nên nếu khách đã có gateway thì đó thường là nơi đặt rate limit hợp lý nhất.
Họ cũng khuyên đưa scan tự động vào CI/CD, dù nên nhớ đây là lời khuyên từ một nhà cung cấp công cụ DAST.
Bước 6: còn những API mà hệ thống của bạn gọi ra ngoài thì sao?
API7 là Server Side Request Forgery (SSRF) và API10 là Unsafe Consumption of APIs, hai mục rất dễ bị bỏ sót nếu bạn chỉ nhìn vào endpoint của chính mình.
Với SSRF, hãy tìm mọi tham số nhận URL, chẳng hạn webhook hoặc “import từ link”, vì đó là nơi server của bạn có thể bị lừa gọi vào mạng nội bộ của khách. Phép thử đơn giản nhất là đưa vào một URL trỏ về địa chỉ nội bộ.
Endpoint /webhooks và địa chỉ 10.0.0.5 dưới đây là giả định, hãy thay bằng một host nội bộ có thật mà khách cho phép bạn thử:
# Minh hoạ: đăng ký webhook trỏ vào một host nội bộ
curl -s -w "\n%{http_code}\n" \
-H "Authorization: Bearer $TOKEN_A" \
-H "Content-Type: application/json" \
-d '{"url": "http://10.0.0.5:8080/admin"}' \
https://staging.example.com/api/v1/webhooks
Kết quả bạn muốn thấy là API từ chối ngay với lỗi 400 vì URL không hợp lệ.
Nếu API nhận URL, rồi khi kích hoạt webhook bạn thấy server thật sự gọi vào host đó, hoặc response lộ ra nội dung từ trang nội bộ, bạn đã tìm ra một đường SSRF. Kiểm tra: lặp lại với http://localhost và với mọi tham số nhận URL mà bạn ghi được ở file inventory.
Với API10, Curity chỉ ra rằng developer thường tin dữ liệu từ API bên thứ ba hơn cả input của người dùng. Thử hình dung hệ thống của bạn lấy tỷ giá hoặc thông tin vận chuyển từ một đối tác rồi ghi thẳng vào database. Nếu đối tác bị xâm nhập, dữ liệu độc sẽ đi vào hệ thống mà không gặp bất kỳ lớp kiểm tra nào.
Cách xử lý là validate response của bên thứ ba bằng schema, đúng như cách bạn validate body ở Bước 4.
Ba lỗi nhỏ nối lại thành một sự cố lớn thế nào?
Sáu bước trên dễ khiến bạn nghĩ mỗi mục OWASP là một ô tách biệt. Thử hình dung một tình huống: staging vẫn chạy v1 (lỗi kiểm kê, API9), v1 không kiểm tra quyền sở hữu đơn hàng (BOLA, API1), và staging được nạp bản sao dữ liệu production để test cho “giống thật”.
Đứng riêng, mỗi lỗi nghe có vẻ nhẹ: một phiên bản cũ, một môi trường không phải production, một endpoint ít người dùng. Ghép lại, chỉ một vòng lặp đổi ID từ 1001 trở đi trên /api/v1/orders của staging là đủ để đọc địa chỉ thật của khách hàng thật. Thiếu thêm rate limit như ở Bước 5, vòng lặp đó chạy hết mà không bị chặn.
Vì thế khi ghi kết quả rà soát, đừng chỉ đánh dấu từng ô. Với mỗi lỗi tìm được, hãy hỏi thêm: lỗi này mở đường cho lỗi nào khác, và gỡ mắt xích nào thì cả chuỗi đứt? Trong ví dụ trên, tắt v1 hoặc xoá dữ liệu thật khỏi staging đã đủ đứt chuỗi ngay cả trước khi bạn sửa code phân quyền.
Những lỗi hay gặp khi tự rà soát
Lỗi phổ biến nhất là chỉ test bằng một tài khoản. Dùng một tài khoản thì không bao giờ phát hiện được BOLA, vì người dùng đó luôn sở hữu dữ liệu mình đọc. Lỗi thứ hai là chỉ rà phiên bản mới nhất trong khi v1 vẫn chạy trên staging với dữ liệu thật.
Lỗi thứ ba là coi ID kiểu UUID như một biện pháp phân quyền, trong khi nó chỉ làm việc đoán ID khó hơn.
Kỹ năng này xuất hiện thế nào ở site khách?
Nếu khách yêu cầu đội bảo mật của họ rà soát trước khi cho hệ thống lên production, một FDE gửi kèm file inventory, bộ test BOLA đang chạy xanh trong CI và một bảng đối chiếu mười mục OWASP ghi rõ “đã kiểm tra, bằng cách nào” có thể biến buổi pentest từ một cuộc săn lỗi thành một buổi xác nhận.
Khi đọc JD, hãy để ý những cụm như “work with customer security teams” hay “security review”; còn trong CV, hãy viết cụ thể kiểu “viết test phân quyền cấp đối tượng cho N endpoint, chặn lỗi BOLA trước khi pentest”, thay vì chỉ ghi “am hiểu OWASP”.
Đội bảo mật của khách sẽ đọc OWASP Top 10 dù bạn có đọc hay không. Điều duy nhất bạn chọn được là ai tìm ra lỗi trước.
6 nguồn
- OWASP API Security Top 10 2023 – Table of Contents
- API1:2023 Broken Object Level Authorization – OWASP API Security
- API9:2023 Improper Inventory Management – OWASP API Security
- Top 10 API Security Vulnerabilities (Curity) · 2023-06-13
- Top API Vulnerabilities and 6 Ways to Mitigate Them (Bright Security) · 2023-10-04
- REST Security Cheat Sheet – OWASP Cheat Sheet Series