Tóm tắt: Đầu vào API là một bề mặt tấn công, vì vậy hãy kiểm thử nó như một bề mặt tấn công. Viết các trường hợp kiểm thử tiêu cực (negative cases) gửi trường quá lớn, sai kiểu dữ liệu, body định dạng sai và chuỗi injection; sau đó xác nhận endpoint trả về lỗi 4xx, không bao giờ là 5xx. Biến xác thực schema thành một kiểm soát bảo mật với
additionalProperties: false, enum và giới hạn độ dài. Chạy toàn bộ bộ kiểm thử trong CI trên mỗi thay đổi. Các tác nhân AI khiến việc này cấp bách hơn: chúng tạo và chuyển tiếp payload với tốc độ máy, vì vậy “tải dữ liệu này” có thể lặng lẽ trở thành “chạy mã này” ở quy mô lớn.
Hầu hết bộ kiểm thử chỉ chứng minh API hoạt động khi người gọi lịch sự: gửi body hợp lệ, nhận 200, kiểm thử thành công. Điều đó gần như không cho biết API sẽ làm gì khi body không thân thiện. Đầu vào không đáng tin cậy là mọi dữ liệu endpoint không tự tạo ra: request body, query string, header, file upload, webhook payload và JSON do tác nhân AI tự động lắp ráp. Hãy giả định rằng sớm muộn sẽ có ai đó gửi phiên bản tệ nhất có thể của mọi đầu vào này.
Vào tháng 7 năm 2026, Hugging Face đã mô tả một sự cố bảo mật có vector tấn công là dữ liệu thay vì mật khẩu bị đánh cắp. Chúng tôi đã đề cập riêng về các bài học từ vụ vi phạm đó; bài này tập trung vào phần thực hành.
Bạn sẽ xây dựng các kiểm thử gửi đúng loại đầu vào mà kẻ tấn công gửi, rồi chạy chúng tự động trên mỗi thay đổi. Các danh mục này phù hợp với OWASP API Security Top 10. Apidog là một cách để thiết kế hợp đồng và chạy các kiểm thử này, nhưng nguyên tắc vẫn áp dụng cho mọi framework.
Đầu vào là một bề mặt tấn công, không phải chỉ là trường biểu mẫu
Xác thực thường bị xem là một phần của UX: phát hiện email trống, hiển thị viền đỏ rồi kết thúc. Nhưng mỗi trường API chấp nhận là một lời hứa mà người gọi có thể phá vỡ, và mỗi lời hứa bị phá vỡ là một đường đi vào logic ứng dụng.
Ví dụ:
-
limitđược kỳ vọng là số nguyên nhỏ nhưng nhận999999999. -
filenameđược kỳ vọng là một tên tệp đơn giản nhưng nhận../../etc/passwd. -
configđược kỳ vọng là cấu hình nhưng nhận một tập lệnh hoặc payload có thể thực thi.
Kiểm thử bảo mật không phải là phần việc tách biệt để thêm vào cuối dự án. Đây là kiểm thử tiêu cực, nhưng tập trung vào những trường có thể gây hại nhất.
Một cách thực hành đơn giản:
- Liệt kê mọi trường endpoint nhận vào.
- Với từng trường, hỏi: “Giá trị tệ nhất có thể gửi vào đây là gì?”
- Viết một kiểm thử cho từng loại lỗi.
- Xác nhận endpoint từ chối an toàn.
Đây là nền tảng của các thực tiễn tốt nhất về bảo mật API.
Làm thế nào “tải dữ liệu này” trở thành “chạy mã này”
Sự cố Hugging Face cho thấy vì sao dữ liệu đầu vào cần được xem xét kỹ. Hugging Face cho biết vector tấn công là các tập dữ liệu độc hại: một tập dữ liệu được chế tạo đã kích hoạt bộ tải tập dữ liệu có thể chạy mã từ xa, đồng thời có injection template trong cấu hình tập dữ liệu. Xem chi tiết trong báo cáo sự cố bảo mật.
Hình thức thất bại có thể khái quát như sau:
- Endpoint chấp nhận thứ được mô tả là “dữ liệu”.
- Việc tải hoặc xử lý dữ liệu kích hoạt một đường dẫn có thể thực thi mã.
- Kẻ tấn công kiểm soát dữ liệu đó.
- “Tải dữ liệu này” trở thành “chạy mã này”.
Template injection cũng tương tự: một giá trị được cho là văn bản tĩnh lại bị một template engine đánh giá, biến văn bản thành thực thi.
Bất kỳ endpoint nào nhận các giá trị như sau đều cần kiểm thử nghiêm ngặt:
- tên loader;
- định dạng dữ liệu;
- template;
- đối tượng tuần tự hóa;
- blob cấu hình;
- tên tệp hoặc đường dẫn;
- tùy chọn có thể được truyền sang shell hoặc công cụ bên ngoài.
Nếu bạn chưa từng gửi cấu hình độc hại vào endpoint đó trong kiểm thử, bạn chưa xác minh được giả định rằng dữ liệu vẫn chỉ là dữ liệu.
Xác thực schema như một kiểm soát bảo mật
Kiểm soát rẻ và hiệu quả nhất là schema nghiêm ngặt ở biên API. Schema không chỉ là tài liệu. Khi từ chối mọi thứ không khớp schema, nó trở thành bộ lọc chạy trước khi logic nghiệp vụ xử lý request.
JSON Schema cung cấp các thành phần cần thiết để tạo bộ lọc này.
Ví dụ schema cho cấu hình tập dữ liệu:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"required": ["loader", "name"],
"properties": {
"loader": { "enum": ["csv", "json", "parquet"] },
"name": {
"type": "string",
"maxLength": 128,
"pattern": "^[\\w .-]+$"
},
"rows": {
"type": "integer",
"minimum": 0,
"maximum": 1000000
}
}
}
Schema trên tạo ra bốn lớp phòng thủ cụ thể:
| Kiểm soát | Tác dụng |
|---|---|
additionalProperties: false |
Từ chối trường lạ như template, command hoặc format. |
enum cho loader
|
Chỉ cho phép loader được hỗ trợ; từ chối pickle:// hoặc loader không xác định. |
maxLength |
Chặn chuỗi nhiều megabyte có thể làm cạn kiệt bộ nhớ. |
pattern |
Loại bỏ ký tự hoặc định dạng không phù hợp trước khi dữ liệu đi sâu hơn vào hệ thống. |
minimum / maximum
|
Chặn giá trị số vượt phạm vi nghiệp vụ hoặc gây tiêu tốn tài nguyên. |
Điểm chính là schema chỉ chấp nhận tập đầu vào hẹp mà ứng dụng thực sự hỗ trợ. Sự hẹp đó là một thuộc tính bảo mật.
Schema không bắt được mọi lỗ hổng. Một giá trị có thể hợp lệ theo schema nhưng vẫn nguy hiểm khi được đưa vào SQL, template engine hoặc shell. Tuy nhiên, schema giải quyết một lỗi rất phổ biến: endpoint nhận dữ liệu mà đội ngũ chưa từng chủ động xác định là được phép.
Kiểm thử tiêu cực: chứng minh endpoint từ chối
Kiểm thử happy-path xác nhận đầu vào tốt tạo ra đầu ra tốt.
Kiểm thử tiêu cực xác nhận đầu vào xấu tạo ra sự từ chối có kiểm soát.
Sự từ chối là một tính năng bảo mật:
-
400 Bad Requesthoặc422 Unprocessable Entity: API đang bảo vệ ranh giới. -
413 Payload Too Large: API đang thực thi giới hạn kích thước. -
500 Internal Server Error: payload đã chạm tới mã không sẵn sàng xử lý nó.
Với mỗi trường, lập danh sách những gì endpoint phải từ chối:
- sai kiểu dữ liệu;
- trường bắt buộc bị thiếu;
- trường bị cấm nhưng vẫn xuất hiện;
- giá trị quá dài;
- số ngoài phạm vi;
- định dạng không hợp lệ;
- chuỗi injection phù hợp với ngữ cảnh sử dụng.
Sau đó xác nhận hai điều:
- Phản hồi là
4xx. - Phản hồi không bao giờ là
5xx.
Danh sách kiểm tra kiểm thử bảo mật API có thể dùng làm điểm bắt đầu theo từng trường.
Xác nhận hành vi, không phụ thuộc vào thông báo lỗi
Tránh viết kiểm thử phụ thuộc vào toàn bộ chuỗi lỗi:
assert "invalid loader" in response.text
Thông báo có thể thay đổi vì refactor vô hại, khiến nhóm có xu hướng nới lỏng kiểm thử.
Thay vào đó, ưu tiên kiểm tra:
assert response.status_code in (400, 413, 422)
assert response.status_code < 500
Nếu phù hợp, hãy xác nhận không có tác dụng phụ:
- không tạo bản ghi;
- không ghi file;
- không gửi webhook;
- không thay đổi trạng thái tác vụ.
Các loại injection cần có kiểm thử riêng
Bạn không cần kiểm thử mọi biến thể có thể tưởng tượng. Nhưng nên có ít nhất một payload thăm dò cố định cho mỗi loại injection phổ biến để lỗi hồi quy thất bại rõ ràng.
Các công cụ phát hiện lỗ hổng API tự động có thể mở rộng phạm vi sau đó. Trước hết, hãy viết các trường hợp thủ công có giá trị cao.
SQL injection
Gửi payload vào mọi trường có thể được dùng trong truy vấn:
1); DROP TABLE datasets;--
Kỳ vọng:
- endpoint xử lý giá trị như dữ liệu;
- trả về
400hoặc kết quả rỗng tùy hợp đồng; - không lộ lỗi cơ sở dữ liệu;
- không có thay đổi dữ liệu ngoài ý muốn.
Template injection
Gửi các payload như:
{{ 7*7 }}
{{ config.__class__ }}
Vào các trường tên, nhãn hoặc template.
Nếu phản hồi chứa 49, rất có thể template engine đã đánh giá đầu vào. Đây là dấu hiệu nghiêm trọng của server-side template injection.
Deserialization không an toàn và loader chạy mã từ xa
Gửi loader không được hỗ trợ hoặc đối tượng tuần tự hóa vào nơi chỉ chấp nhận giá trị đơn giản:
{
"loader": "pickle://s3/models/payload.pkl"
}
Endpoint phải từ chối loader không nằm trong allowlist. Không cố gắng “hữu ích” bằng cách tự suy luận hoặc tải loader lạ.
Command injection
Gửi payload vào các trường có thể trở thành đối số shell, ví dụ tên tệp hoặc tùy chọn chuyển đổi:
; id
$(id)
Nếu endpoint trả về dữ liệu cho thấy lệnh đã chạy, đây là một phát hiện nghiêm trọng.
Payload quá lớn, định dạng sai và nhầm lẫn Content-Type
Không phải payload độc hại nào cũng là chuỗi injection tinh vi. Nhiều payload chỉ quá lớn hoặc sai cấu trúc, và chúng có thể làm hỏng parser trước khi logic xác thực chạy.
Kiểm thử giới hạn kích thước
Gửi:
- một trường chuỗi có năm megabyte cùng một ký tự;
- mảng JSON có một triệu phần tử;
- body vượt quá giới hạn request của dịch vụ.
Ví dụ:
oversized_payload = {
"loader": "csv",
"name": "A" * 5_000_000
}
Kỳ vọng phản hồi:
413 Payload Too Large
Không chấp nhận tình huống worker cấp phát bộ nhớ cho đến khi bị treo hoặc sập.
Kiểm thử body sai định dạng
Gửi các body như:
{"loader": "csv",
Hoặc JSON có dấu phẩy thừa:
{
"loader": "csv",
}
Hoặc JSON lồng sâu hàng nghìn cấp để thăm dò cạn kiệt stack.
Kỳ vọng là lỗi 400 nhanh chóng, không phải request treo hoặc worker lỗi.
Kiểm thử Content-Type
Thử các cặp header và body không khớp:
Content-Type: application/json
Nhưng gửi XML:
<dataset><loader>csv</loader></dataset>
Hoặc khai báo:
Content-Type: application/xml
Nhưng gửi payload XML có external entity để thăm dò XXE.
Cũng nên gửi JSON dưới text/plain để kiểm tra parser có chấp nhận dữ liệu quá lỏng lẻo hay không.
Mục tiêu là xác minh server không chỉ tin vào header hoặc body riêng lẻ, mà kiểm tra đúng parser cho đúng content type.
Tại sao tác nhân AI làm tăng rủi ro
Các rủi ro trên đã tồn tại trước AI agent. Agent thay đổi quy mô và tốc độ.
Một kẻ tấn công thủ công phải gõ từng request. Một tác nhân AI có thể tạo, biến đổi và chuyển tiếp payload với tốc độ máy.
Ba đặc tính làm rủi ro tăng lên:
Tổng hợp đầu vào
Agent tạo giá trị trường mà không ai viết tay và có thể vượt ngoài các ví dụ kiểm thử thông thường.Thử lại và chuỗi hóa cuộc gọi
Một tài liệu hoặc webhook bị nhiễm độc có thể tạo ra hàng nghìn request độc hại trong vài giây.Chuyển tiếp dữ liệu được yêu cầu tin tưởng
Agent có thể lấy payload từ nguồn bên ngoài rồi gửi nó đến API nội bộ mà không hiểu đây là dữ liệu độc hại.
Mô hình “tải dữ liệu này” biến thành “chạy mã này” đặc biệt nguy hiểm với agent, vì agent có thể thực hiện lệnh xuyên qua ranh giới tin cậy mà không nhận ra.
Xem thêm về prompt injection cho các nhóm API.
Biện pháp phòng thủ không thay đổi: xác thực nghiêm ngặt, từ chối có kiểm soát và kiểm thử tự động. Điều thay đổi là bạn không thể dựa vào kiểm tra thủ công khi lưu lượng do agent tạo ra.
Xây dựng bộ kiểm thử tiêu cực và chạy trong CI
Đưa các payload tiêu cực vào bộ kiểm thử chạy trên mọi pull request. Ví dụ pytest dưới đây gửi cấu hình độc hại tới endpoint staging và xác nhận endpoint từ chối an toàn:
import httpx
import pytest
BASE = "https://staging.internal/v1"
HOSTILE_CONFIGS = [
{"loader": "pickle://s3/models/payload.pkl", "format": "auto"},
{"loader": "csv", "name": "{{ 7*7 }}"},
{"loader": "csv", "name": "{{ config.__class__ }}"},
{"loader": "csv", "filter": "1); DROP TABLE datasets;--"},
{"loader": "csv", "name": "A" * 5_000_000},
]
@pytest.mark.parametrize("config", HOSTILE_CONFIGS)
def test_dataset_config_is_refused(config):
response = httpx.post(
f"{BASE}/datasets",
json={"config": config},
timeout=10,
)
assert response.status_code in (400, 413, 422), response.text
assert response.status_code < 500, (
"5xx means the payload reached logic it should not"
)
assert "49" not in response.text, (
"template rendered: server-side template injection"
)
Một lưu ý thực tế: nếu endpoint của bạn tạo dữ liệu hoặc kích hoạt tác vụ bất đồng bộ, hãy thêm kiểm tra không có tác dụng phụ. Ví dụ, truy vấn lại danh sách dataset để xác nhận không có bản ghi mới được tạo sau request bị từ chối.
Chạy kiểm thử trong GitHub Actions
Workflow tối thiểu:
name: api-abuse-tests
on: [push, pull_request]
jobs:
negative-input:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pytest tests/negative_input.py -q
Để workflow thực sự bảo vệ merge, hãy cấu hình status check này là bắt buộc trên nhánh chính.
Dùng hợp đồng OpenAPI để giữ kiểm thử sát endpoint
Công cụ schema-first giúp kiểm thử tiêu cực dễ duy trì hơn vì hợp đồng API và kịch bản kiểm thử nằm gần nhau.
Trong Apidog, bạn có thể thiết kế endpoint theo hợp đồng OpenAPI, sau đó lưu các kịch bản tiêu cực cạnh các kịch bản happy-path:
- trường quá lớn;
- sai kiểu dữ liệu;
- trường không được phép;
- loader không nằm trong allowlist;
- chuỗi SQL injection;
- chuỗi template injection;
- Content-Type không khớp;
- body JSON sai định dạng.
Mỗi kịch bản nên xác nhận phản hồi là 4xx. Sau đó chạy cùng bộ kịch bản trong CI qua Apidog CLI để thay đổi làm lỏng xác thực sẽ làm lỗi build thay vì được triển khai.
Nếu muốn bắt đầu, hãy tải xuống Apidog và thêm một trường hợp tiêu cực cho endpoint hiện có.
Cần phân biệt rõ phạm vi: Apidog là công cụ thiết kế, kiểm thử, mô phỏng và tài liệu API. Nó không phải WAF, không lọc trực tiếp lưu lượng production và không thay thế SIEM. Xác thực hợp đồng cũng không bắt được mọi lỗ hổng. Giá trị chính là làm rõ endpoint thực sự chấp nhận gì, đồng thời biến những giả định đó thành kiểm thử có thể chạy lặp lại.
Các câu hỏi thường gặp
Sự khác biệt giữa kiểm thử tiêu cực và fuzzing là gì?
Kiểm thử tiêu cực gửi một tập đầu vào xấu được chọn có chủ đích, thường là một trường hợp cho mỗi loại lỗi cần quan tâm.
Fuzzing gửi số lượng lớn đầu vào ngẫu nhiên hoặc đã biến đổi để tìm lỗi mà bạn chưa nghĩ tới.
Bắt đầu với kiểm thử tiêu cực vì chúng nhanh, xác định và phù hợp với CI. Thêm fuzzing khi cần mở rộng phạm vi bao phủ.
Có nên chạy các kiểm thử này trên production không?
Không. Hãy chạy trên staging hoặc môi trường biệt lập.
Một số payload, như body quá lớn hoặc command injection probe, được thiết kế để gây áp lực lên hệ thống. Nếu có lỗi, chúng cũng có thể làm thay đổi dữ liệu. Môi trường kiểm thử chuyên dụng cho phép kiểm thử mạnh mà không gây rủi ro cho người dùng thật.
WAF có bắt được các payload này không?
WAF là một lớp phòng thủ theo chiều sâu hữu ích, nhưng không thay thế việc endpoint tự từ chối đầu vào xấu.
Quy tắc WAF có thể bị bỏ qua và WAF không hiểu đầy đủ logic nghiệp vụ của bạn. Các kiểm thử này phải chứng minh bản thân endpoint xử lý đầu vào an toàn.
Bao nhiêu trường hợp tiêu cực là đủ cho mỗi endpoint?
Nhắm đến ít nhất một trường hợp cho mỗi trường, theo từng loại lỗi có thể xảy ra:
- sai kiểu;
- ngoài phạm vi;
- quá dài;
- trường bị cấm;
- trường bắt buộc bị thiếu;
- injection phù hợp với ngữ cảnh.
Thông thường chỉ cần vài trường hợp cho mỗi endpoint. Phạm vi bao phủ theo loại lỗi quan trọng hơn số lượng test thô.
Xác thực schema có ngăn chặn hoàn toàn injection không?
Không. Schema không nên là lớp phòng thủ duy nhất.
Schema nghiêm ngặt giúp loại bỏ nhiều đầu vào sai định dạng, quá lớn hoặc có trường không mong muốn. Nhưng một giá trị hợp lệ theo schema vẫn có thể là SQL injection hoặc template injection nếu ứng dụng xử lý nó không an toàn.
Vẫn cần:
- truy vấn tham số hóa;
- deserialization an toàn;
- mã hóa đầu ra;
- tránh gọi shell với dữ liệu người dùng;
- allowlist cho loader, định dạng và hành động được phép.
Schema giúp thu hẹp bề mặt mà các lớp bảo vệ còn lại phải xử lý.
Top comments (0)