Ha valaha kiraktál egy Express appot nginx vagy egy load balancer mögé, valószínűleg beírtad ezt az egy sort, és mentél is tovább:
app.set('trust proxy', true)
Onnantól a req.ip a valódi kliens címét mutatta a proxy loopback-címe helyett, a logok értelmesek lettek, a rate limiter kulcsa is stimmelt. Mindenki boldog. Csak épp ezzel az egy sorral a req.ip értékét átadtad annak, aki a kérést küldi — beleértve azt is, aki épp be akar törni. Ez a cikk arról szól, miért a true a lehető legrosszabb választás erre a beállításra, és mit írj helyette.

Mit csinál valójában a trust proxy
Amikor a klienstől közvetlenül érkezik a kérés, a req.ip egyszerű: az a TCP-kapcsolat másik végének címe, amit a kernel lát. Csakhogy egy reverse proxy mögött ez a cím mindig a proxyé lesz, sosem a felhasználóé. Az igazi kliens IP-jét a proxy egy fejlécbe teszi — a de facto szabvány erre az X-Forwarded-For (röviden XFF).
A trust proxy beállítás pontosan azt mondja meg az Expressnek, hogy mennyire higgyen ennek a fejlécnek. Alapból ki van kapcsolva (false): ilyenkor az Express egyszerűen eldobja az XFF-et, és a req.ip a socket címe marad. Ha bekapcsolod, az Express elkezdi a fejlécből olvasni a klienst. A kérdés csak az, hogy melyik bejegyzést hiszi el belőle.
Az XFF ugyanis egy lista. A szintaxisa kliens, proxy1, proxy2: a bal szélső a legkorábbi (az eredeti kliens), a jobb szélső a hozzád legközelebbi proxy. Ezt jó fejben tartani, mert innen ered az egész probléma.
A true a bal szélső bejegyzést hiszi el
Felraktam egy minimális Express 5-öt (v5.2.1, Node 22), és a req.ip meg a req.ips értékét kiírattam különböző trust proxy beállításokkal, ugyanazokra a kérésekre. A lényeg elfér néhány sorban:
trust proxy: false
X-Forwarded-For: 1.2.3.4 -> req.ip = ::ffff:127.0.0.1 (a fejlécet eldobja)
trust proxy: true
X-Forwarded-For: 1.2.3.4 -> req.ip = 1.2.3.4
X-Forwarded-For: 9.9.9.9, 10.0.0.1 -> req.ip = 9.9.9.9 (a BAL szélső!)
trust proxy: 1
X-Forwarded-For: 1.2.3.4 -> req.ip = 1.2.3.4 (nincs valódi proxy: spoofolható!)
X-Forwarded-For: 9.9.9.9, 10.0.0.1 -> req.ip = 10.0.0.1 (jobbról az első)
Nézd meg a középső blokkot. A true beállításnál a kérésemet közvetlenül a szerverhez küldtem (semmilyen proxy nem volt előtte), én meg egy sima curl -H-val ráírtam egy tetszőleges XFF-et — és az Express azt szolgálta ki req.ip-ként. A kételemű láncnál a 9.9.9.9-et választotta, ami a lista bal széle. Az Express dokumentációja ezt nem is titkolja: true esetén „a kliens IP-je a bal szélső bejegyzés az X-Forwarded-For fejlécben”. A bal szél viszont pontosan az a rész, amit a kliens ír. A true tehát nem azt jelenti, hogy „megbízom a proxyban”, hanem azt, hogy „megbízom mindenkiben, aki bármit ír ebbe a fejlécbe”.
És itt jön a rész, amit sokan félreértenek: a false ebből a szempontból biztonságosabb, mint a true. A false-nál a req.ip legalább a valódi socket-cím marad (proxy mögött ez a proxy IP-je — pontatlan, de nem hamisítható). A true-nál a req.ip teljes egészében a támadó kezében van. Aki „biztos, ami biztos” alapon kapcsolja be, épp az ellenkezőjét éri el.
Innen egy sor az egész rate limit megkerülése
Ez nem elméleti. A leggyakoribb IP-alapú védelem, a rate limiting, közvetlenül a req.ip-re épül. Fogtam az express-rate-limit-et (v8.6.2), beállítottam percenként 3 kérésre a /login-ra, a trust proxy-t meg true-ra. Aztán kétféleképpen tüzeltem rá.
// limit = 3 / perc, ugyanaz a (spoofolt) IP:
kérés 1: 200 kérés 2: 200 kérés 3: 200 kérés 4: 429 kérés 5: 429
// ugyanaz a limit, de kérésenként új X-Forwarded-For:
203.0.113.1: 200 .2: 200 .3: 200 .4: 200 .5: 200 ... mind 200
Az első sor mutatja, hogy a limiter működik: a negyedik kérésnél jön a 429 Too Many Requests. A második sorban semmi mást nem csináltam, csak minden kéréshez más IP-t írtam az X-Forwarded-For-ba. Mivel a limiter a req.ip-t használja kulcsként, és a req.ip most a fejléc bal széle, minden kérés új „felhasználónak” számít. A limit így nem 3 lett, hanem végtelen. Egy jelszó-brute-force ellen ez a védelem pontosan annyit ér, mint a be nem kapcsolt.
És nem csak a rate limitről van szó. Ugyanez a hamisítható req.ip mérgezi a hozzáférési logokat, félrevezeti az IP-alapú engedélylistákat (allowlist), és bármilyen „csak belső hálózatról” jellegű döntést kijátszhatóvá tesz.
A helyes beállítás: számold meg a proxykat
A trust proxy nem bináris kapcsoló, csak sokan annak használják. Elfogad számot, konkrét IP-t vagy alhálózatot, előre definiált neveket (loopback, linklocal, uniquelocal), és saját függvényt is. A biztonságos használat kulcsa, hogy ne a bal, hanem a jobb oldalról olvasson — onnan, ahol a te infrastruktúrád ír, nem a kliens.
Ha számot adsz meg, az Express jobbról az N. bejegyzést veszi kliensnek a láncból, ahol N a közted és az internet között álló proxyk száma (ezt le is mértem: háromelemű XFF-nél trust proxy: 1 a jobb szélsőt, 2 a jobbról másodikat, 3 a harmadikat adta). A fenti táblázatban ez a 9.9.9.9, 10.0.0.1 láncnál látszik: trust proxy: 1 mellett a jobb szélső 10.0.0.1 lett a req.ip — az, amit a hozzám legközelebbi (egyetlen) proxy fűzött hozzá —, a támadó által beírt 9.9.9.9-et pedig az Express átugorja. Egy nginx előtted: 1; nginx mögött egy cloud load balancer: 2. IP-alapú megadásnál (app.set('trust proxy', '10.0.0.0/8')) az Express jobbról balra halad a láncon, és az első nem megbízható címnél megáll — az lesz a kliens.
A számláláshoz azt kell tudnod, hány saját hop van a lánc jobb végén. Ez néha meglepően fárasztó: egy rosszul beállított hop-szám ugyanúgy hamisíthatóvá teszi a req.ip-t, mert a támadó egyszerűen betölti annyi elemmel a fejlécet, hogy a te „jobbról N.” pozíciód pont az ő adatára essen. Érdemes egy teszt-endpointon kiíratni a req.ip-t és a nyers XFF-et, és valódi kérésen ellenőrizni, hogy tényleg a proxyd címét szűröd-e ki.
A csavar: a hop-szám is csak addig véd, amíg a proxy tényleg hozzáfűz
Itt van az a részlet, ami miatt érdemes volt lemérni ezt az egészet, nem csak elolvasni a doksit. A fenti táblázatban a trust proxy: 1 az egyelemű 1.2.3.4 fejlécre is 1.2.3.4-et adott vissza — pedig ott nem volt igazi proxy. A hop-szám ugyanis abból indul ki, hogy a lánc jobb végét a te proxyd írja. Ha az appod közvetlenül is elérhető az internetről (a proxy megkerülésével), akkor a támadó saját maga állítja elő a teljes láncot, és a hop-szám a hamisított adatra mutat.
Az MDN ezt egy mondatban összefoglalja: ha a szerver közvetlenül is elérhető az internetről — még ha egy megbízható proxy is van előtte —, akkor az X-Forwarded-For egyetlen része sem tekinthető biztonságosnak. Ebből két gyakorlati szabály következik. Egy: a legkülső, hozzád legközelebbi proxynak felül kell írnia a beérkező X-Forwarded-For-t (nem csak hozzáfűzni), különben a kliens által becsempészett elemek a láncban maradnak. Kettő: a származási szervert (origin) tényleg zárd el, hogy csak a proxy érje el — különben az egész trust-lánc kikerülhető.
Ha ez a fejléc-egyveleg (ki fűz hozzá, ki ír felül, hányan) túl törékenynek tűnik, jó okkal: ezért létezik a szabványosított Forwarded fejléc (RFC 7239), ami egy helyen viszi a klienst, a protokollt és a hostot. Ez sem varázsszer — ugyanúgy csak megbízható proxytól érdemes elhinni —, de legalább egyetlen, jól definiált formátum, nem három ad hoc X-Forwarded-*.
Az express-rate-limit már kiabál emiatt
Annyira gyakori ez a hiba, hogy az express-rate-limit külön beépített ellenőrzést kapott rá. Amint a trust proxy true, és a limitert használod, a konzolra ezt kapod (ezt is a saját futtatásom adta):
ValidationError: The Express 'trust proxy' setting is true, which allows
anyone to trivially bypass IP-based rate limiting.
code: 'ERR_ERL_PERMISSIVE_TRUST_PROXY'
A hozzá tartozó hibakód-oldal pontosan ezt írja le: a true a bal szélső XFF-bejegyzést használja, amit a kliens is beállíthat, így megnyitja a rate limiter megkerülését. Van párja is: az ERR_ERL_UNEXPECTED_X_FORWARDED_FOR akkor jön, ha érkezik XFF, de a trust proxy false — ez általában azt jelzi, hogy a limiter globálisan korlátoz mindenkit egy közös kulcson (a proxy IP-jén), tehát a beállításod nem stimmel. Ha production alatt bármelyiket látod a logban, az nem zaj, hanem a konfiguráció hibája.
Mikor melyiket, és használnám-e élesben
A döntés valójában egyszerű, ha nem kapcsolóként gondolsz rá:
false(alapértelmezés) — ha nincs proxy előtted, vagy nincs szükséged a kliens IP-jére. Ne kapcsold be „csak úgy”.- szám (hop-count) — a leggyakoribb helyes eset: ismered a proxyk számát, és a legkülső felülírja az XFF-et. Egy nginx előtt:
1. - IP / alhálózat /
loopback— ha a proxyid címe stabil és ismert, ez a legpontosabb. A dinamikus IP-jű felhős LB-eknél viszont karbantartási teher. true— production, publikus szerveren gyakorlatilag soha. Csak akkor véd, ha a legkülső proxy garantáltan letörli a bejövő XFF-et, de akkor is fölöslegesen kockázatos a hop-számhoz vagy IP-hez képest.
Használnám-e a true-t élesben? Nem. Nincs olyan helyzet, amit ne oldana meg jobban egy konkrét hop-szám vagy IP-lista, és a true egyetlen félreértett proxy-konfignál csendben kinyitja az összes IP-alapú védelmedet. A tanulság nem az, hogy „ne bízz a proxyban”, hanem hogy pontosan egy dologban bízz: a saját infrastruktúrád által írt, jobb oldali bejegyzésben. Minden más a kliens, és a kliens hazudik.