A rate limiting kapcsán a leggyakoribb tévhit, hogy az egyenletesen elosztott kérésekre van tervezve. Sokan azt hiszik, ha egy végpont korlátja 60 kérés/perc, akkor másodpercenként legfeljebb egyet küldhetnek. A valóság ennél sokkal árnyaltabb – és számunkra, támadók számára, sokkal érdekesebb. A token bucket algoritmus ugyanis megengedi, sőt, kifejezetten támogatja a rövid ideig tartó, intenzív kéréscsomagokat (burst). Ez a „tűrés” a rendszer Achillessarka.
A Token Vödör Anatómája: Több mint egy számláló
Ahelyett, hogy egyszerűen számolná az időegység alatti kéréseket, a token bucket egy állapot-alapú mechanizmus. Képzelj el egy vödröt, amibe egyenletes ütemben csöpögnek a tokenek (engedélyek). Minden egyes API kérés „elfogyaszt” egy tokent a vödörből. Ha a vödör üres, a kérés elutasításra kerül (jellemzően 429 Too Many Requests hibával), amíg újabb token nem érkezik.
A rendszert három kulcsparaméter határozza meg:
- Vödör mérete (
bucket_sizevagyburst): A vödör maximális kapacitása. Ez határozza meg, hogy mennyi token gyűlhet össze, és ennélfogva mekkora lehet a maximális, egyszerre elküldhető kéréscsomag. - Feltöltési ráta (
refill_rate): Az a sebesség, amellyel új tokenek kerülnek a vödörbe (pl. 1 token/másodperc). Ez definiálja a rendszer hosszú távú, fenntartható átlagos terhelhetőségét. - Kezdeti tokenek: A vödörben lévő tokenek száma az első kérés pillanatában. Ez általában megegyezik a vödör méretével.
A token vödör modell vizualizációja.
A Támadási Stratégia: A Kitörés (Burst) Kihasználása
A támadás lényege, hogy nem a feltöltési rátát, hanem a vödör méretét célozzuk. Ahelyett, hogy a kéréseket elosztanánk az időben, egyetlen, rendkívül rövid időpillanat alatt küldünk annyi kérést, amennyi a vödör kapacitása. Ezzel gyakorlatilag „lenullázzuk” a védelmet egy rövid időablakra.
Miért hatékony ez a megközelítés?
- Race condition kihasználása: Lehetőséget adhat két, normál esetben atomi művelet közé ékelődni.
- Erőforrás-kimerítés (DoS): Egy hirtelen terhelési csúcs megterhelheti a háttérrendszereket, adatbázis-kapcsolatokat, vagy akár az MI modellt, mielőtt az automatikus skálázás reagálna.
- Brute-force ablak: Egy jelszó-visszaállítási végponton a 100-as vödörméret lehetővé teszi 100 kód egyidejű kipróbálását, ahelyett, hogy percekig kellene próbálkozni.
- Logikai hibák triggerelése: Bizonyos rendszerek nincsenek felkészítve a párhuzamosan, szinte ezredmásodperces eltéréssel beérkező, azonos felhasználótól származó kérésekre, ami váratlan állapotokat eredményezhet.
Felderítés és Kivitelezés Lépésről Lépésre
A támadás két fázisból áll: a paraméterek felderítéséből és a tényleges burst végrehajtásából.
1. A Vödör Méretének (burst) Meghatározása
Küldj egy gyors, párhuzamosított kéréssorozatot a célvégpontra. Számold, hány kérés kap 200 OK (vagy más sikeres) választ, mielőtt az első 429 Too Many Requests megérkezik. Ez a szám nagy valószínűséggel a vödör mérete.
import asyncio
import httpx
TARGET_URL = "https://api.example.com/v1/process"
MAX_BURST_ATTEMPT = 50 # Próbálkozzunk egy ésszerűen magas számmal
async def send_request(client, i):
try:
response = await client.get(TARGET_URL)
print(f"Kérés #{i}: {response.status_code}")
return response.status_code
except httpx.RequestError as exc:
print(f"Hiba a kérésnél #{i}: {exc}")
return None
async def find_burst_limit():
async with httpx.AsyncClient() as client:
# Párhuzamos kérések indítása
tasks = [send_request(client, i) for i in range(MAX_BURST_ATTEMPT)]
results = await asyncio.gather(*tasks)
# Sikeres kérések számolása a 429-es hiba előtt
successful_requests = 0
for status in results:
if status == 429:
break
if status is not None and status < 400:
successful_requests += 1
print(f"\nFeltételezett vödör méret (burst limit): {successful_requests}")
# Futtatás
asyncio.run(find_burst_limit())
2. A Feltöltési Ráta (refill_rate) Becslése
Miután teljesen kiürítetted a vödröt (azaz folyamatos 429-es hibákat kapsz), várj egy meghatározott időt (pl. 1 másodpercet), majd küldj egyetlen kérést. Ha sikeres, a ráta legalább 1 token/másodperc. Ha nem, növeld a várakozási időt, amíg egy kérés át nem megy. Ez a módszer kevésbé precíz, de jó becslést ad a fenntartható kérésszámra.
| Paraméter | Felderítési Módszer | Cél |
|---|---|---|
| Vödör mérete (Burst) | Párhuzamos kérések küldése a 429-es hibáig. | A maximális egyszeri kéréscsomag méretének megállapítása. |
| Feltöltési ráta | Várakozás a vödör kiürítése után, majd szekvenciális próbálkozás. | A fenntartható, hosszú távú kérésszám meghatározása. |
Konklúzió: A Gát, Ami Átszakítható
A token bucket mechanizmus egy elegáns megoldás a szerverek védelmére, de a konfigurációja kritikus. Egy túl nagyra méretezett vödör (bucket_size) hamis biztonságérzetet ad. A fejlesztők a hosszú távú átlagos terhelésre (refill_rate) koncentrálnak, miközben a támadók a rövid távú, maximális kapacitást keresik.
A Red Teaming során a feladatunk pontosan az, hogy ne fogadjuk el a felszíni korlátokat. Meg kell értenünk a védelem mögötti algoritmust, és annak logikáját kell a saját céljainkra fordítani. A token vödör nem egy áthatolhatatlan fal, hanem egy gát. A gátakat pedig, megfelelő nyomással, át lehet szakítani.