NL Portal Backend CVE-2026-55414
MEDIUMSeverity by source
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
AC:H reflects that successful token exfiltration requires an attacker-controlled listener at the configured host - a condition beyond attacker control absent in standard deployments.
Primary rating from GitHub Advisory.
CVSS VectorGitHub Advisory
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
Lifecycle Timeline
2DescriptionGitHub Advisory
Summary
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.
Affected
nl.nl-portal:form(the public resolver / entry point) together withnl.nl-portal:objectenapi(where the host guard lives).- First shipped in 1.1.0.RELEASE (2023-10-31); the vulnerable code was introduced on 2023-08-12 (commit
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, HEAD45abcd2). Fixed in 3.0.4.RELEASE (see Fix below).
Data flow (confirmed in source)
form/.../graphql/FormDefinitionQuery.kt-@QueryMapping getFormDefinitionByObjectenApiUrl(@Argument url), noCommonGroundAuthenticationparameter (same forgetFormDefinitionById).- →
form/.../service/ObjectsApiFormDefinitionService.kt- passes the URL through unvalidated. - →
zgw/objectenapi/.../service/ObjectenApiService.ktgetObjectByUrl(url)- the only guard is host equality (URI.create(url).host == objectsApiClientConfig.url.host); no scheme/port/path check. - →
zgw/objectenapi/.../client/ObjectsApiClient.ktgetObjectByUrl(url)viawebClientWithoutBaseUrl(), which attaches the default headerAuthorization: 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.
Proof of concept (lab, against the real WebFlux stack)
- An unauthenticated
POST /graphqlcallinggetFormDefinitionByObjectenApiUrl(url: ...)executes without authentication. - With the configured Objecten-API host pointed at a mock server, an outbound request to a caller-chosen port/path on that host carried
Authorization: Token <configured-token>- confirming the token is attached to caller-influenced URLs.
Impact and severity - important limitations
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 thejava.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 islocalhost; 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 anddata.formDefinitionare all non-nullable Kotlin properties (JacksonKotlinModuleregistered). An object without a top-leveldata.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:
- the Objecten-API host shares infrastructure with an attacker-controllable endpoint (other port/path), enabling capture of the privileged token; or
- a URL-parser differential is later found that escapes the host guard.
Remediation
- Move the host validation out of
ObjectenApiService.getObjectByUrland intoObjectsApiClient.getObjectByUrlso the direct callerZakenApiService.getZaakDetailsis 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 existingObjectsApiClient.getObjectByIdpattern,/api/v2/objects/{uuid}). - Separately decide whether
getFormDefinitionByObjectenApiUrl/getFormDefinitionByIdshould remain unauthenticated. They are currently intentionally public (forms load before login); for a stricter posture, add aCommonGroundAuthenticationparameter as in the other resolvers - noting this breaks pre-login form loading.
Credit
Reported responsibly by Ray Sabee (https://whitehatsecurity.nl), independent security researcher - GitHub @raysabee.
Fix
Fixed in 3.0.4.RELEASE (commit 39ad80f, PR #700, "rework form module"):
- The unauthenticated resolvers
getFormDefinitionByObjectenApiUrland the deprecatedgetFormDefinitionByIdwere removed from bothFormDefinitionQueryand the GraphQL schema. getFormDefinitionByNamenow requires aCommonGroundAuthenticationparameter (no longer public).- The URL-based service method
findObjectsApiFormDefinitionByUrl(url)was removed and replaced bygetObjectsApiFormDefinitionById(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. - Form definitions are now retrieved through the new authenticated query
getFormDefinitionByTaskId(taskId)innl.nl-portal:taak, which authorizes the caller against the task (CommonGroundAuthentication, BSN/KVK match, else401) and derives the form-definition UUID from the task's own server-side data, not from caller input. - No resolver feeds caller-controlled input into
ObjectenApiService.getObjectByUrlanymore. Theobjectenapimodule itself was not changed; the fix lives entirely innl.nl-portal:formand the newnl.nl-portal:taakquery.
Upgrade instructions
- Backend: upgrade
nl.nl-portal:*to 3.0.4 (or later). - Frontend: upgrade
nl-portal-frontend-librariesto v3.0.3 (or later). This is required: the removed GraphQL queries (getFormDefinitionByObjectenApiUrl,getFormDefinitionById) and the now-authenticatedgetFormDefinitionByNameare a breaking change. Frontend v3.0.3 uses the new authenticatedgetFormDefinitionByTaskId/getFormDefinitionByNamequeries.
AnalysisAI
Unauthenticated callers can trigger server-side request forgery against NL Portal Backend Libraries (nl.nl-portal:form versions 1.1.0.RELEASE through 3.0.3) by invoking the public GraphQL resolvers getFormDefinitionByObjectenApiUrl or getFormDefinitionById, causing the backend to issue outbound HTTP requests bearing a privileged Objecten-API Authorization: Token header to a caller-influenced URL on the configured Objecten-API host. The SSRF is constrained to the same configured host by a host-equality guard, and arbitrary data disclosure is further limited by strict typed deserialization in Kotlin, which keeps practical real-world impact at Medium despite unauthenticated network access. A lab proof-of-concept was confirmed by the reporter against the real Spring WebFlux stack; no public exploit code has been independently identified and the vulnerability is not listed in CISA KEV.
Technical ContextAI
The affected package is nl.nl-portal:form (Maven, pkg:maven/nl.nl-portal:form), a Spring WebFlux/Kotlin GraphQL module used in the Dutch NL Portal civic services platform. The root cause is CWE-862 (Missing Authorization): the /graphql endpoint is declared permitAll() in OauthSecurityAutoConfiguration.kt, and Spring's WebFlux security only enforces authentication on resolvers that declare a CommonGroundAuthentication parameter. The two vulnerable resolvers - getFormDefinitionByObjectenApiUrl(@Argument url) and the deprecated getFormDefinitionById(@Argument id) - declare no such parameter and carry no @PreAuthorize annotation, so they are reachable without any credentials. The data flow passes the caller-supplied URL through ObjectsApiFormDefinitionService without validation into ObjectenApiService.getObjectByUrl, which applies a host-equality guard only (URI.create(url).host == objectsApiClientConfig.url.host), with no scheme, port, or path validation. ObjectsApiClient then issues the request via webClientWithoutBaseUrl(), which attaches Authorization: Token <token> to the fully caller-supplied URL. A secondary unguarded call path exists in ZakenApiService.getZaakDetails, which calls objectsApiClient.getObjectByUrl directly, bypassing the service-level host guard; that path is currently only reachable via an authenticated resolver with server-derived URLs and is not an independent unauthenticated vector.
RemediationAI
Upgrade all nl.nl-portal:* backend library artifacts to version 3.0.4.RELEASE or later, as detailed in PR #700 (https://github.com/nl-portal/nl-portal-backend-libraries/pull/700). The fix removes the unauthenticated resolvers getFormDefinitionByObjectenApiUrl and getFormDefinitionById entirely from FormDefinitionQuery and the GraphQL schema, replaces URL-based form lookup with UUID-based fetching via the fixed /api/v2/objects/{uuid} path (eliminating any caller-supplied URL from the data flow), and requires CommonGroundAuthentication on all remaining form definition queries. The frontend library nl-portal-frontend-libraries must simultaneously be upgraded to v3.0.3 or later - this is a breaking API change, as the removed GraphQL operations and the now-authenticated getFormDefinitionByName require the frontend to migrate to the new getFormDefinitionByTaskId and authenticated getFormDefinitionByName queries. If immediate upgrade is not feasible, a compensating control is to place a WAF or API gateway rule blocking POST requests to /graphql that name the operations getFormDefinitionByObjectenApiUrl or getFormDefinitionById in the request body; note this will break pre-login form rendering for end users until the upgrade is complete.
Oracle Java SE 7 Update 6 and earlier contains multiple sandbox bypass vulnerabilities via the ClassFinder and forName m
Remote code execution in IBM Sterling B2B Integrator, Sterling Integrator, and Tivoli Common Reporting allows unauthenti
Java Runtime Environment sandbox bypass via incorrect image channel verification in 2D component allows remote unauthent
Oracle Java SE JDK/JRE 7 and 6 Update 27 and earlier allows remote code execution with complete system compromise throug
JBoss Seam 2 in Red Hat JBoss EAP 4.3.0 fails to sanitize JBoss Expression Language inputs, allowing remote attackers to
Unspecified vulnerability in the Java Runtime Environment (JRE) component in Oracle Java SE 7 update 4 and earlier, 6 up
Multiple vulnerabilities in Oracle Java 7 before Update 11 allow remote attackers to execute arbitrary code by (1) using
Unspecified vulnerability in the Java Runtime Environment (JRE) component in Oracle Java SE 7 Update 2 and earlier, 6 Up
The WLS Security component in Oracle WebLogic Server 10.3.6.0, 12.1.2.0, 12.1.3.0, and 12.2.1.0 allows remote attackers
Unspecified vulnerability in the Java Runtime Environment (JRE) component in Oracle Java SE 7 Update 7 and earlier allow
Remote unauthenticated attackers can execute arbitrary code on Adobe ColdFusion servers through Java deserialization fla
The ExceptionDelegator component in Apache Struts before 2.2.3.1 interprets parameter values as OGNL expressions during
Same weakness CWE-862 – Missing Authorization
View allSame technique Authentication Bypass
View allShare
External POC / Exploit Code
Leaving vuln.today
GHSA-xm3x-9cfw-jhx4