Hovedsiden holdt, satellittene gjorde det ikke
- #pentest
- #wordpress
- #security
- #api-security
- #third-party
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.
Det interessante med dette oppdraget var ikke det som brast — det var hvor
ulikt herdingsnivået var innenfor én og samme kunde. Tjall AS (tjallas.no)
drifter hovednettstedet sitt på Cloudways managed WordPress med aktiv WAF,
mens kurs- og opplæringssubdomenene henger på egne DigitalOcean-droplets uten
tilsvarende beskyttelse. Hovedsiden holdt. Satellittene gjorde det ikke. Og en
tredjeparts adtech-plattform kunden bruker, lekket noe den ikke skulle lekket.
Kontekst
Scope var en uautentisert baseline-test av Tjall AS sin nett- og skyflate,
SOW-referanse SOW-20260531, testtype ekstern rekognosering uten testkonto.
Kontakten hos kunden var IT-sjef Lars Berg ([email protected]). Målet var å
kartlegge angrepsflaten utad og se hvor langt en uautentisert angriper kommer
før WAF, autentisering eller manglende tilgang stopper dem.
Jeg kjører denne typen oppdrag med AI i loopen: agenter gjør recon, kjører skannere og foreslår neste steg, jeg styrer scope, leser resultater og tar vurderingene som krever dømmekraft — spesielt skillet mellom et plausibelt funn og et verifisert et. Det skilletet ble sentralt i dette oppdraget, fordi agenten produserte flere plausible funn som ikke overlevde verifisering.
Angrepsflaten
Første steg var rekognosering. Jeg lot en agent hente sertifikat-transparens
via crt.sh og krysse med passiv DNS. Det ga subdomenkartet under. IP-er er
sanert til dokumentasjonsrekkene.
| Subdomene | Vert | Teknologi |
|---|---|---|
tjallas.no / www | 203.0.113.96 (DigitalOcean/Cloudways) | WordPress 6.9.4 + Elementor, WPML, WP Rocket, WPForms |
kurs.tjallas.no | 198.51.100.185 (DigitalOcean) | WordPress 7.0 |
seokurs.tjallas.no | 198.51.100.185 (samme boks) | WordPress 7.0 |
nettsideopplaring.tjallas.no | 203.0.113.160 (DigitalOcean) | WordPress 7.0 |
matomo.tjallas.no | 198.51.100.13 (DigitalOcean) | Selvhostet Matomo, nginx 1.18 |
I tillegg kom tre vanity-domener mot tredjeparts SaaS: rapport.tjallas.no
→ Report360, seo.tjallas.no → SE Ranking, dsp.tjallas.no → TabMo «Hawk»
DSP. Kunden bekreftet leverandørsamtykke for tredjepartsplattformene, så de ble
testet på tenant-/konfigurasjonsnivå — proporsjonalt, ingen DoS, ingen
destruktive handlinger (SOW §9/§14).
Det som slo meg med en gang i kartet: de tre kurs-nettstedene ligger på egne droplets, ikke bak Cloudways/Imunify360-laget som hovedhosten. Det er to helt ulike herdingsregimer innenfor samme kunde.
Hovedsiden holdt
Hovedhosten 203.0.113.96 lot seg ikke røre fra utsiden. Agenten kjørte en
nmap-sweep som viste kun port 22, 80 og 443 åpne (997 filtrert av
host-brannmur). TLS var konsekvent satt til kun TLS 1.3, A-gradede ciphers,
gyldig Let's Encrypt-sertifikat.
På applikasjonslaget var de vanlige WordPress-angrepsvektorene lukket:
GET /wp-json/wp/v2/users HTTP/1.1
Host: tjallas.noHTTP/1.1 401 Unauthorized
{"code":"rest_not_logged_in","message":"..."}GET /?author=1 HTTP/1.1
Host: tjallas.noHTTP/1.1 404 Not FoundXML-RPC var blokkert, og konfigurasjonsfiler (.env, .git, wp-config.php.bak,
.user.ini) returnerte alle 403. Det er standard Cloudways-herding, og det
fungerte.
Det som derimot skjedde under skannelast, var at WAF-en (Imunify360/WebShield)
begynte å banne tester-IP-en min (198.51.100.56) på tilkoblingsnivå:
HTTP/1.1 415 Unsupported Media Type
# deretter HTTP 000 (connection-level IP-ban)Det er en positiv kontroll — effektiv anti-automasjon — men det skjuler samtidig applikasjonsflaten for legitim testing. For å teste selve hovedappen i dybden, må kunden allowliste tester-IP. Jeg noterte det som et prosessfunn, ikke et sikkerhetsfunn.
Satellittene lekket
De tre kurs-nettstedene hadde ikke samme beskyttelse. De lå rett på DigitalOcean, testbare uten WAF foran. Her kom de vesentlige funnene.
F1 — Brukerenumerering (Medium) Medium
Forutsetninger: ingen. Uautentisert tilgang til kurs.tjallas.no,
seokurs.tjallas.no og nettsideopplaring.tjallas.no. Standard Chrome
User-Agent, ingen proxy.
- Hent brukerliste via WordPress REST API:
GET /wp-json/wp/v2/users HTTP/1.1
Host: nettsideopplaring.tjallas.noHTTP/1.1 200 OK
Content-Type: application/json
[
{"id":1,"name":"admin","slug":"larsb","_links":{"self":[{"href":"https:\/\/nettsideopplaring.tjallas.no\/wp-json\/wp\/v2\/users\/1"}]}},
{"id":2,"name":"kontakt","slug":"kontakt-tjallas-no","_links":{"self":[{"href":"https:\/\/nettsideopplaring.tjallas.no\/wp-json\/wp\/v2\/users\/2"}]}}
]- Bekreft via author-arkivet (den klassiske
?author=N-omveien):
GET /?author=1 HTTP/1.1
Host: nettsideopplaring.tjallas.noHTTP/1.1 301 Moved Permanently
Location: https://nettsideopplaring.tjallas.no/author/larsb/Responsen bekrefter to ting: at larsb er et gyldig admin-brukernavn (id 1,
visningsnavn «admin»), og at kontakt-tjallas-no er en annen konto
([email protected]). Samme admin-slug larsb gikk igjen på alle tre
kurs-nettsteder — samme person, samme gravatar-hash, tre installasjoner som
deler credentials i praksis.
- Negativ kontroll — samme oppslag mot hovedhosten, som har brukerenumerering slått av:
GET /wp-json/wp/v2/users HTTP/1.1
Host: tjallas.noHTTP/1.1 401 UnauthorizedSteg 1 og 2 på satellittene mot den negative kontrollen på hovedsiden er
poenget: hovedsiden implementerte kontrollen, satellittene gjorde det ikke.
Det gyldige brukernavnet senker terskelen for målrettet passordgjetting,
credential stuffing og phishing. En angriper som vet at admin heter larsb og
går igjen på tre sider, har et mye smalere og mer realistisk mål.
Jeg prøvde også å utnytte informasjonen. Etter å ha enumerert brukerne, kjørte
jeg et begrenset, ikke-destruktivt forsøk på autentiseringsomgåelse mot
nettsideopplaring.tjallas.no/wp-login.php — to kontoer, syv kuraterte
svake/standard-passord, 3 sekunder mellom hvert forsøk. Alt feilet (HTTP
200 med error-respons, ingen auth-cookie), og etter ca. 12 forsøk slo en
rate-limiter inn med 415. Det er en positiv kontroll på innloggingssiden:
kontoene motstår gjetting, og brute-force er begrenset. Men brukernavnet er
fortsatt eksponert, og det er selve funnet.
Mitigering: Blokker REST-listing av brukere for uautentiserte
(rest_user_inquiries-filter eller functions.php), deaktiver
author-arkiv-enumerering, vurder å endre admin-slug, og påtving 2FA på
admin-kontoer. Innloggingsbeskyttelsen som slo inn er positiv — behold den.
F2 — Utdaterte plugins med kjente CVE-er (Medium) Medium
Forutsetninger: samme tre kurs-nettsteder. WPScan med API-token for CVE-oppslag.
Jeg ba agenten kjøre WPScan mot kurs- og seokurs-nettstedene med sårbarhetsdatabase slått på:
wpscan --url https://seokurs.tjallas.no --api-token <REDACTED> \
--enumerate vp --random-user-agent --throttle 1500Utdrag av resultatet (sanert):
[i] Plugin Version Information:
jet-elements 2.7.4 [!] 8 vulnerabilities identified
jet-blocks 1.3.16 [!] 5 vulnerabilities identified
elementor 4.1.1
wpforms 1.21.0
[!] jet-elements 2.7.4 vulnerabilities:
* CVE-2025-39447 Missing Authorization [CVSS 8.8] requires Contributor+
* CVE-2025-39448 Stored XSS [CVSS 6.1] requires Contributor+
* CVE-2025-53982 Stored XSS [CVSS 6.1] requires Contributor+
...
[!] jet-blocks 1.3.16 vulnerabilities:
* CVE-2025-39451 Missing Authorization [CVSS 8.8] requires Contributor+
* CVE-2025-53988 Information Disclosure [CVSS 5.3] requires Subscriber+
* CVE-2025-30987 Stored XSS [CVSS 6.1] requires Contributor+
...Kryssjekken viste at Crocoblock sine JetElements- og JetBlocks-plugins var versjoner med flere kjente CVE-er — både lagret XSS og «Missing Authorization»-varianter. De fleste krever en innlogget bruker med lave rettigheter (Contributor/Subscriber+), men Missing Authorization-variantene er de som bør prioriteres fordi de utvider hvem som kan utløse dem.
Negativ kontroll — og her ble det en blindvei. Jeg forsøkte ikke å
validere CVE-2025-39447 eller CVE-2025-39451 aktivt. Årsaken er prosaisk: i det
øyeblikket agenten begynte å laste kurs- og seokurs-nettstedene, begynte også
disse hostene (som viste seg å ligge på Cloudways bak rDNS) å banne
tester-IP-en min med HTTP 000. Videre utnyttelse var gated på enten en
allowliste-avtale eller en autentisert testkonto, og det hadde jeg ikke i
denne fasen. F2 står derfor som et versjonsbasert funn, ikke et verifisert
brudd — jeg dokumenterte det slik, ikke som utnyttet. Det er viktig: et
WPScan-treff på en CVE er en påstand om at versjonen er sårbar, ikke et bevis
på at den konkrete instansen er utnyttbar under kundens konfigurasjon.
Mitigering: Oppdater alle Crocoblock Jet*-plugins (og Elementor) til siste versjon. Etabler en rutine for automatiske eller raske plugin-oppdateringer — gjerne med en kort staging-vurdering før produksjon, slik at oppdateringen faktisk skjer og ikke blir liggende.
Tredjeparten som lekket tenant-listen
Det tredje vesentlige funnet var ikke på kundens egne servere i det hele
tatt. Det var på dsp.tjallas.no, som er et white-label-domene mot TabMo
«Hawk»-plattformen — en tredjeparts adtech-DSP driftet av TabMo på AWS.
F6 — env.js eksponerer klientkonfig og full tenant-liste (Medium) Medium
Forutsetninger: ingen. dsp.tjallas.no er en AngularJS SPA
(manager.hawk-platform.io / static.tabmo.io). Uautentisert tilgang.
- Hent den delte frontend-konfigurasjonen:
GET /env.js HTTP/1.1
Host: dsp.tjallas.noHTTP/1.1 200 OK
Content-Type: application/javascript
window.__ENV__ = {
GOOGLE_API_KEY: "<REDACTED>",
AUTH0_CLIENT_ID: "<REDACTED>",
AUTH0_DOMAIN: "<REDACTED>.auth0.com",
AUTH0_AUDIENCE: "<REDACTED>",
DATADOG_KEY: "<REDACTED>",
ORGANIZATIONS: [
{"id":"11111111-1111-4111-8111-111111111111","name":"..."},
{"id":"22222222-2222-4222-8222-222222222222","name":"..."},
/* ...full liste over alle TabMos kunder... */
{"id":"33333333-3333-4333-8333-333333333333","name":"Tjall AS"}
]
};Responsen inneholder tre kategorier data:
- Klient-side API-nøkler — Google API-nøkkel, Auth0 klient-ID/domene/audience, Datadog-nøkkel. Disse er delvis offentlige ved SPA-design (en SPA kan ikke holde en serverside-hemmelighet), men de krever at leverandøren har satt referer/IP-begrensninger på nøklene.
- Den fullstendige tenant-listen for hele TabMo-plattformen — alle kunders organisasjons-ID-er, inkludert Tjall AS sin egen. Dette er en informasjonslekkasje i leverandørplattformen, ikke i kundens oppsett.
- Tjall AS sin egen organisasjons-ID, som kobler dem til plattformen.
- Negativ kontroll / falske positiver. Agenten flagget også
config.json,.envogversion.jsonpådsp.tjallas.nosom potensielle funn. Verifisering viste at alle returnerte den samme 1672-byteindex.html-fallbacken — typisk SPA-ruting, ikke ekte filer:
GET /config.json HTTP/1.1
Host: dsp.tjallas.noHTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1672
<!doctype html><html lang="en"><head>...<!-- AngularJS sirius SPA --></head>...Størrelsen og Content-Type: text/html bekrefter at dette er SPA-fallback,
ikke en lekket konfigurasjonsfil. Jeg forkastet alle tre som falske positiver.
Det er akkurat det skillet agenten ikke klarer alene — den produserer plausible
funn like fort som den produserer plausible feil; verifiseringen er det som
skillner.
Jeg testet ikke Google API-nøkkelen aktivt (for å ikke forbruke TabMos kvote), og jeg lagret ikke tredjeparts tenant-listen (SOW §14 dataminimering). F6 er en sårbarhet i TabMos plattform som berører alle deres kunder — ikke et Tjall-spesifikt problem.
Mitigering: Tjall AS bør rapportere funnet til TabMo. TabMo bør verifisere at Google API-nøkkelen er referer/IP-begrenset, og vurdere å fjerne den globale tenant-listen fra delt frontend-konfig — den trenger ikke ligge i en fil som leveres til enhver besøkende.
Blindveier og forkastede funn
Verifiseringen produserte flere blindveier som er verdt å ta med, fordi de er der agenten sin plausibilitet-hype ville ha overlevert et falskt funn til rapporten.
Matomo config/config.ini.php returnerte HTTP 200. Det så ut som en
database-/salt-lekkasje. Verifisering viste at responsen var Matomo sin
2-byte vernelinje (<?php-exit) — ingen hemmeligheter lekket. Matomo var
for øvrig herdet: vendor/, .git/, plugins/ ga 403,
installasjonsmodulen var låst («already installed»), og API krevde auth
(getMatomoVersion → access denied). Ikke et funn.
matomo.tjallas.no innlogging eksponert mot internett — det er et
reelt, om enn lavt, funn (F5). Versjon var skjult (bra), CSP var til stede.
Anbefalingen er IP-begrensning av admin og påtving 2FA.
M365/tenant-lekkasje: getuserrealm for tjallas.no returnerte
State=4 NameSpaceType=Unknown — domenet er ikke en managed Azure AD-tenant.
Ingen openid-config-eksponering, ingenting å pivotere. Ikke et funn.
Subdomain-takeover: alle NXDOMAIN-subdomener manglet dangling CNAME,
og alle SaaS-CNAME-er pekte på levende leverandører. Ingen takeover.
Lavere alvorlighetsgrad — verdt å nevne
Resten av funnene var lavprioritet, men samler seg til et bilde av inkonsekvent herding:
- F3 —
readme.htmltilgjengelig (Lav) på alle tre kurs-nettsteder, avslører WordPress 7.0. Hovedsiden blokkerer den. Low - F4 — Manglende sikkerhetsheadere (Lav): hverken hovedsiden eller
kurs-nettstedene sender HSTS, CSP,
X-Frame-OptionsellerX-Content-Type-Options. Med TLS 1.3 påkrevd er HSTS trygt å slå på. Low - F8/F9 — E-post/DNS: Tjall AS har faktisk et sterkt e-postoppsett —
SPF med
-allhardfail, DKIM, DMARC medsp=rejectog streng alignment. Det eneste som mangler er at hoveddomenets DMARC-policy står påp=quarantine(ikkereject), og at domenet mangler MTA-STS, TLS-RPT og DNSSEC. Low - F10 — SSH eksponert (Lav): port 22 åpen mot internett på matomo- og nettsideopplæring-droplets. OpenSSH-versjonene er oppdaterte, og ingen passordgjetting ble utført. Anbefaling: kun nøkkelbasert auth, kilde-IP-begrensning, fail2ban — samme port-filtrering som Cloudways-hostene. Low
Verktøy og hvem som kjørte hva
- Rekognosering:
crt.sh-oppslag og passiv DNS — kjørt av agent. Jeg validerte subdomenkartet manuelt mot kundens bekreftede eierliste. - Nettverk:
nmap-sweep mot alle IP-er — agent-generert kommando, jeg satte scope og port-rekkevidde. - Fingerprinting:
whatwebog manuellecurl-oppslag — agent. - WordPress-sårbarhet:
wpscan --api-token <REDACTED> --enumerate vpmot kurs/seokurs — agent kjørte, jeg leste CVE-listen og vurderte alvorlighetsgrad. - Brukerenumerering (F1): REST- og author-arkiv-oppslag — agent genererte, jeg verifiserte manuelt mot hovedsiden som negativ kontroll.
- Autentiseringsomgåelse (F1-utnyttelse): jeg kuraterte passordlisten og
satte ratelimit; agenten kjørte løkken mot
wp-login.php. - TabMo env.js (F6): agent hentet og parset; jeg klassifiserte tenant-listen som en leverandørlekkasje og forkastet SPA-fallback-treffene.
- Verifisering av falske positiver: Matomo
config.ini.php, SPA-fallback pådsp.tjallas.no, M365-realm — alt verifisert manuelt av meg.
Hva jeg lærte
- Ett herdingsnivå per kunde er en illusjon. Hovedsiden var herdet, men kurs-subdomenene på egne droplets var det ikke. Angrepsflaten er den svakeste lenken, ikke den sterkeste. Sammenlign med mønsteret i da WordPress holdt men e-posten åpnet døren — det er den samme asymmetrien: ett lag holdt, et annet raste.
- Brukernavn-lekkasje er ikke et brudd i seg selv, men det senker
terskelen for alt annet. At
larsbgikk igjen på tre installasjoner gjorde det til et smalt, realistisk mål. Innloggingsbeskyttelsen holdt — men det betyr bare at neste angrepsvektor (phishing, credential stuffing fra en annen lekk) får jobben gjort lettere. - Et WPScan-treff er ikke et utnyttet funn. CVE-2025-39447 og -39451 står i rapporten som versjonsbaserte, ikke som verifisert utnyttet. Det hadde vært fristende å skrive «sårbar», men uten en autentisert testkonto eller IP-allowliste var valideringen blokkert. Dokumenter det du vet, ikke det du antar.
- Tredjeparts SaaS utvider angrepsflaten mer enn kunder forventer. TabMo-lekkasjen berørte alle TabMos kunder, ikke bare Tjall AS — men Tjall AS er den som må rapportere den, fordi det er deres domene som peker dit. Se også WP Defender-løpet for et beslektet poeng om at skanneren bare er så god som den som tolker den.
- Den negative kontrollen er alvorlighetsgraden. F1 ble et reelt
Medium-funn fordi hovedsiden returnerte
401/404på samme oppslag — det beviser at kontrollen finnes og at satellittene mangler den. Uten den negative kontrollen er «et endepunkt returnerte 200» bare støy.
Veien videre
Det åpenbare neste steget for kunden er å bringe kurs-nettstedene opp på
samme herdingsnivå som hovedhosten — enten ved å legge dem bak samme
WAF-lag, eller ved å herd dem individuelt (blokkere bruker-enumerering,
oppdatere Crocoblock-plugins, legge på sikkerhetsheadere, begrense
readme.html). For meg som tester er lærdommen å be om IP-allowliste
før jeg begynner å laste, ikke etter at WAF-en har bannet meg — jeg
brukte tid på å finne ut at kurs-hostene også lå bak Cloudways-bann, noe jeg
kunne ha visst fra start med en koordinert allowliste.