Het admin-subdomein stopt CSRF niet: vier controles voor de lancering
Een nieuwe hostnaam is een nieuwe origin, vaak dezelfde site, dus same-site-cookies en eenvoudige formulieren steken eroverheen. Controleer de tokens, de poort en de autorisatie.
De beheerinterface naar admin.example.com verhuizen is een echte verbetering: het houdt beheercookies weg van openbare paginas, geeft beide applicaties een eigen cache en laat je een strenger netwerkbeleid op een hostnaam richten. Maar het is geen CSRF-controle, en het maakt de code van de openbare site niet veilig om open te laten voor een ingelogde beheerder. De splitsing verandert wat de browser als grens beschouwt, niet wat de server controleert, dus doe deze vier controles voordat je zegt dat het klaar is.
Lees origin en site als twee verschillende woorden
Een origin is schema, host en poort, dus https://www.example.com en https://admin.example.com zijn verschillende origins. Een site, in de zin van SameSite, is schema plus registreerbaar domein, dus die twee hostnamen zijn meestal dezelfde site. Dat verschil is de hele val: het SameSite-attribuut helpt je niet tussen zuster-subdomeinen, omdat de browser het verzoek helemaal niet als kruisling-site beschouwt. Richt de beheerinterface op een werkelijk ander registreerbaar domein, bijvoorbeeld admin-example.net, en het verzoek wordt echt kruisling-site, en daar begint SameSite=Lax of Strict werk te doen waarop je kunt vertrouwen.
Test het in de browser in plaats van erover te redeneren.
Laad de openbare site, open de ontwikkelaarstools en kijk welke cookies de browser aan een verzoek naar de adminhost koppelt. Heeft de cookie geen Domain-attribuut, dan geldt hij alleen voor de host en blijft hij op zijn eigen naam; draagt hij Domain=example.com, dan reist hij naar elke zuster, en het nieuwe subdomein heeft je voor die cookie niets opgeleverd.
Scheid het lezen van het antwoord van het versturen van het verzoek
Het same-originbeleid beperkt het lezen van antwoorden tussen origins. Het houdt een verzoek niet tegen en verhindert niet dat een formulier inloggegevens meeneemt die de browser al heeft. Een eenvoudig verzoek, met een formulier-inhoudstype of een gewone GET, wordt zonder voorafgaande controle afgeleverd; CORS beslist daarna of het aanroepende script het antwoord mag lezen. CSRF leeft volledig aan de verzendkant.
Daarom heeft de server een expliciete controle nodig voor bewerkingen die de toestand wijzigen. Een aan de sessie gebonden synchronisatietoken, of een server-side vergeleken double-submit-cookie, dekt het geval; een Origin- of Referer-vergelijking is de moeite waard als tweede poort, maar niet als enige, omdat niet elk verzoek de header draagt en niet elke proxy hem bewaart. De OWASP-richtlijn voor het voorkomen van CSRF ziet SameSite precies daarom als een laag onder meerdere.
Bepaal wat de netwerkpoort werkelijk bewijst
Een lijst met toegestane VPN-uitgangen voor de adminhost is een goede controle en een zwakke identiteitsclaim. Hij bewijst dat het verzoek van een adres kwam dat de VPN uitgeeft, dat iedereen op die uitgang kan delen, en zegt niets over welke persoon of welk apparaat erachter zit. Behandel hem als een toegangsfilter, niet als authenticatie.
Houd hem onafhankelijk van de controles op verzoekniveau. Als de lijst het enige is tussen een aanvaller en een eindpunt dat de toestand wijzigt, haalt een verkeerd geconfigureerde route, een split-tunnelclient of een geleende laptop de controle volledig weg.
Log ook weigeringen buiten de verwachte bereiken. Een golf ervan is het nuttigste signaal dat iemand de grens verkent die je net hebt gebouwd, en stilte is het signaal dat het filter helemaal niet wordt bereikt.
Houd cookies, inloggen en autorisatie expliciet
De reikwijdte van de cookie bepaalt het grootste deel van de uitkomst. Een cookie van het ouderdomein wordt naar de adminhost gestuurd, dus een scriptinjectie op de openbare site kan handelen met de sessie van een beheerder; een cookie die alleen voor de host geldt kan dat niet. Geef de voorkeur aan de vorm die alleen voor de host geldt, voeg het Secure-attribuut toe en gebruik het voorvoegsel __Host- waar het platform het ondersteunt, want dat voorvoegsel vereist Secure, Path=/ en geen Domain-attribuut. Lees de antwoordheader in plaats van de cookiepot: Set-Cookie: __Host-admin_session=...; Secure; Path=/; HttpOnly; SameSite=Lax, zonder Domain-attribuut. Een browser gooit een __Host--cookie met Domain weg, dus een cookie die nog op zusterhosts verschijnt, bewijst dat het voorvoegsel nooit is toegepast.
Houd daarna de autorisatie op de server, per verzoek, op elk beheereindpunt, niet alleen bij het inloggen. Een verzoek van een toegestaan netwerk moet nog steeds worden geauthenticeerd en geautoriseerd voor de bewerking die het vraagt; de hostnaam is geen actor. Als de openbare en de beheerapplicatie een inlogstroom of een sessieopslag delen, schrijf die relatie op, want het is de kortste weg van een fout op de openbare site naar een beheeractie.
Controleer de grens opnieuw na de splitsing
Voer de negatieve tests uit, niet alleen het gelukkige pad: een kruisling-siteformulier zonder token moet worden geweigerd, een token uit een andere sessie moet worden geweigerd, en geen enkele GET mag de toestand wijzigen. Controleer de cookie-attributen in de ontwikkelaarstools, bevestig dat de Origin-controle afgaat op een niet-overeenkomend verzoek, en probeer de adminhost van buiten het VPN-bereik om het filter te zien werken.
Niets hiervan haalt een XSS-fout binnen de beheerapplicatie zelf weg. Een aparte hostnaam is een isolatiegrens aan de browserkant met echte waarde, en het is geen vervanging voor het voorkomen van onveilige code-uitvoering of voor het beschermen van verzoeken aan de serverkant. Na de splitsing is de lijst korter dan hij lijkt: een aparte origin, cookies alleen voor de host, een token bij elke wijziging, een toegangsfilter waarop je niet als identiteit vertrouwt, en autorisatie per verzoek beslist.