Elke per-IP-kontrole wat ons uitgestuur het, kon met een header omseil word
Ons rekening-uitsluit-teller het mislukte aanmeldings by vyf afgetop. Ons rate limiter het misbruikende kliënte gesmoor. Albei het op die kliënt se IP-adres gesleutel, wat die voor die hand liggende ding is om op te sleutel. Albei was heeltemal omseilbaar, en die omseiling was 'n enkele HTTP-header wat jy self kon stel.
Hier is hoekom. Wanneer jou app agter 'n reverse proxy loop, en ons s'n loop agter nginx op Kubernetes, kom die TCP-verbinding wat die app sien nie van die gebruiker af nie. Dit kom van die proxy af. Elke request kom aan met dieselfde socket-eweknie-adres: die ingress. Die regte kliënt-IP moet dus in 'n header gedra word, X-Forwarded-For, wat die proxy stel en die app lees. ASP.NET Core het middleware vir presies dit: dit lees X-Forwarded-For en herskryf die verbinding se remote IP om ooreen te stem, sodat die res van jou kode, insluitend jou rate limiter, die ware kliënt sien.
Die probleem is dat X-Forwarded-For net 'n header is. Enigiemand kan een stuur. As die app dit onvoorwaardelik vertrou, en ons s'n het, dit van enige bron hoegenaamd vertrou, dan is die waarde onder die aanvaller se beheer. Die aanval is dus nie slim nie, dit is een reël:
curl -H "X-Forwarded-For: 10.0.0.1" https://.../login
curl -H "X-Forwarded-For: 10.0.0.2" https://.../login
curl -H "X-Forwarded-For: 10.0.0.3" https://.../login
Verander die header met elke request en die app dink elke poging kom van 'n splinternuwe kliënt af. Die uitsluit-teller, gesleutel op daardie IP, bereik nooit vyf vir enige enkele waarde nie. Die rate limiter se emmer is elke keer vars. Jy kan heeldag lank 'n wagwoord brute force, en elke per-IP-verdediging wat ons gehad het, tel vrolik tot by een. 'n Kontrole wat sleutel op 'n waarde wat die aanvaller stel, is nie 'n kontrole nie.
Die regstelling klink triviaal: vertrou X-Forwarded-For net van jou eie proxy af. Dit is triviaal in beginsel en knopig in die praktyk, en Kubernetes is waar die knopigheid woon.
Jy kan nie net ophou om die header te lees nie. As jy dit doen, lyk elke request asof dit van die ingress af kom, so nou deel al jou kliënte een IP en die per-IP-kontroles breek in die ander rigting: een misbruiker trip die uitsluiting vir almal. Jy moet X-Forwarded-For lees, maar dit net glo wanneer die request werklik van 'n proxy gekom het wat jy vertrou.
ASP.NET se forwarded-headers-middleware vertrou volgens bron: 'n lys bekende proxy-IP's, of bekende proxy-netwerke as CIDR's gegee. Die naïewe skuif is om die ingress-pod se IP vas te pen. Op AKS is daardie IP nie stabiel nie. Ingress-pods word hergeskeduleer en herskaal, en hul adresse verander saam met hulle, so 'n vasgepende IP raak verouderd en breek óf wettige verkeer óf, as jy 'n ou terugval laat staan, maak dit die gat weer oop. Wat wel stabiel is, is die subnet waaruit die pods hul adresse trek. Jy vertrou dus die node-subnet se CIDR, nie enige enkele adres nie. Elke hop wat die header wettiglik kan stel, woon in daardie reeks, en niks buite dit word geglo nie.
Dan is daar die telling. Die middleware stap die X-Forwarded-For-lys van regs af deur en skil een vertroude hop op 'n slag af, en wat ná die vertroude hops oorbly, word as die kliënt geneem. Stap een hop te ver en jy tree van jou proxy af en beland in die deel van die header wat die aanvaller geskryf het. Die aantal hops wat jy afskil, moet dus presies gelyk wees aan die aantal proxies wat werklik by die header byvoeg. In ons pad is daar een: nginx. Die Azure-load balancer daarvoor is laag 4. Dit stuur TCP aan, dit parseer nie HTTP nie, en dit voeg geen X-Forwarded-For-inskrywing by nie. Die korrekte forward limit is dus een. Nie die verstek nie, nie "'n paar om veilig te wees" nie. Een, want een proxy raak aan daardie header.
Daardie paar, vertrou die node-subnet en skil presies een hop af, is die hele regstelling. Die kliënt-IP wat die middleware vir jou gee, is nou die waarde wat nginx uit die regte socket geskryf het, en die aanvaller se ingespuite inskrywings sit links daarvan, geïgnoreer, want ons hou op stap voordat ons hulle bereik.
Die oordraagbare deel is nie die getalle nie, dit is dat hulle getalle oor jou spesifieke topologie is. X-Forwarded-For is nie data nie, dit is delegering van vertroue: om dit te lees, is om te sê "ek glo wie ook al dit gestel het". Daardie geloof moet vasgepen wees aan die presiese hops in jou request-pad, nie wyer nie en nie nouer nie. Vertrou te breed en dit is spoofbaar. Vertrou te nou en jy laat al jou kliënte in die proxy inmekaarval. En die telling is gekoppel aan jou infrastruktuur, so die dag wat jy 'n CDN voor alles sit, het jy 'n hop bygevoeg, en die forward limit wat gister korrek was, is nou een te min. Die regstelling is nie 'n towerkonstante nie. Dit is: tel die proxies in jou pad, vertrou daardie en net daardie, en tel weer elke keer as die pad verander.
Om proxy-hops korrek te tel is onaantreklik, maklik om verkeerd te kry, en dit bepaal of jou rate limits enigsins iets beteken. Authagonal doen daardie boekhouding vir jou, sodat jou uitsluiting die kliënt tel en nie 'n header nie.