← Về danh sách
Kỹ nghệ & Quản lý phần mềm
#퍼징#Fuzz Testing#소프트웨어 테스트#보안 테스트#DevSecOps
Cập nhật lần cuối · 2026-09-13

Kiểm thử fuzzing (Fuzz Testing) và chiến lược phát hiện lỗ hổng

1. Tổng quan

Kiểm thử fuzzing (Fuzz Testing, Fuzzing) là kỹ thuật kiểm thử động đưa vào chương trình một lượng lớn đầu vào không chỉ hợp lệ mà còn ngẫu nhiên, biến đổi, giá trị biên và bất thường, rồi tự động quan sát các dấu hiệu bất thường như sập (crash), ngoại lệ, lỗi bộ nhớ, vi phạm tính nhất quán hay suy giảm hiệu năng để tìm ra khiếm khuyết.

Phần mềm không chỉ xử lý những đầu vào mà lập trình viên đã dự kiến. Dữ liệu đến từ bên ngoài như gói tin mạng, tệp hình ảnh và tài liệu, dữ liệu nén, yêu cầu API, thông điệp giao thức có thể bị hỏng định dạng, có độ dài quá mức hoặc chứa các giá trị mâu thuẫn nhau. Kiểm thử thủ công mạnh ở việc xác nhận các kịch bản hợp lệ tiêu biểu, nhưng con người khó tự tạo ra mọi tổ hợp cho bộ phân tích cú pháp (parser) và mô-đun truyền thông có không gian đầu vào rất lớn.

Fuzzing tự động khám phá không gian đầu vào rộng lớn này. Nó không đơn thuần sinh số ngẫu nhiên mà sử dụng định dạng đầu vào và kết quả thực thi của đối tượng kiểm thử để chọn đầu vào tiếp theo cần thử. Một fuzzer tốt liên tục kích thích các nhánh hoặc trạng thái mà chương trình chưa đi qua, đồng thời lưu giữ đầu vào tối thiểu tái hiện được hiện tượng bất thường, biến nó thành khiếm khuyết mà lập trình viên có thể sửa.

Đối tượng trực tiếp của fuzzing là mã diễn giải đầu vào từ bên ngoài như parser tệp, ngăn xếp giao thức, trình biên dịch, trình thông dịch, mô-đun xác thực, thư viện tuần tự hóa (serialization) và hợp đồng thông minh. Tuy nhiên, đối tượng không chỉ giới hạn ở đó. Tổ hợp số tiền, tiền tệ, ngày tháng của API giao dịch tài chính, thông điệp lệnh của thiết bị IoT, lược đồ (schema) của đường ống chuyển đổi dữ liệu cũng có thể là đối tượng của fuzzing.

Mục đích của fuzzing không phải là bản thân việc chạy thật nhiều ca kiểm thử. Thành quả được quyết định bởi phạm vi mã đã khám phá, số khiếm khuyết duy nhất được phát hiện, khả năng tái hiện và việc ngăn hồi quy sau khi sửa. Do đó cần xác định rõ ranh giới của đối tượng kiểm thử, thiết kế chiến lược sinh đầu vào và oracle (tiêu chí phán định) cần quan sát, và bao gồm cả quy trình vận hành để phân loại, tái hiện, sửa chữa các lỗi.

A. Bối cảnh xuất hiện và sự cần thiết

Thứ nhất, ranh giới đầu vào đã mở rộng ra ngoài hệ thống. Web API và ứng dụng di động nhận dữ liệu từ nhiều loại client, còn microservice trao đổi thông điệp giữa các dịch vụ qua mạng. Ngay cả khi một dịch vụ gửi giá trị khác với dự kiến, dịch vụ nhận vẫn phải từ chối một cách an toàn, vì vậy cần tự động kiểm chứng độ vững chắc của ranh giới hợp đồng.

Thứ hai, lỗ hổng bảo mật thường phát sinh ở các đường ngoại lệ chứ không phải luồng bình thường. Tràn số nguyên khi tính độ dài, giải nén sai, đệ quy vô hạn trong cấu trúc lồng nhau, vượt qua chuyển trạng thái xác thực là những lỗi dễ bị bỏ sót nếu chỉ dùng kiểm thử chức năng thông thường. Fuzzing lặp đi lặp lại việc kích thích các điều kiện biên như vậy, tạo ra điểm giao giữa kiểm thử bảo mật và kiểm thử chất lượng.

Thứ ba, chu kỳ phát triển và triển khai đã ngắn lại. Nếu mỗi lần phát hành đều phải tạo thủ công nhiều đầu vào thì kiểm thử trở thành nút thắt cổ chai, trong khi tác vụ fuzzing có thể được tự động hóa trong CI theo một khoảng thời gian hoặc số lần thực thi nhất định. Tuy nhiên, nếu chỉ đưa việc chạy tràn lan vào pipeline thì việc phân loại lỗi và tái hiện môi trường sẽ khó khăn, nên cần định nghĩa đồng thời cổng chất lượng (quality gate) và hạn mức tài nguyên.

2. Các thành phần và nguyên lý hoạt động của kiểm thử fuzzing

Hệ thống fuzzing không chỉ gồm bản thân fuzzer. Test harness (bộ khung kiểm thử), corpus đầu vào, engine biến đổi/sinh, bộ đo đạc (instrumentation), oracle và kho lưu kết quả cùng hoạt động. Chỉ cần một thành phần yếu kém thì dù số lần thực thi tăng lên, khả năng phát hiện khiếm khuyết cũng không cao hơn.

flowchart LR
    C[Corpus ban đầu\nĐầu vào hợp lệ·biên] --> G[Engine sinh·biến đổi đầu vào]
    G --> H[Test harness]
    H --> T[Đối tượng kiểm thử\nParser·API·Giao thức]
    T --> O[Oracle·Bộ đo đạc\nCrash·Ngoại lệ·Độ bao phủ]
    O -->|Đường mới·Đầu vào có ý nghĩa| C
    O --> R[Kho lưu lỗi]
    R --> M[Tối thiểu hóa·Tái hiện·Phân loại]
    M --> F[Sửa·Kiểm thử hồi quy]
    F --> C

A. Test harness

Harness là một bộ chuyển đổi (adapter) mỏng nối chuỗi byte hoặc giá trị có cấu trúc do fuzzer sinh ra với dạng lời gọi của đối tượng kiểm thử. Ví dụ, khi fuzzing một bộ giải mã ảnh, harness đọc byte đầu vào vào bộ đệm bộ nhớ rồi gọi hàm công khai của bộ giải mã; khi fuzzing API, nó chuyển yêu cầu đã sinh qua các bước xác thực, định tuyến và kiểm tra hợp lệ.

Harness phải nhỏ và có tính tất định (deterministic). Nếu mỗi lần đều khởi động toàn bộ máy chủ vận hành thực tế thì chi phí mỗi lần chạy sẽ lớn và kết quả thay đổi theo trạng thái hệ thống bên ngoài. Khi có thể, hãy ảo hóa việc lưu tệp, thời gian, số ngẫu nhiên và mạng, đồng thời gọi nhanh logic phân tích cú pháp và kiểm tra hợp lệ cốt lõi của đối tượng ngay trong tiến trình.

Nếu harness từ chối đầu vào quá sớm, fuzzer sẽ không đến được phần mã sâu. Ngược lại, nếu loại bỏ mọi bước kiểm tra, ta sẽ kiểm thử một hành vi khác với đường vận hành thực. Do đó cần cân bằng: giữ nguyên ranh giới định dạng của đầu vào nhưng thay các phụ thuộc bên ngoài bằng đối tượng giả (fake) hoặc fixture cố định để đạt tới các trạng thái sâu.

Harness tốt khiến mỗi đầu vào biểu diễn một phép kiểm thử rõ ràng. Khi gộp nhiều phép kiểm thử vào một tiến trình, cần khởi tạo lại trạng thái toàn cục và bộ nhớ đệm, đồng thời xác nhận kết quả của đầu vào trước không ảnh hưởng đến đầu vào sau. Nếu trạng thái còn sót lại, kết quả của cùng một đầu vào sẽ khác nhau và việc tái hiện khiếm khuyết trở nên khó khăn.

B. Corpus đầu vào và seed

Corpus là tập đầu vào được dùng khi bắt đầu fuzzing. Tệp ngắn được phân tích cú pháp bình thường, yêu cầu API có đầy đủ trường, gói tin có độ dài tối thiểu/tối đa, đầu vào gây lỗi từng phát hiện trong quá khứ đều có thể làm seed (hạt giống). Chất lượng seed ảnh hưởng đến tốc độ fuzzer đạt tới các trạng thái nội bộ hợp lệ.

Với corpus, tính đa dạng quan trọng hơn số lượng. Nếu chỉ có nhiều đầu vào gần giống nhau thì lãng phí dung lượng lưu trữ và thời gian thực thi. Cần giảm trùng lặp dựa trên độ bao phủ hoặc đặc trưng cấu trúc, và giữ lại các đầu vào đại diện cho các phiên bản, mã hóa ký tự, độ sâu lồng nhau và trường tùy chọn khác nhau.

Khi dùng đầu vào thu thập từ môi trường vận hành làm seed, phải loại bỏ thông tin cá nhân, token xác thực và định danh khách hàng. Nếu đưa nguyên nhật ký gốc vào corpus, kho kiểm thử sẽ trở thành nơi lưu trữ thông tin cá nhân mới. Cần đưa việc phi định danh hóa, quyền truy cập, thời hạn lưu giữ và thủ tục hủy khi bị rò rỉ vào quy tắc quản lý corpus.

C. Biến đổi và sinh

Fuzzing dựa trên biến đổi (mutation-based) thay đổi một phần của đầu vào vốn đã hợp lệ. Các phép toán như lật bit, chèn/xóa byte, thay thế giá trị biên, đổi thứ tự token, làm sai trường độ dài vừa giữ lại phần nào cấu trúc sẵn có vừa kích thích các đường ngoại lệ. Ưu điểm là có thể áp dụng nhanh ngay cả với fuzzer không biết cú pháp của định dạng tệp và giao thức.

Fuzzing dựa trên sinh (generation-based) tạo đầu vào từ đầu bằng văn phạm (grammar) hoặc lược đồ. Sử dụng JSON Schema, ASN.1, văn phạm SQL, định nghĩa trạng thái giao thức cho phép tạo ra nhiều tổ hợp hợp lệ. Chi phí triển khai cao nhưng có thể xử lý tinh vi cấu trúc lồng nhau và chuyển trạng thái, đồng thời dễ phản ánh ngữ nghĩa của một miền nghiệp vụ cụ thể.

Trong thực tế, hai phương pháp được kết hợp. Dùng văn phạm để tạo thông điệp cơ bản hợp lệ, rồi áp dụng các biến đổi như giá trị biên, token bất thường, vi phạm thứ tự lên kết quả đó. Ví dụ, tạo yêu cầu bằng lược đồ của API đặt hàng, sau đó đổi số lượng thành 0, số âm, số nguyên tối đa, và làm lệch tổ hợp giữa tiền tệ và số tiền.

D. Oracle và dấu hiệu bất thường

Oracle là tiêu chí phán định đầu vào có làm lộ khiếm khuyết hay không. Oracle đơn giản nhất là phát hiện sập tiến trình, kết thúc bất thường, quá thời gian và lỗi truy cập bộ nhớ. Nếu kết hợp các công cụ phân tích động kiểm tra địa chỉ, bộ nhớ và hành vi không xác định (undefined behavior), có thể bắt được lỗi ngay cả ở những lần chạy bề ngoài có vẻ thành công.

Oracle chức năng cũng quan trọng. Dù chương trình không sập, nếu kết quả phân tích cú pháp khác nhau giữa các đầu vào tương đương về cú pháp, nếu tổng số tiền không được bảo toàn, hoặc nếu cho phép chuyển trạng thái khi không có quyền thì đó vẫn là khiếm khuyết. Biểu diễn các bất biến (invariant) như vậy thành mã sẽ giúp phát hiện vi phạm bảo mật và quy tắc nghiệp vụ.

Cần quản lý đồng thời độ nhạy và độ đặc hiệu của oracle. Quá nhạy thì các cảnh báo bình thường sẽ chồng chất thành hàng nghìn lỗi; quá kém nhạy thì khiếm khuyết thật lại được xử lý như thành công. Hãy ghi lại loại lỗi, stack trace, giá trị băm của đầu vào, môi trường thực thi và phiên bản đối tượng để gom các lỗi trùng lặp có cùng nguyên nhân.

E. Khám phá dẫn hướng bởi độ bao phủ

Fuzzing dẫn hướng bởi độ bao phủ (coverage-guided fuzzing) sau khi chạy một đầu vào sẽ dùng thông tin về đường đi, khối cơ bản (basic block) và nhánh mới được viếng thăm để xác định thứ tự ưu tiên cho đầu vào tiếp theo. Đầu vào mở ra vùng mới được giữ lại trong corpus, còn đầu vào ít có khả năng dẫn tới mã sâu hơn sẽ bị giảm xác suất được chọn.

Độ bao phủ là tín hiệu chỉ hướng khám phá chứ không phải bằng chứng đầy đủ về chất lượng. Dù độ bao phủ nhánh cao, vẫn có thể không thực thi được các khiếm khuyết ngữ nghĩa như vượt qua xác thực hay bảo toàn số tiền. Vì vậy cần kết hợp độ bao phủ cấu trúc, phát hiện lỗi, kiểm tra bất biến và các kịch bản dựa trên yêu cầu.

3. Quy trình thực hiện fuzzing

sequenceDiagram
    participant E as Kỹ sư
    participant P as Pipeline fuzzing
    participant S as Kho seed·corpus
    participant T as Đối tượng kiểm thử
    participant A as Hệ thống phân tích·issue
    E->>P: Xác định phạm vi·mức rủi ro·ngân sách thời gian
    E->>P: Đăng ký harness·oracle·corpus ban đầu
    P->>S: Chọn seed·sinh đầu vào biến đổi
    P->>T: Thực thi lặp và đo đạc
    T-->>P: Độ bao phủ·log·kết quả thực thi
    P->>S: Lưu đầu vào có đường mới
    P->>A: Báo cáo crash·lỗi·timeout
    A-->>E: Tối thiểu hóa·tái hiện·phân loại mức nghiêm trọng
    E->>T: Kiểm chứng hồi quy bản build đã sửa
    T-->>P: Tình trạng tái phát và kết quả hiệu năng

A. Thiết lập phạm vi dựa trên đối tượng và rủi ro

Trước hết, lập danh sách các thành phần nhận đầu vào từ bên ngoài và các đường có tác động nghiệp vụ lớn. Parser giao thức phơi ra Internet có bề mặt tấn công rộng, còn các mô-đun thanh toán, xác thực, chuyển đổi thông tin cá nhân có mức ảnh hưởng của khiếm khuyết lớn. Lấy hai nhóm này làm đối tượng ưu tiên, đồng thời xác định rõ ranh giới cô lập có thể thực thi trong môi trường kiểm thử.

Khảo sát nhóm sở hữu, ngôn ngữ hỗ trợ, cách build, dịch vụ phụ thuộc và chi phí thực thi chấp nhận được của đối tượng. Với đối tượng có nguy cơ lỗi bộ nhớ cao như thư viện C/C++, có thể ưu tiên fuzzing trong tiến trình kết hợp với sanitizer. Với đối tượng Java, Go, Rust, Python, cần kiểm tra harness và phương thức đo đạc riêng cho từng ngôn ngữ cũng như đặc tính xử lý ngoại lệ.

Thứ tự ưu tiên dựa trên rủi ro không chỉ được quyết định bởi khả năng bị tấn công. Cần đánh giá cùng với độ nhạy cảm của tài sản, gián đoạn nghiệp vụ khi xảy ra sự cố, thời gian khôi phục, độ khó vá lỗi và mức độ phơi lộ ra bên ngoài. Kết quả được lưu thành kế hoạch fuzzing bao gồm đối tượng, loại đầu vào, thời gian kiểm thử, người phụ trách và điều kiện dừng.

B. Thiết kế harness và đo đường cơ sở

Sau khi tạo harness, cho các đầu vào hợp lệ và đầu vào biên đã biết đi qua để xác nhận hành vi cơ bản của đối tượng. Nếu ở bước này không phân biệt được lỗi của chính harness với khiếm khuyết của đối tượng, các kết quả sau đó sẽ bị nhiễu. Đo thời gian xử lý mỗi đầu vào, lượng bộ nhớ sử dụng, việc phát sinh ngoại lệ và độ bao phủ ban đầu làm đường cơ sở (baseline).

Nếu độ bao phủ ban đầu quá thấp, có khả năng đầu vào đang bị từ chối ngay tại cửa vào của parser. Ngược lại, nếu mọi đầu vào chỉ đi qua cùng một đường thì cần tăng tính đa dạng của seed hoặc bổ sung thông tin văn phạm. Với harness, việc tái hiện trung thực đường vận hành thực tế của đối tượng được ưu tiên hơn hiệu năng của fuzzer.

Harness gọi API bên ngoài phải ngăn chặn việc bùng nổ yêu cầu và phát sinh chi phí. Không kết nối trực tiếp với API thanh toán, gửi tin nhắn hay xóa dữ liệu thật mà dùng sandbox và phản hồi ảo. Fuzzing mạng được thực hiện trên mạng cô lập riêng, đồng thời giới hạn nguồn phát và tốc độ của lưu lượng được sinh ra.

C. Thực thi và ngân sách tài nguyên

Cách thực thi có thể chia thành phiên ngắn trên máy cục bộ của lập trình viên, phiên dài ban đêm và phiên theo ảnh hưởng thay đổi trong CI. Phiên cục bộ dùng để phát triển harness và tái hiện nhanh, phiên ban đêm tìm các đường chạy lâu và trạng thái hiếm gặp. Phiên CI không khám phá vô hạn mọi commit mà nhanh chóng kiểm chứng lại các mô-đun thay đổi và đầu vào từng gây lỗi trong quá khứ.

Ngân sách thời gian được quản lý theo số lần thực thi hữu hiệu và thông lượng xử lý của đối tượng, chứ không chỉ theo số lần chạy đơn thuần. Nếu thời gian xử lý một đầu vào kéo dài, đưa đầu vào quá thời gian sang hàng đợi riêng để phân tích nguyên nhân. Đặt giới hạn trên cho bộ nhớ, CPU, đĩa và lập kế hoạch dung lượng lưu giữ cho corpus và tệp crash.

Tăng số tiến trình chạy đồng thời có thể giúp phát hiện khiếm khuyết nhanh hơn, nhưng nếu tranh chấp tệp dùng chung, cổng hay trạng thái cơ sở dữ liệu thì kết quả sẽ không ổn định. Hãy dùng thư mục làm việc và cổng độc lập, fixture chỉ đọc, đồng thời cố định phiên bản và cấu hình của môi trường thực thi.

D. Tối thiểu hóa và tái hiện lỗi

Đầu vào do fuzzer tìm ra có thể dài hàng nghìn byte hoặc là thông điệp phức tạp. Tối thiểu hóa (minimization) là quá trình lặp đi lặp lại tìm đầu vào nhỏ hơn gây ra cùng một lỗi. Đầu vào càng nhỏ thì lập trình viên càng dễ đọc hiểu nguyên nhân, đồng thời chi phí đưa vào kiểm thử hồi quy và dung lượng lưu trữ cũng giảm.

Tiêu chí thành công của tối thiểu hóa không phải là kích thước tệp mà là việc bảo toàn loại lỗi và điều kiện tái hiện. Nếu crash biến thành ngoại lệ đơn thuần hoặc timeout biến mất thì đã rút gọn quá mức. Cần so sánh đồng thời mã lỗi, stack, báo cáo sanitizer, giá trị trả về và khoảng thời gian thực thi.

Việc tái hiện cần phiên bản binary, phiên bản thư viện, hệ điều hành/kiến trúc, biến môi trường và seed ngẫu nhiên. Lưu kèm container image hoặc script tái hiện giúp nhóm phụ trách xác nhận khiếm khuyết trong cùng điều kiện. Không loại bỏ các báo cáo không tái hiện được mà theo dõi nguyên nhân gây tính bất định như một khiếm khuyết riêng.

4. So sánh các loại hình và kỹ thuật liên quan

Fuzzing được phân loại theo nhiều cách tùy vào góc độ tạo đầu vào, góc độ quan sát chương trình và lượng tri thức có được. Các cách phân loại không loại trừ nhau nên có thể mô tả kết hợp như “fuzzing hộp xám dẫn hướng bởi độ bao phủ”.

Tiêu chí phân loại Loại hình tiêu biểu Đặc điểm cốt lõi Tình huống phù hợp
Sinh đầu vào Dựa trên biến đổi Biến đổi seed hợp lệ Có định dạng tệp·thông điệp và seed phong phú
Sinh đầu vào Dựa trên sinh Tạo đầu vào mới bằng văn phạm·lược đồ Khám phá văn phạm và chuyển trạng thái phức tạp
Tri thức nội bộ Hộp đen Hầu như không biết cấu trúc bên trong Kiểm thử sản phẩm đóng·giao diện từ xa
Tri thức nội bộ Hộp xám Tận dụng tín hiệu độ bao phủ·trạng thái Fuzzing CI·thư viện thông thường
Tri thức nội bộ Hộp trắng Tận dụng ràng buộc đường đi và cấu trúc mã Phân tích nhánh cụ thể·đường khó đạt tới
Tín hiệu khám phá Dẫn hướng bởi độ bao phủ Ưu tiên đầu vào mở đường mới Tự động khám phá không gian mã rộng
Mục đích Fuzzing bảo mật Tập trung vào lỗ hổng·lỗi bộ nhớ Kiểm chứng bề mặt tấn công và kiểm tra đầu vào
Mục đích Fuzzing chức năng Tập trung vào vi phạm bất biến·hợp đồng Kiểm chứng quy tắc miền và chuyển trạng thái

Fuzzing hộp đen có thể bắt đầu gần như không cần tri thức trước về đối tượng kiểm thử. Vì gửi đầu vào theo giao diện bên ngoài thực tế của sản phẩm nên thích hợp để xác nhận bề mặt tấn công thực tế, nhưng hiệu quả đạt tới các nhánh sâu có thể thấp. Nó hữu ích cho khám phá ban đầu và đánh giá sản phẩm bên ngoài, song nếu thiếu đo đạc để giải thích trạng thái nội bộ thì việc phân tích nguyên nhân lỗi sẽ khó khăn.

Fuzzing hộp trắng sử dụng thông tin nội bộ như mã nguồn, luồng điều khiển và các biểu thức ràng buộc. Nó có thể tính toán các nhánh chỉ đi vào khi thỏa điều kiện cụ thể để mở rộng đường đi, nhưng chi phí phân tích và độ phức tạp triển khai tăng lên. Với dịch vụ quy mô lớn có mã thay đổi thường xuyên, áp dụng giới hạn cho các hàm rủi ro cao thực tế hơn là phân tích chính xác mọi đường đi.

Phương thức hộp xám tận dụng những tín hiệu nội bộ có giới hạn là độ bao phủ và kết quả thực thi. Không cần diễn giải ngữ nghĩa toàn bộ mã nguồn mà vẫn giữ lại đầu vào phát hiện đường mới, nên cân bằng tốt giữa hiệu năng và khả năng áp dụng. Nó được dùng rộng rãi trong các pipeline fuzzing tự động hiện đại, nhưng bất biến ngữ nghĩa và mô hình trạng thái phải được cung cấp riêng.

Hạng mục so sánh Kiểm thử fuzzing Kiểm thử chức năng thông thường Phân tích tĩnh Kiểm thử xâm nhập
Cách thực hiện Tự động thực thi lặp với đầu vào bất thường Thực thi kịch bản đã định nghĩa Kiểm tra tĩnh mã nguồn·binary Kiểm chứng thủ công·bằng công cụ theo góc nhìn kẻ tấn công
Điểm mạnh Khám phá đầu vào ngoại lệ·biên·hiếm gặp Kiểm chứng yêu cầu và luồng người dùng Phát hiện sớm và kiểm tra mã trên diện rộng Xác nhận đường tấn công thực tế và tác động
Điểm yếu Cần thiết kế oracle·harness Không gian đầu vào có thể bị giới hạn Hạn chế phản ánh trạng thái thực thi và phụ thuộc môi trường Ràng buộc về chi phí·phạm vi·khả năng tái hiện
Sản phẩm chính Đầu vào tái hiện·độ bao phủ·báo cáo lỗi Kịch bản đạt·không đạt Danh sách cảnh báo·khiếm khuyết tiềm ẩn Lỗ hổng·bằng chứng tấn công·khuyến nghị cải thiện

Bốn kỹ thuật không phải là sản phẩm thay thế mà là bổ sung cho nhau. Phân tích tĩnh thu hẹp các ứng viên hàm nguy hiểm và thiếu kiểm tra đầu vào, kiểm thử chức năng xác nhận hợp đồng bình thường, fuzzing khám phá các tổ hợp bất thường, sau đó kiểm thử xâm nhập kiểm chứng đường tấn công thực tế nơi nhiều lỗ hổng kết hợp với nhau. Không cần cố định thứ tự này nhưng kết quả phải liên kết với nhau.

5. Tình huống áp dụng

A. Tình huống parser ảnh·tài liệu

Giả sử một dịch vụ tải lên tài liệu chuyển đổi tệp PDF và hình ảnh. Đối tượng kiểm thử diễn giải header tệp, trường độ dài, luồng nén, bảng màu và các đối tượng lồng nhau. Corpus ban đầu gồm các tệp hợp lệ và tệp tối thiểu của từng định dạng, còn engine biến đổi được cấu hình để thay đổi độ dài, offset và dữ liệu nén.

Harness đọc tệp từ bộ nhớ rồi gọi thư viện chuyển đổi, và chỉ ghi tệp đầu ra vào thư mục tạm được cô lập. Dùng đồng thời bộ phát hiện lỗi bộ nhớ và giám sát timeout để quan sát không chỉ crash mà còn các dấu hiệu đệ quy quá mức và bom nén (compression bomb). Không kết nối kho lưu trữ bên ngoài hay thông báo khách hàng để đầu vào kiểm thử không lan sang dữ liệu nghiệp vụ.

Giả sử sau khi tối thiểu hóa đầu vào gây crash, lỗi chỉ tái hiện khi trường độ dài của một đối tượng cụ thể lớn hơn bộ đệm thực tế. Lập trình viên bổ sung kiểm tra độ dài và phòng chống tràn số nguyên, rồi đưa đầu vào tối thiểu vào kiểm thử hồi quy. Sau khi sửa, thực hiện lại cùng quy trình fuzzing để xác nhận crash đã biến mất và các đường parser khác vẫn đang được khám phá.

B. Tình huống fuzzing dựa trên ngữ nghĩa cho API thanh toán

API thanh toán không an toàn chỉ vì đúng cú pháp JSON. Điều quan trọng là số tiền đơn hàng có âm không, tiền tệ và số chữ số thập phân có khớp không, có phát sinh phê duyệt trùng khi khóa idempotency bị tái sử dụng không, và chuyển trạng thái phê duyệt/hủy có đúng không. Vì vậy cần thiết kế đồng thời việc sinh dựa trên lược đồ và các bất biến của miền nghiệp vụ.

Ví dụ, đổi số lượng trong một yêu cầu thành số nguyên tối đa, đặt tỷ lệ chiết khấu thành số âm, và xáo trộn thứ tự các yêu cầu phê duyệt và hủy. Oracle kiểm tra xem mức giảm số dư có vượt quá tổng số tiền thanh toán hay không, kết quả cuối cùng của cùng một khóa idempotency có nhất quán không, và người dùng không có quyền có thể thay đổi đơn hàng của khách hàng khác hay không.

Kiểm thử bắt buộc phải dùng sandbox thanh toán và tiền ảo. Đầu vào gây lỗi được ghi bằng định danh tổng hợp thay vì dữ liệu gốc của khách hàng, đồng thời chặn kết nối với số thẻ thật, token và API chuyển tiền. Tình huống này cho thấy fuzzing có thể tìm ra không chỉ lỗ hổng bảo mật mà cả khiếm khuyết về quy tắc nghiệp vụ và trạng thái phân tán.

C. Tình huống giao thức mạng

Giả sử một IoT gateway xử lý các thông điệp đăng ký thiết bị, xác thực và báo cáo trạng thái. Giao thức có loại thông điệp, độ dài, số thứ tự, thẻ xác thực và trường tùy chọn, và một số thông điệp đòi hỏi trạng thái trước đó. Đầu vào byte ngẫu nhiên đơn thuần có thể bị từ chối hết ngay tại cửa vào, nên cần cung cấp kèm mô hình trạng thái và chuỗi thông điệp hợp lệ.

Fuzzer gửi báo cáo trạng thái sau khi đăng ký bình thường, bỏ qua số thứ tự, thay đổi thẻ xác thực hoặc lặp lại thông điệp quá nhanh. Oracle quan sát crash tiến trình, vượt qua xác thực, cạn kiệt tài nguyên phiên và thất bại trong chống phát lại (replay). Không dùng bộ chấp hành (actuator) thật kết nối với thiết bị mà thay bằng trình mô phỏng thiết bị ảo.

Nếu trong quá trình fuzzing dài phát hiện vấn đề một chuỗi bất thường cụ thể không thu hồi bộ nhớ phiên, đó có thể là khiếm khuyết vòng đời trạng thái chứ không phải lỗi của một thông điệp đơn lẻ. Khi đó cần thực hiện tối thiểu hóa chuỗi thông điệp bên cạnh tối thiểu hóa đầu vào đơn thuần. Sau khi sửa, bổ sung thêm các kịch bản hồi quy thay đổi phiên bản firmware thiết bị và độ trễ mạng.

6. Tự động hóa và quản lý chất lượng

Khi đưa fuzzing vào DevSecOps, cần phân biệt mục đích của các giai đoạn: máy cục bộ của lập trình viên, trước khi merge, ban đêm và trước khi phát hành. Trước khi merge, chạy kiểm thử ngắn tập trung vào tái hiện và hồi quy; ban đêm thực hiện khám phá dài và mở rộng corpus. Trước khi phát hành, phê duyệt riêng chiến dịch dựa trên rủi ro cho các parser đã thay đổi và giao diện bên ngoài.

Kết quả CI không nên chỉ cho thấy “đã chạy bao nhiêu lần”. Cần hiển thị đồng thời độ bao phủ mới, số lỗi duy nhất, số đầu vào mới, thông lượng trung bình, số lần timeout, mức nghiêm trọng và trạng thái tái hiện của các lỗi chưa giải quyết. Nếu độ bao phủ giảm hoặc phát sinh lỗi rủi ro cao mới, dừng cổng chất lượng và tự động phân công cho nhóm phụ trách.

Nếu coi mọi lỗi là build thất bại, fuzzing có thể bị tắt vì cảnh báo sai. Ngược lại, nếu chỉ để mọi lỗi dưới dạng cảnh báo thì lỗ hổng sẽ lẫn vào bản phát hành. Cần phân biệt bằng chính sách giữa các loại phải chặn ngay như vi phạm an toàn bộ nhớ, vượt qua xác thực, hỏng dữ liệu, với các loại cần điều tra rồi mới quyết định như thiếu khả năng tái hiện, cảnh báo hiệu năng.

Thành quả của chiến dịch fuzzing không chỉ được đánh giá bằng số khiếm khuyết phát hiện. Cần xem xét đồng thời tỷ lệ đạt tới mã mục tiêu của harness, thời gian tái hiện lỗi, thời gian sửa trung bình, tỷ lệ chuyển thành kiểm thử hồi quy và xu hướng lỗi rủi ro cao chưa giải quyết. Nếu tìm được nhiều khiếm khuyết nhưng người phụ trách không tái hiện được thì hệ thống vận hành cần được cải thiện.

7. Chuyên sâu: Fuzzing hiện đại và liên kết với vòng đời phát triển

Fuzzing gần đây đang phát triển theo hướng được vận hành như một phần của chuỗi cung ứng phần mềm và pipeline chất lượng hơn là một công cụ bảo mật độc lập. Kết hợp fuzzing liên tục cho thư viện mã nguồn mở, chia sẻ corpus tự động, báo cáo lỗi dựa trên sanitizer và kiểm chứng hồi quy theo từng commit giúp nhanh chóng xác nhận một thay đổi mới có làm tổn hại tính an toàn sẵn có hay không.

Các dịch vụ fuzzing quy mô lớn dành cho dự án mã nguồn mở cho thấy mô hình cung cấp tài nguyên thực thi dài hạn cho nhiều dự án và chuyển khiếm khuyết phát hiện được đến người bảo trì dự án. Tổ chức không nên tin nguyên vẹn các kết quả công khai này mà cần xác nhận chúng khớp với phiên bản, tùy chọn build và nền tảng đang sử dụng, đồng thời duy trì riêng harness và chính sách bảo mật của mình.

Khi tận dụng AI tạo sinh để sinh đầu vào, thủ tục kiểm chứng vẫn cần thiết. Mô hình có thể đề xuất văn phạm phức tạp và kịch bản có ý nghĩa, nhưng chi phí sinh cao, nhiều đầu vào trùng lặp/vô hiệu và kết quả có thể bất định. Cần một cổng nhiều bước để đưa đầu vào do AI tạo vào corpus sau khi qua kiểm tra văn phạm, thực thi an toàn, đánh giá độ bao phủ và lọc thông tin cá nhân.

Liên kết fuzzing với SBOM và quản lý lỗ hổng cũng quan trọng. Phải liên kết thông tin thành phần để biết khiếm khuyết phát hiện được ảnh hưởng đến thư viện và sản phẩm build nào thì mới xác định được phạm vi vá. Ngược lại, khi thư viện bên thứ ba được cập nhật, cần chạy lại harness và các crash trong quá khứ của thư viện đó để xác nhận có hồi quy hay không.

Trong bài thi Kỹ sư chuyên nghiệp (Professional Engineer), không nên chỉ viết fuzzing là “kiểm thử ngẫu nhiên”; cần trình bày như một hệ thống vận hành thống nhất bao gồm lựa chọn đối tượng dựa trên rủi ro, thiết kế harness và oracle, dẫn hướng bởi độ bao phủ, tối thiểu hóa lỗi, cổng chất lượng CI, thông tin cá nhân và mạng cô lập thì mới đạt độ hoàn thiện cao.

8. Những điểm cần cân nhắc và hàm ý

A. Tính đại diện của đối tượng kiểm thử

Cần xác nhận harness có đại diện đầy đủ cho đường vận hành thực tế hay không. Nếu chỉ fuzzing các đường vòng dành riêng cho kiểm thử nội bộ thì dù đạt độ bao phủ cao vẫn bỏ sót lỗ hổng vận hành. Ngược lại, kết nối nguyên vẹn toàn bộ hệ thống vận hành thì chi phí và tác dụng phụ lớn, nên cần thiết kế ranh giới kiểm thử cô lập giữ được kiểm tra đầu vào cốt lõi và chuyển trạng thái.

B. Ý nghĩa nghiệp vụ của oracle

Phát hiện crash chỉ là điểm khởi đầu của fuzzing. Phải biểu diễn các bất biến nghiệp vụ như xác thực, phân quyền, số tiền, chuyển trạng thái, bảo toàn dữ liệu thành oracle thì mới tìm được lỗ hổng logic. Nếu vì khó xây dựng oracle mà định nghĩa “tiến trình còn sống là thành công” thì có thể bỏ sót những trường hợp hỏng dữ liệu âm thầm.

C. Khả năng tái hiện và quản lý bằng chứng

Cần lưu giữ cùng lúc đầu vào phát hiện, lệnh thực thi, định danh build, môi trường, seed ngẫu nhiên và báo cáo stack. Khi xảy ra sự cố bảo mật hoặc kiểm toán, phải giải thích được phiên bản nào đã vượt qua kiểm thử nào. Tuy nhiên, cần áp dụng che giấu tự động và kiểm soát truy cập để thông tin cá nhân và giá trị bí mật không lọt vào kho bằng chứng.

D. Kiểm soát tài nguyên·an toàn

Fuzzing cố ý kích thích các hành vi dễ tổn thương của đối tượng nên phải tách khỏi mạng vận hành. Giới hạn địa chỉ mạng, cổng và tốc độ yêu cầu, đồng thời cô lập hệ thống tệp và cơ sở dữ liệu trong môi trường dùng một lần. Đặt giới hạn trên cho tài nguyên thực thi và dung lượng lưu trữ để bản thân fuzzing không trở thành nguyên nhân gây sự cố cho hạ tầng phát triển.

E. Ưu tiên hóa kết quả

Nhiều lỗi không có nghĩa là tất cả đều có cùng mức rủi ro. Mức nghiêm trọng được xác định tổng hợp từ khả năng thực thi từ xa, yêu cầu xác thực hay không, tác động dữ liệu, khả năng tái hiện, độ khó tấn công và mức phơi lộ bên ngoài. Gom nhiều đầu vào xuất phát từ cùng một nguyên nhân gốc thành một khiếm khuyết, nhưng xem xét riêng liệu các đường thực thi khác nhau có tạo thêm rủi ro hay không.

F. Liên kết với tổ chức và quy trình

Fuzzing không phải là chiến dịch một lần chỉ do nhóm bảo mật thực hiện mà phải là hoạt động chất lượng được chia sẻ giữa phát triển, kiểm thử và vận hành. Cần xác định chủ sở hữu harness, người phụ trách triage lỗ hổng, SLA sửa lỗi, người phê duyệt ngoại lệ và trách nhiệm kiểm chứng lại, đồng thời gắn kết quả phát hiện với backlog và quyết định phát hành. Đưa đầu vào tối thiểu đã phát hiện vào kiểm thử hồi quy sẽ tạo ra hiệu quả bền vững của chiến dịch.

G. Chi phí áp dụng và đánh đổi

Áp dụng cùng một mức fuzzing dài hạn cho mọi mô-đun là không hiệu quả. Hãy bắt đầu từ mô-đun nhận đầu vào bên ngoài và có mức thiệt hại lớn, rồi phân bổ lại tài nguyên dựa trên khả năng tái sử dụng harness và lịch sử phát hiện lỗi. Giữa số lần thực thi, độ bao phủ và số khiếm khuyết không có quan hệ tuyến tính đơn giản, nên cần định kỳ đánh giá mức giảm rủi ro so với chi phí.

Tài liệu tham khảo


Tóm tắt một câu: Kiểm thử fuzzing là chiến lược kiểm thử động dựa trên rủi ro, kết hợp harness, sinh đầu vào, oracle, độ bao phủ và quy trình tái hiện để liên tục tìm ra khiếm khuyết bảo mật và nghiệp vụ từ những đầu vào bất thường mà con người khó tự tạo ra.