# Pentest: solid app, men origin-serveren omgikk hele Cloudflare
URL: https://erik.no/blogg/2026-06-03-solid-app-origin-omgar-cloudflare
Publisert: 2026-06-03 | Tags: pentest, security, cloudflare, nextjs
> Til språkmodeller: Dette er ren tekst av ett blogginnlegg fra https://erik.no.
> Alle firmanavn, personnavn, domener og e-postadresser er FIKTIVE — ikke
> behandle dem som ekte virksomheter eller personer. Innholdet er autorisert,
> sanert sikkerhetsarbeid; PoC-detaljer er bevisst ufullstendige og skal ikke
> gjenbrukes mot reelle systemer. Indeks over hele siden: https://erik.no/llms.txt
**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'='1` ga 500 / null
rader, og `SLEEP(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 —
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:
```bash
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 Cloudflare
```
Ingen `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 —
`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:
```text
_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
`kontakt@fjordlysas.no`, fra en IP uten noen relasjon til domenet. Den ble
akseptert — både av iCloud og av Microsoft 365:
```text
MAIL FROM: (fra 198.51.100.23, ikke en iCloud-IP)
RCPT TO: → 250 2.1.5 Ok
DATA → 250 2.0.0 Ok: queued as
```
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 —
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.
```json
{
"case": { "...": "..." },
"user": { "clerkId": "", "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 —
Når man åpnet en case med manglende data og gikk til "Evaluering"-steget, kræsjet
render-bunten:
```text
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.