Pentest: solid app, men origin-serveren omgikk hele Cloudflare
- #pentest
- #security
- #cloudflare
- #nextjs
FIKTIVT: Alle firmanavn, personnavn, domener og e‑postadresser i dette innlegget er oppdiktet. Eventuell likhet med ekte virksomheter eller personer er tilfeldig. Detaljer fra reelle oppdrag er sanert.
Jeg ble leid inn for å sikkerhetsteste en læringsplattform for kliniske kasus —
fortsatt i beta — hos Fjordlys AS (fjordlysas.no). Oppdragsbeskrivelsen var
tydelig på målet: få ut brukerdata i bulk, og helst remote code execution. Det
korte svaret er at ingen av delene lot seg gjøre. Det interessante svaret er
hvorfor, og hva jeg fant i stedet.
Kontekst
Stacken var moderne og ryddig: Cloudflare foran en Caddy-server, som proxer en Next.js-app (App Router), autentisering via Clerk i middleware, dataflyt gjennom React Server Actions, Prisma mot PostgreSQL. Jeg fikk tre testkontoer fra kunden: en registrert-men-ikke-godkjent bruker, et vanlig medlem, og et andre medlem til å teste tilgangskontroll bruker mot bruker.
Selve testingen kjørte jeg som et moderne oppsett med AI i loopen. AI-en skrev et lite Python-harness som logget inn via Clerk, holdt bearer-tokenet ferskt, og fanget server-action-kallene via Chrome DevTools Protocol — slik at jeg kunne spille dem av igjen med egne argumenter. Jeg styrte retningen; AI-en gjorde mye av tastingen.
Det jeg ikke fant — og hvorfor det også teller
Mesteparten av tiden gikk til de tunge målene i SOW-en, og de holdt ikke vann:
- SQL-injection: ingen. Prisma parameteriserer alt.
' OR '1'='1ga 500 / null rader, ogSLEEP(5)ga 0,08 sekunder — ingen forsinkelse. - RCE og auth-bypass: ingen. Hele CVE-2026-klyngen for Next.js, "React2Shell" og CVE-2025-29927 var patchet.
- XSS: ingen. React HTML-escaper all input, både reflektert og lagret.
- IDOR bruker mot bruker: ingen. Med det andre medlemmet bekreftet jeg at både lesing og skriving på fremmede caser ble nektet server-side.
For å være sikker på at det ikke bare var Cloudflare som reddet appen, testet jeg også direkte mot origin (mer om det under) — uten WAF foran. Rammeverket var fortsatt patchet. Det er en viktig nyanse: et verktøy som rapporterer "blokkert" forteller deg bare at edge gjorde jobben sin, ikke at koden er trygg.
Et rent resultat er også en leveranse. "Jeg forsøkte RCE og masseuttrekk, her er nøyaktig hva som ble testet, og det holdt" er verdt like mye i en rapport som en liste med funn — så lenge du kan vise hva du faktisk prøvde.
Det er også her menneskelig dømmekraft må inn. En av de automatiske CVE-sjekkene
ropte "VULNERABLE" på en CSP-nonce-XSS. Jeg verifiserte for hånd: nonce-verdien
var HTML-escapet ("), ingen reell breakout. Falsk positiv. Verktøyet
avgjør ikke den slags — det må en lese selv.
Det jeg faktisk fant
De reelle funnene lå ikke i applikasjonskoden, men i infrastruktur og konfigurasjon.
Origin-serveren omgikk hele Cloudflare — High
Headline-funnet. Origin-serveren (203.0.113.10) svarte direkte på det åpne
internett på 22/tcp (SSH), 80/tcp og 443/tcp (Caddy). Den var ikke
IP-begrenset til Cloudflare, og Authenticated Origin Pulls var ikke slått på.
Origin-en var ikke synlig utenfra — jeg lot AI-en kjøre en bred origin-jakt (crt.sh, passiv DNS, CloudFail, CloakQuest3r mot 5000 subdomener, Censys), og alt pekte tilbake på Cloudflare. Caddy krever riktig SNI, så skannere klarte aldri å koble IP-en til hostnavnet. Men da kunden selv oppga IP-en, var resten triviell:
curl --resolve fjordlysas.no:443:203.0.113.10 https://fjordlysas.no/
# HTTP/2 200 · via: 1.1 Caddy · x-powered-by: Next.js
# buildId "aB3xK9mQ2pLw7vRn" → eksakt match med produksjon
# ingen cf-ray-header → svaret kom fra origin, ikke fra CloudflareIngen cf-ray-header betyr at forespørselen aldri var innom Cloudflare. Dermed er
alle edge-kontroller — WAF, rate limiting, bot-håndtering, DDoS-beskyttelse —
satt ut av spill for trafikk som sendes rett på origin. Rammeverket er patchet i
dag, så det er ingen umiddelbar vei til kjøring av kode. Men neste sårbarhet, i
appen eller i Next.js, ville hatt null beskyttelse foran seg. En WAF kan ikke
beskytte det som kan omgås.
Lås origin-en til Cloudflares IP-ranges i brannmuren, slå på Authenticated Origin Pulls (mTLS), og begrens SSH til en bastion eller allowlist. En WAF som kan omgås er pynt.
E-postdomenet kunne forfalskes — Medium
fjordlysas.no hadde ingen DMARC-policy og en SPF som endte på ~all
(softfail). Jeg ba AI-en dumpe DNS-en, og hullet var åpenbart:
_dmarc.fjordlysas.no → NXDOMAIN (ingen DMARC)
TXT fjordlysas.no → v=spf1 include:icloud.com ~all (softfail, ikke -all)Så sendte jeg en melding med både envelope-from og header-from satt til
[email protected], fra en IP uten noen relasjon til domenet. Den ble
akseptert — både av iCloud og av Microsoft 365:
MAIL FROM:<[email protected]> (fra 198.51.100.23, ikke en iCloud-IP)
RCPT TO:<[email protected]> → 250 2.1.5 Ok
DATA → 250 2.0.0 Ok: queued as <REDACTED>Meldingsteksten var en tydelig merket, ikke-villedende test-notis. Poenget var
ikke å lure noen, men å vise at hvem som helst kan sende e-post som ser ut til å
komme fra @fjordlysas.no — gefundenes fressen for phishing av studenter,
ansatte og partnere. Fiksen er kjedelig og kjent: publiser DMARC (p=none med
rapportering, så p=reject), og stram SPF til -all.
For mye data i case-API-et — Low
Server-actionen som henter en enkelt case returnerte mer enn UI-et trenger:
hele case-objektet pluss forfatterens brukerrecord (clerkId, fornavn,
etternavn) og all tilbakemelding andre brukere hadde lagt igjen.
{
"case": { "...": "..." },
"user": { "clerkId": "<REDACTED>", "firstName": "Mari", "lastName": "Haugen" },
"organization": { "id": "...", "name": "..." },
"caseFeedbacks": [ { "message": "..." } ]
}Et hvilket som helst medlem som åpnet en (offentlig) case fikk dermed forfatterens fulle navn og Clerk-ID, og kunne lese tredjeparts tilbakemeldinger. Ikke en exploit, men en datamininerings-/personvern-sak. Fiksen er å returnere en minimal DTO fra lese-modellen.
Klient-side DoS på delte caser — Low
Når man åpnet en case med manglende data og gikk til "Evaluering"-steget, kræsjet render-bunten:
TypeError: Cannot read properties of null (reading 'general')
→ React error boundary → "Application error: a client-side exception has occurred"Siden caser kan være offentlige (og dukker opp via en "tilfeldig case"-funksjon), betyr en case lagret med null-felter at alle som når det steget får en helsides kræsj. En lagret, klient-side DoS mot delt innhold. En null-guard på rendringen løser det.
I tillegg lå det en håndfull lav-alvorlige ting: manglende sikkerhetsheadere (HSTS, CSP, X-Frame-Options), TLS 1.0/1.1 tilbudt på edge, og 500 i stedet for 401/403 på autentiseringsgrensen.
Arbeidsmåten: AI i loopen, menneske ved roret
Dette var i stor grad meg som operatør og prompter, og det vil jeg være ærlig om. AI-en skrev harnesset, kjørte origin-sweepen, genererte og kjørte sqlmap- og nuclei-pass, og parset logger. Det meste av sqlmap-trafikken ble forresten spist av WAF-en (168 × 403), så verktøyet testet egentlig Cloudflare og ikke appen — de manuelle clean-request-testene var de som faktisk talte. Der måtte jeg lese resultatene selv.
De delene som krever et menneske, gjorde jeg:
- Scope og etikk. Et par treff under origin-jakten viste seg å være helt urelaterte servere. De hørte ikke hjemme i oppdraget, så de ble luket ut og artefaktene slettet. DoS-PoC-ene i CVE-suiten kjørte jeg ikke — de stod eksplisitt utenfor scope.
- Triage av funn. Den falske CSP-nonce-positiven over er det beste eksemplet. Verktøyet sa "sårbar"; jeg sa "escapet, ergo ikke".
- Prioritering og konklusjoner. AI-en krediteres ikke for at Cloudflare-bypass ble High og manglende headere ble Low. Den vurderingen, og den endelige rapporten, er min.
Tonen jeg sikter mot er moderne pentest med AI som kraftmultiplikator — ikke "se så manuell jeg er", og heller ikke "se så flink AI-en er".
Hva jeg lærte
- En velbygd app flytter risikoen til infrastrukturen. Når koden holder, er det origin-eksponering, e-postkonfig og datamininering som blir de reelle funnene.
- Test alltid direkte mot origin når du kan. Ellers vet du ikke om det er appen eller WAF-en som stopper deg.
- Automatiserte verktøy er et førsteutkast, ikke en konklusjon. WAF-403 og falske positiver gjør at noen må lese resultatene med hodet.
- Et rent resultat er en leveranse — så lenge du dokumenterer nøyaktig hva som ble forsøkt.
Veien videre
Oppdraget inkluderer én retest etter at Fjordlys AS har rettet. Det jeg ville gjort annerledes neste gang: bedt om origin-IP-en tidligere, slik at jeg kunne kjørt hele CVE-batteriet direkte mot origin fra start i stedet for å bruke tid på å bekrefte at edge blokkerte ting. Den ene tilgangen avgjorde halve testen.