Skip to main content

NL Portal Backend CVE-2026-55414

MEDIUM
Missing Authorization (CWE-862)
2026-06-19 https://github.com/nl-portal/nl-portal-backend-libraries GHSA-xm3x-9cfw-jhx4
5.3
CVSS 3.1 · GitHub Advisory
Share

Severity by source

GitHub Advisory PRIMARY
5.3 MEDIUM
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
vuln.today AI
3.7 LOW

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.

3.1 AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N
4.0 AV:N/AC:H/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N

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
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
Low
Integrity
None
Availability
None

Lifecycle Timeline

2
Source Code Evidence Fetched
Jun 19, 2026 - 14:55 vuln.today
Analysis Generated
Jun 19, 2026 - 14:55 vuln.today

DescriptionGitHub 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 with nl.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, HEAD 45abcd2). Fixed in 3.0.4.RELEASE (see Fix below).

Data flow (confirmed in source)

  1. form/.../graphql/FormDefinitionQuery.kt - @QueryMapping getFormDefinitionByObjectenApiUrl(@Argument url), no CommonGroundAuthentication parameter (same for getFormDefinitionById).
  2. form/.../service/ObjectsApiFormDefinitionService.kt - passes the URL through unvalidated.
  3. 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.
  4. 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.

Proof of concept (lab, against the real WebFlux stack)

  • An unauthenticated POST /graphql calling getFormDefinitionByObjectenApiUrl(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:

  1. 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.
  2. 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:

  • 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.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}).
  • Separately decide whether 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.

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 getFormDefinitionByObjectenApiUrl and the deprecated getFormDefinitionById were removed from both FormDefinitionQuery and the GraphQL schema.
  • getFormDefinitionByName now requires a CommonGroundAuthentication parameter (no longer public).
  • The URL-based service method 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.
  • Form definitions are now retrieved through the new authenticated query 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.
  • No resolver feeds caller-controlled input into 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.

Upgrade instructions

  • Backend: upgrade nl.nl-portal:* to 3.0.4 (or later).
  • Frontend: upgrade 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.

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.

More in Java

View all
CVE-2012-4681 CRITICAL POC
9.8 Aug 28

Oracle Java SE 7 Update 6 and earlier contains multiple sandbox bypass vulnerabilities via the ClassFinder and forName m

CVE-2015-7450 CRITICAL POC
9.8 Jan 02

Remote code execution in IBM Sterling B2B Integrator, Sterling Integrator, and Tivoli Common Reporting allows unauthenti

CVE-2013-2465 CRITICAL POC
9.8 Jun 18

Java Runtime Environment sandbox bypass via incorrect image channel verification in 2D component allows remote unauthent

CVE-2011-3544 CRITICAL POC
9.8 Oct 19

Oracle Java SE JDK/JRE 7 and 6 Update 27 and earlier allows remote code execution with complete system compromise throug

CVE-2010-1871 HIGH POC
8.8 Aug 05

JBoss Seam 2 in Red Hat JBoss EAP 4.3.0 fails to sanitize JBoss Expression Language inputs, allowing remote attackers to

CVE-2012-1723 CRITICAL POC
9.8 Jun 16

Unspecified vulnerability in the Java Runtime Environment (JRE) component in Oracle Java SE 7 update 4 and earlier, 6 up

CVE-2013-0422 CRITICAL POC
9.8 Jan 10

Multiple vulnerabilities in Oracle Java 7 before Update 11 allow remote attackers to execute arbitrary code by (1) using

CVE-2012-0507 CRITICAL POC
9.8 Jun 07

Unspecified vulnerability in the Java Runtime Environment (JRE) component in Oracle Java SE 7 Update 2 and earlier, 6 Up

CVE-2015-4852 CRITICAL POC
9.8 Nov 18

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

CVE-2012-5076 CRITICAL POC
9.8 Oct 16

Unspecified vulnerability in the Java Runtime Environment (JRE) component in Oracle Java SE 7 Update 7 and earlier allow

CVE-2017-3066 CRITICAL POC
9.8 Apr 27

Remote unauthenticated attackers can execute arbitrary code on Adobe ColdFusion servers through Java deserialization fla

CVE-2012-0391 CRITICAL POC
9.8 Jan 08

The ExceptionDelegator component in Apache Struts before 2.2.3.1 interprets parameter values as OGNL expressions during

Share

CVE-2026-55414 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy