CVE-2026-55414Medium· 5.3▾ SunlitNL Portal Backend Libraries: Unauthenticated form resolver forwards the privileged Objecten-API token to a caller-supplied URL (SSRF)
▾ Sunlit zone — Low / medium · no exploitation signal
impact 29.2 · likelihood 0 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
The public GraphQL resolvers getFormDefinitionByObjectenApiUrl(url) and the deprecated getFormDefinitionById(id) fetch a caller-supplied URL using the privileged Objecten-API token. Because the /graphql endpoint is permitAll() and these resolvers do not declare a CommonGroundAuthentication parameter, an unauthenticated caller can make the backend issue an outbound request carrying Authorization: Token <objecten-api-token> to a caller-influenced URL on the configured Objecten-API host. This is a constrained (same-host) server-side request forgery combined with missing authorization.
Reported responsibly and confirmed in a local lab build against the project's own WebFlux security stack. No production system was accessed.
nl.nl-portal:form (the public resolver / entry point) together with nl.nl-portal:objectenapi (where the host guard lives).b2f87ca) and is present in every release since (1.1.x, 1.2.5, 1.3.0, the 3.0.x line, and 3.1.0 / next-minor, HEAD 45abcd2). Fixed in 3.0.4.RELEASE (see Fix below).form/.../graphql/FormDefinitionQuery.kt — @QueryMapping getFormDefinitionByObjectenApiUrl(@Argument url), no CommonGroundAuthentication parameter (same for getFormDefinitionById).form/.../service/ObjectsApiFormDefinitionService.kt — passes the URL through unvalidated.zgw/objectenapi/.../service/ObjectenApiService.kt getObjectByUrl(url) — the only guard is host equality (URI.create(url).host == objectsApiClientConfig.url.host); no scheme/port/path check.zgw/objectenapi/.../client/ObjectsApiClient.kt getObjectByUrl(url) via webClientWithoutBaseUrl(), which attaches the default header Authorization: Token <token> to the fully caller-supplied URL.Reachability: /graphql is permitAll() (core/.../security/OauthSecurityAutoConfiguration.kt). Authentication is only enforced on resolvers that declare a CommonGroundAuthentication parameter; these do not, and there is no @PreAuthorize/instrumentation safety net. The project's own GraphQLEndpointAuthorizationIT lists getFormDefinitionByObjectenApiUrl as an intentionally public operation — so the unauthenticated reachability is by design; the defect is that an intentionally-public resolver forwards a privileged token to a caller-influenced URL.
Secondary (defense-in-depth): zgw/zaken-api/.../service/ZakenApiService.kt getZaakDetails calls objectsApiClient.getObjectByUrl directly, bypassing the service-level host guard. It is currently only reachable via the authenticated ZaakQuery.zaakdetails field resolver with server-derived URLs, so it is not an unauthenticated vector today — but it shows why the guard belongs in the client.
POST /graphql calling getFormDefinitionByObjectenApiUrl(url: ...) executes without authentication.Authorization: Token <configured-token> — confirming the token is attached to caller-influenced URLs.Assessed as Medium because two code-level facts constrain practical impact:
No cross-host SSRF / token exfiltration in standard deployments. The token only travels to the configured Objecten-API host. Exfiltration requires an attacker-controlled listener at that host (a different port/path routing elsewhere) — generally not the case in managed deployments. A range of URL-parser bypass payloads was tested (userinfo @, %2f/%00/%09, backslash, #/?, double-host, trailing-dot, IDN/Unicode full-stop, fraction-slash, IPv6); no parser differential was found between the java.net.URI-based guard and the Spring/Netty URI builder used by WebClient — every payload either kept the request on the configured host or was rejected (fail-closed). The lab token-leak PoC works only because the configured host there is localhost; this does not generalize to production.
Arbitrary PII object read is blocked by typed deserialization. The response is deserialized into ObjectsApiObject<ObjectsApiFormIoFormDefinition>, whose envelope fields and data.formDefinition are all non-nullable Kotlin properties (Jackson KotlinModule registered). An object without a top-level data.formDefinition (e.g. taken/berichten/zaakdetails) fails to deserialize (DecodingException) and returns no data. The resolver can therefore only return objects shaped like a form definition — and form definitions are intentionally public (loaded pre-login).
Escalation conditions that would raise severity toward High:
ObjectenApiService.getObjectByUrl and into ObjectsApiClient.getObjectByUrl so the direct caller ZakenApiService.getZaakDetails is covered too, and tighten it from host-only to scheme + host + port + path-prefix. Preferably, do not accept a full URL at all: validate/extract the object UUID and rebuild the URL from the fixed configured base (reuse the existing ObjectsApiClient.getObjectById pattern, /api/v2/objects/{uuid}).getFormDefinitionByObjectenApiUrl / getFormDefinitionById should remain unauthenticated. They are currently intentionally public (forms load before login); for a stricter posture, add a CommonGroundAuthentication parameter as in the other resolvers — noting this breaks pre-login form loading.Reported responsibly by Ray Sabee (https://whitehatsecurity.nl), independent security researcher — GitHub @raysabee.
Fixed in 3.0.4.RELEASE (commit 39ad80f, PR #700, "rework form module"):
getFormDefinitionByObjectenApiUrl and the deprecated getFormDefinitionById were removed from both FormDefinitionQuery and the GraphQL schema.getFormDefinitionByName now requires a CommonGroundAuthentication parameter (no longer public).findObjectsApiFormDefinitionByUrl(url) was removed and replaced by getObjectsApiFormDefinitionById(objectId: UUID), which fetches by UUID via the fixed /api/v2/objects/{uuid} path (no caller-supplied URL, so no SSRF) and validates the object type against the configured form-definition object type.getFormDefinitionByTaskId(taskId) in nl.nl-portal:taak, which authorizes the caller against the task (CommonGroundAuthentication, BSN/KVK match, else 401) and derives the form-definition UUID from the task's own server-side data, not from caller input.ObjectenApiService.getObjectByUrl anymore. The objectenapi module itself was not changed; the fix lives entirely in nl.nl-portal:form and the new nl.nl-portal:taak query.nl.nl-portal:* to 3.0.4 (or later).nl-portal-frontend-libraries to v3.0.3 (or later). This is required: the removed GraphQL queries (getFormDefinitionByObjectenApiUrl, getFormDefinitionById) and the now-authenticated getFormDefinitionByName are a breaking change. Frontend v3.0.3 uses the new authenticated getFormDefinitionByTaskId / getFormDefinitionByName queries.nl.nl-portal:form >= 1.1.0, < 3.0.4Upgrade to a patched release:
nl.nl-portal:form 3.0.4Connected by shared product, vendor, weakness, or advisory.
CVE-2024-0829Medium· 4.3The Comments Extra Fields For Post,Pages and CPT plugin for WordPress is vulnerable to Missing Authorization in all versions up to, and including, 5.0
CVE-2026-11807Critical· 9.6A missing authorization vulnerability was found in the Event-Driven Ansible (EDA) websocket API
CVE-2026-49463Medium· 6.5NL Portal: Missing per-user authorization on document and decision GraphQL queries in nl-portal-backend-libraries
CVE-2026-49464High· 8.1NL Portal Backend Libraries provide backend components for Dutch government portals that interact with residents, customers, suppliers, and partner organizations
CVE-2025-13772High· 7.1GitLab has remediated an issue in GitLab EE affecting all versions from 18.4 before 18.5.5, 18.6 before 18.6.3, and 18.7 before 18.7.1 that could have allowed an authenticated user to access and utilize AI model settings from unauthorize…
CVE-2026-54683Medium· 6.5NL Portal Backend Libraries: Document contents remained downloadable by any logged-in user (incomplete fix of CVE-2026-49463)