Hỏi & Đáp#
1. Lỗ hổng Race Condition là gì, và chúng khác biệt như thế nào so với các loại lỗ hổng khác trong ứng dụng web? Giải thích khái niệm race condition, bao gồm cách các tiến trình (processes) hoặc luồng (threads) chạy đồng thời có thể dẫn đến các trạng thái không an toàn hoặc không mong muốn.#
Race condition (Điều kiện tương tranh) là một lỗ hổng xảy ra khi quyết định bảo mật của ứng dụng phụ thuộc vào thời gian hoặc thứ tự thực thi của nhiều thao tác chạy đồng thời (concurrently). Trong một thiết kế an toàn, một thao tác nhạy cảm phải kiểm tra điều kiện và cập nhật trạng thái liên quan như một hành động nguyên tử (atomic action - không thể chia nhỏ). Ở một thiết kế có lỗ hổng, sẽ có một khoảng thời gian ngắn (time window) giữa bước kiểm tra và bước cập nhật. Nếu hai hoặc nhiều request bước vào khoảng thời gian này gần như cùng lúc, tất cả chúng đều có thể vượt qua bước kiểm tra trước khi ứng dụng kịp ghi nhận rằng hành động này đã được thực hiện.
Race condition khác với nhiều lỗ hổng web khác vì input độc hại trông có vẻ hoàn toàn hợp lệ. Ví dụ, SQL Injection thường dựa vào việc phá vỡ cấu trúc truy vấn database, còn Cross-Site Scripting (XSS) dựa vào việc chèn mã thực thi ở client-side. Thay vào đó, race condition lạm dụng lỗ hổng trong luồng xử lý (workflow), quản lý trạng thái, cơ chế khóa (locking), hoặc quá trình xử lý giao dịch (transaction) của ứng dụng. Bản chất của cuộc tấn công không nằm ở một payload đặc biệt; nó nằm ở việc gửi các request hợp lệ vào đúng thời điểm để server xử lý chúng theo một thứ tự không an toàn.
Hầu hết các ứng dụng web đều phải xử lý nhiều request cùng lúc. Sự đồng thời này có thể liên quan đến nhiều worker threads, các tác vụ bất đồng bộ (asynchronous tasks), giao dịch cơ sở dữ liệu, hoặc các dịch vụ phân tán. Nếu ứng dụng sử dụng trạng thái chia sẻ (shared state) như số dư tài khoản, lượt dùng mã giảm giá, số lượng hàng tồn kho, trạng thái reset mật khẩu hay dữ liệu phiên (session data), mỗi request phải cập nhật trạng thái đó một cách an toàn.
Race condition xuất hiện khi hai thao tác cùng truy cập vào một trạng thái chia sẻ vào cùng một thời điểm và ít nhất một trong số đó có sửa đổi nó. Kết quả cuối cùng phụ thuộc vào thời gian (timing) thay vì một quy tắc bảo mật đáng tin cậy. Ví dụ, ứng dụng có thể thực hiện logic sau khi áp dụng mã giảm giá:
| |
Nếu các request được xử lý tuần tự (sequentially), logic này có vẻ an toàn. Tuy nhiên, nếu nhiều request áp mã gửi đến gần như cùng lúc, một vài request có thể hoàn thành xong bước xác thực trước khi có bất kỳ request nào kịp cập nhật trạng thái mã giảm giá. Tất cả chúng đều cho rằng mã chưa được dùng, dẫn đến việc cùng một mức giảm giá được áp dụng nhiều lần. Điều này tạo ra một trạng thái không an toàn, khiến tổng tiền giỏ hàng thấp hơn so với quy tắc nghiệp vụ dự kiến.
2. Mô tả quá trình khai thác lỗ hổng Race Conditions trong một ứng dụng hoặc hệ thống web. Kẻ tấn công có thể sử dụng các kỹ thuật nào để gây ra race condition, và những lỗ hổng này có thể dẫn đến vi phạm bảo mật hoặc sai lệch dữ liệu như thế nào?#
Một phương pháp luận thực tế để khai thác race condition bắt đầu bằng việc xác định một workflow có chứa các giới hạn về bảo mật hoặc nghiệp vụ. Các mục tiêu tiềm năng bao gồm: sử dụng mã giảm giá, cập nhật số dư tài khoản, quy trình thanh toán, quy trình reset mật khẩu, đăng ký tài khoản, thay đổi email, đổi thẻ quà tặng, rate limiting (giới hạn tỷ lệ), file upload, hoặc thay đổi đặc quyền.
Đầu tiên, người kiểm thử đo lường (benchmark) hành vi bình thường bằng cách gửi request tuần tự. Bước này giúp xác định cơ chế kiểm soát dự kiến. Ví dụ, áp mã giảm giá lần đầu sẽ thành công, lần thứ hai sẽ bị từ chối với thông báo Coupon already applied. Sau đó, tester gửi cùng một request đó nhiều lần song song (in parallel) để xem liệu có nhiều request cùng vượt qua bước kiểm tra trước khi trạng thái được cập nhật hay không.
Các kỹ thuật phổ biến để gây ra race condition bao gồm:
- Gửi các request trùng lặp song song bằng tính năng tab groups của Burp Repeater.
- Sử dụng HTTP/2 multiplexing và kỹ thuật tấn công gói tin đơn (single-packet attack) để giảm thiểu độ trễ mạng (network jitter).
- Sử dụng kỹ thuật đồng bộ byte cuối cùng (last-byte synchronization) cho các mục tiêu dùng HTTP/1.1.
- Sử dụng Turbo Intruder cho các đợt request (batches) lớn hơn hoặc phức tạp hơn.
- Lặp lại cuộc tấn công sau khi reset trạng thái ứng dụng, vì cửa sổ race (race windows) có thể rất nhỏ và không ổn định.
- Kiểm thử các workflow nhiều bước (multi-step), nơi hai endpoint khác nhau cùng sửa đổi một trạng thái liên quan vào cùng một lúc.
Race conditions có thể dẫn đến vi phạm bảo mật và các vấn đề về tính toàn vẹn dữ liệu. Tùy thuộc vào workflow bị ảnh hưởng, kẻ tấn công có thể bypass rate limits, dùng mã giảm giá hoặc thẻ quà tặng nhiều lần, mua sản phẩm với giá không tưởng, rút hoặc chuyển tiền nhiều lần, tạo ra các bản ghi không nhất quán, bypass kiểm tra phân quyền, hoặc chiếm đoạt các đặc quyền trái phép.
Khai thác Limit overrun race conditions#
Mục tiêu Lab#
Mục tiêu của bài lab PortSwigger Web Security Academy này là khai thác lỗ hổng limit overrun race condition (vượt quá giới hạn) trong luồng thanh toán mua hàng. Ứng dụng cung cấp mã giảm giá PROMO20, mã này chỉ được áp dụng giảm giá 20% duy nhất một lần. Bằng cách gửi song song nhiều request áp mã, tôi sẽ cố gắng ép ứng dụng áp dụng cùng một mã giảm giá nhiều lần để giảm giá của chiếc Lightweight “l33t” Leather Jacket xuống thấp hơn số dư (store credit) hiện có trong tài khoản.
Bước 1: Mở ứng dụng lab#
Tôi mở lab PortSwigger có tên Limit overrun race conditions. Trang chủ hiển thị một cửa hàng trực tuyến với thông báo mã khuyến mãi:
| |
Điều này xác nhận bề mặt tấn công của lab có khả năng nằm ở quy trình mua hàng và cơ chế mã giảm giá.
Bước 2: Mở sản phẩm mục tiêu#
Tiếp theo, tôi mở trang sản phẩm của chiếc Lightweight “l33t” Leather Jacket. Đây là mặt hàng bắt buộc để giải quyết lab. Giá của sản phẩm này cao hơn nhiều so với store credit hiện có, nên không thể mua theo cách thông thường.
Trang sản phẩm cho phép thêm mặt hàng vào giỏ. Vì mục tiêu của lab là mua sản phẩm đắt tiền này, tôi đã thêm một chiếc áo khoác vào giỏ hàng.
Bước 3: Kiểm tra giỏ hàng trước khi áp mã giảm giá#
Sau khi thêm áo khoác vào giỏ, tôi mở trang giỏ hàng. Giỏ hàng hiển thị giá áo là $1337.00, trong khi store credit khả dụng chỉ có $45.89.
Tại thời điểm này, không thể đặt hàng (place order) vì tổng số tiền cao hơn nhiều so với số dư tài khoản. Điều này xác nhận rằng tôi cần tìm cách giảm tổng giá trị giỏ hàng trước khi thanh toán.
Bước 4: Áp mã giảm giá một lần để hiểu hành vi bình thường#
Tôi nhập mã PROMO20 và nhấn Apply. Ứng dụng chấp nhận mã và áp dụng một lần giảm 20% cho giỏ hàng.
Sau khi áp mã thành công một lần, tổng tiền vẫn quá cao để mua được áo khoác. Điều này là dễ hiểu vì chỉ giảm 20% là không đủ. Quan sát quan trọng ở đây là tính năng mã giảm giá có thay đổi trạng thái giỏ hàng ở phía server (server-side), biến nó thành một mục tiêu sáng giá để kiểm thử race condition.
Bước 5: Bắt request áp mã bằng Burp Suite#
Tôi bật proxy Burp Suite và bắt lại request áp mã trong tab HTTP history. Request liên quan có dạng:
| |
Request chứa hai tham số ở body: một token csrf hợp lệ và giá trị coupon PROMO20. Vì request này thực hiện việc áp dụng giảm giá, tôi đã gửi nó sang Repeater để kiểm thử sâu hơn.
Bước 6: Đo lường (Benchmark) hành vi tuần tự bình thường#
Trước khi thử race condition, tôi test request theo cách bình thường. Khi gửi request một lần, server phản hồi thành công (ví dụ: Coupon applied). Khi gửi lại đúng mã đó sau khi nó đã được áp dụng, hành vi dự kiến của ứng dụng là từ chối việc áp mã trùng lặp.
Hành vi tuần tự này cho thấy ứng dụng có thiết lập giới hạn (limit). Tuy nhiên, nó không chứng minh được rằng giới hạn này được thực thi một cách nguyên tử (atomically). Bước tiếp theo là kiểm tra xem giới hạn này có thể bị vượt qua bằng các request đồng thời hay không.
Bước 7: Tạo một nhóm (group) trong Repeater với nhiều request giống hệt nhau#
Trong Burp Repeater, tôi tạo một nhóm gồm nhiều request POST /cart/coupon lặp lại. Mỗi tab sử dụng chung một session cookie, CSRF token và giá trị coupon.
Mục đích của việc tạo nhóm là để gửi nhiều request áp mã giống hệt nhau với khoảng thời gian sát nhau nhất có thể. Nếu ứng dụng kiểm tra trạng thái mã giảm giá và cập nhật trạng thái giỏ hàng bằng các thao tác không nguyên tử (non-atomic operations) riêng biệt, nhiều request có thể cùng lọt qua vòng kiểm tra mã chưa được áp dụng trước khi server kịp ghi nhận việc sử dụng mã.
Bước 8: Gửi các request trong nhóm theo kiểu song song (parallel)#
Sau khi chuẩn bị xong nhóm Repeater, tôi sử dụng tùy chọn send group và chọn chế độ gửi song song (parallel sending mode).
Kỹ thuật này cố gắng kích hoạt cửa sổ race (race window) bằng cách ép server xử lý đồng thời nhiều request áp mã. Trong lab này, tôi chọn tùy chọn gửi song song của Burp Repeater, cụ thể là chế độ tấn công gói tin đơn (single-packet attack mode). Kỹ thuật này giúp giảm thiểu network jitter và đảm bảo các request trong nhóm đến server gần như cùng một lúc.
Bước 9: Quan sát kết quả áp mã thành công nhiều lần#
Sau khi gửi nhóm song song, một vài request trả về phản hồi áp mã thành công. Điều này cho thấy nhiều request đã lọt vào khoảng thời gian chênh lệch (vulnerable window) trước khi ứng dụng kịp cập nhật trạng thái giỏ hàng trên server.
Việc có nhiều phản hồi thành công là rất quan trọng, vì một cơ chế bảo mật tốt chỉ được phép cho áp dụng một mã giảm giá trên mỗi giỏ hàng. Nhiều phản hồi thành công chứng tỏ quy tắc nghiệp vụ đã không được thực thi một cách nguyên tử.
Bước 10: Tải lại giỏ hàng và kiểm tra tổng tiền đã giảm#
Tôi tải lại trang giỏ hàng để xem liệu các request lặp lại có làm thay đổi tổng tiền hay không. Giỏ hàng cho thấy cùng một mã giảm giá đã được áp dụng rất nhiều lần, làm giảm đáng kể tổng giá trị đơn hàng.
Ở lần thử đầu tiên, tổng tiền đã giảm xuống gần bằng số dư hiện có nhưng vẫn còn hơi cao. Điều này chứng tỏ race condition đang hoạt động, nhưng tôi cần một đợt tấn công mạnh hơn hoặc lặp lại thao tác để kéo tổng tiền xuống thấp hơn store credit.
Bước 11: Lặp lại cuộc tấn công cho đến khi tổng tiền thấp hơn store credit#
Tôi xóa bớt mã giảm giá khỏi giỏ hàng (khi cần) và lặp lại việc gửi nhóm request song song. Sau một lần thử nghiệm thành công khác, việc giảm giá liên tục đã đẩy tổng tiền xuống còn $15.40, thấp hơn số dư $45.89 đang có.
Điều này xác nhận lỗ hổng limit overrun race condition có thể bị khai thác. Ứng dụng dự định chỉ cho dùng mã một lần, nhưng các request đồng thời đã khiến cùng một mức giảm giá được áp dụng nhiều lần.
Bước 12: Đặt hàng và hoàn thành lab#
Cuối cùng, tôi nhấn Place order khi tổng tiền đã thấp hơn store credit. Đơn hàng được chấp nhận và trạng thái lab chuyển thành Solved.
Xác nhận cuối cùng cho thấy chiếc áo khoác đắt tiền đã được mua với mức giá ngoài dự kiến. Bài lab hoàn thành.
Phân tích lỗ hổng (Vulnerability explanation)#
Nguyên nhân cốt lõi của lỗ hổng này là thao tác kiểm tra-và-cập nhật (check-and-update) không nguyên tử trong luồng áp mã giảm giá. Ứng dụng có kiểm tra xem mã đã được áp dụng hay chưa, nhưng nó không khóa (lock) trạng thái giỏ hàng hoặc thực hiện cả hai việc kiểm tra và cập nhật bên trong một giao dịch an toàn (safe transaction). Hậu quả là, nhiều request song song có thể cùng lúc vượt qua điều kiện xác thực trước khi ứng dụng kịp ghi nhận mã đã được sử dụng.
Dạng lỗi này được gọi là limit overrun race condition (race condition vượt quá giới hạn). Quy tắc nghiệp vụ quy định mã chỉ được áp dụng một lần, nhưng quá trình thực thi đồng thời cho phép vượt qua giới hạn này. Việc khai thác không yêu cầu phải bypass input validation, thay đổi giá trị mã giảm giá hay sửa đổi giá trực tiếp. Nó lạm dụng timing và sự đồng thời để khiến các request hợp lệ tạo ra một trạng thái cuối cùng không mong muốn.
Tác động bảo mật (Security impact)#
Tác động được chứng minh ở đây là việc giảm giá trái phép. Trong một ứng dụng thương mại điện tử thực tế, điều này có thể gây ra thiệt hại tài chính trực tiếp vì kẻ tấn công có thể mua sản phẩm với giá thấp hơn mức quy định. Các lỗi race condition tương tự cũng có thể ảnh hưởng đến thẻ quà tặng, hệ thống hoàn tiền (refund), điểm khách hàng thân thiết, giới hạn hàng tồn kho, giới hạn tỷ lệ (rate limits), cập nhật số dư tài khoản, hoặc các luồng nghiệp vụ quan trọng khác.
Các tác động tiềm ẩn bao gồm:
- Áp dụng các mã giảm giá dùng một lần thành nhiều lần.
- Đổi thẻ quà tặng hoặc store credit nhiều hơn một lần.
- Mua sản phẩm với mức giá ngoài ý muốn.
- Bypass rate limits hoặc giới hạn sử dụng (usage limits).
- Tạo ra các bản ghi không nhất quán trong giỏ hàng, thanh toán hoặc hàng tồn kho.
- Gây thiệt hại tài chính và các vấn đề về tính toàn vẹn dữ liệu.
Khuyến nghị khắc phục (Remediation recommendations)#
Để ngăn chặn lỗ hổng này, ứng dụng cần đảm bảo logic xác thực và áp dụng mã giảm giá phải mang tính nguyên tử (atomic). Server phải đảm bảo rằng tại một thời điểm, chỉ có một request duy nhất có thể áp mã vào giỏ hàng.
Các biện pháp phòng thủ được khuyến nghị bao gồm:
- Đóng gói thao tác kiểm tra và cập nhật mã giảm giá vào trong một database transaction duy nhất.
- Sử dụng khóa cấp hàng (row-level locking) hoặc cơ chế khóa server-side tương đương cho trạng thái giỏ hàng và mã giảm giá.
- Áp dụng ràng buộc cơ sở dữ liệu duy nhất (unique database constraint) để ngăn việc cùng một mã giảm giá bị áp dụng nhiều lần vào cùng một giỏ hàng.
- Tránh việc chỉ dựa vào các bước kiểm tra ở tầng ứng dụng (application-layer) mà thiếu đi lớp bảo vệ ở tầng cơ sở dữ liệu.
- Tính toán lại tổng tiền giỏ hàng trên server ngay trước khi thanh toán.
- Xác minh rằng tổng giá trị cuối cùng của đơn hàng nhất quán với tất cả các quy tắc về mã giảm giá và số dư tài khoản.
- Thiết kế các thao tác nhạy cảm theo hướng idempotent (có tính lũy đẳng) nếu có thể.
- Bổ sung cơ chế giám sát (monitoring) để phát hiện các trường hợp áp mã trùng lặp, mức giảm giá bất thường và các đợt request tăng đột biến.
Kết luận#
Bài lab này đã minh họa một lỗ hổng limit overrun race condition trong luồng xử lý mã giảm giá của một trang thương mại điện tử. Dưới các bài test tuần tự bình thường, mã giảm giá dường như đã bị giới hạn chỉ cho dùng một lần. Tuy nhiên, bằng cách bắt request POST /cart/coupon và gửi nhiều bản sao của nó song song qua Burp Repeater, tôi đã khiến server áp dụng cùng một mã giảm giá nhiều lần. Điều này giúp giảm giá của chiếc Lightweight “l33t” Leather Jacket xuống thấp hơn mức số dư khả dụng, cho phép đặt hàng thành công và giải quyết xong bài lab.