OHIF Viewer
OIDC Credential Theft via Externally Controlled URL
A configuration shipped in the official OHIF Viewer distribution allows a crafted link to cause an authenticated user's session credentials to be sent to an attacker-controlled server. A single click by a logged-in clinician is sufficient; the attacker needs no credentials of their own. The OHIF maintainers have released a fix in v3.12.2: the affected data sources now validate the URL they fetch against an operator-configured origin allowlist before a request inherits the user's token. Deployments on v3.12.0 or earlier should upgrade to v3.12.2 or later. The issue is tracked as CVE-2026-12473 and published by CISA as advisory ICSMA-26-176-02.
Description
Two data sources registered in the configuration shipped with the official OHIF distribution read a URL from the ?url= query parameter and fetch it with no origin check, no scheme restriction, and no allowlist. Both sit on auto-generated routes (/:modeRouteName/dicomwebproxy and /:modeRouteName/dicomjson), so no deployment-specific configuration is needed for a crafted link to reach them.
DicomWebProxyDataSource/index.ts:17-34 (v3.12.0)
initialize: async ({ params, query }) => { const url = query.get('url');
if (!url) { throw new Error(`No url for '${name}'`); } else { const response = await fetch(url); const data = await response.json(); if (!data.servers?.dicomWeb?.[0]) { throw new Error('Invalid configuration returned by url'); }
dicomWebDelegate = createDicomWebApi( data.servers.dicomWeb[0].configuration || data.servers.dicomWeb[0], servicesManager ); dicomWebDelegate.initialize({ params, query }); }},The fetched document is parsed as a DICOMweb server configuration, and its wadoRoot and qidoRoot values become the endpoints for every metadata and image request that follows. The second data source takes the same parameter and parses the response as a study manifest, in which each instance.url becomes an image fetch target.
DicomJSONDataSource/index.js:64-80 (v3.12.0)
initialize: async ({ query, url }) => { if (!url) { url = query.get('url'); } let metaData = getMetaDataByURL(url);
// ...
const response = await fetch(url); const data = await response.json();Neither fetch is the disclosure by itself. The credential follows because userAuthenticationService is a global singleton and nothing binds its token to an origin or to the data source that asked for it. Once the clinician has signed in, two independent paths attach that token to whatever endpoint the active data source names.
DicomWebDataSource/index.ts:145-152 (v3.12.0)
getAuthorizationHeader = () => { const xhrRequestHeaders: HeadersInterface = {}; const authHeaders = userAuthenticationService.getAuthorizationHeader(); if (authHeaders && authHeaders.Authorization) { xhrRequestHeaders.Authorization = authHeaders.Authorization; } return xhrRequestHeaders;};That closure is re-read before each query rather than captured once (qidoDicomWebClient.headers = getAuthorizationHeader()), so the header carries a live token rather than a stale one. In parallel, the Cornerstone image loader attaches the same header to every pixel-data request through a global beforeSend hook.
initWADOImageLoader.js:28-46 (v3.12.0)
beforeSend: function (xhr) { const sourceConfig = extensionManager.getActiveDataSource()?.[0].getConfig() ?? {}; const headers = userAuthenticationService.getAuthorizationHeader();
const xhrRequestHeaders = { Accept: acceptHeader, };
if (headers) { Object.assign(xhrRequestHeaders, headers); }
return xhrRequestHeaders;},We confirmed both routes end-to-end on 2026-02-19 against the official ohif/app:v3.12.0 image with Keycloak 24.0.5 as the OIDC provider. The link carried no token of its own; the attacker server received the signed-in clinician's live Keycloak access token, once through the DICOMweb query path and once through the image-loader path.
Attacker server output (bearer token truncated)
[*] GET /config.json - serving DICOMweb configuration[!] GET /steal/studies?limit=101&offset=0&StudyInstanceUID=1.2.3 Authorization: Bearer eyJhbGciOiJSUzI1NiIs...[!] GET /steal/studies/1.2.3/series?includefield=00080021,00080031 Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
[*] GET /studies.json - serving study manifest[!] GET /steal/image.dcm Authorization: Bearer eyJhbGciOiJSUzI1NiIs...The decoded token was the live access token for the test clinician account, issued by the Keycloak realm to the ohif-viewer client, with a session identifier matching the active browser session. It is a credential, not a copy of the study data: replayed against the imaging backend it grants whatever access the clinician holds.
OHIF merged #5963 on 2026-04-21 and #5973 on 2026-04-27, introducing a secureConfigFetch helper. In an authenticated deployment, a cross-origin configuration URL must now appear in an operator-configured allowlist before it is fetched at all.
secureConfigFetch.js:82-89 (v3.12.2)
if (isAuthenticated && !isSameOrigin) { const normalizedAllowedOrigins = normalizeAllowedOrigins(allowedOrigins); if (!normalizedAllowedOrigins.length || !normalizedAllowedOrigins.includes(parsedUrl.origin)) { throw new Error( `Blocked remote configuration origin "${parsedUrl.origin}" in authenticated environment` ); }}Maintainer Joe Boccanfuso asked us to validate that fix. On 2026-04-28 we rebuilt the disclosure harness against ohif/app:v3.13.0-beta.63 and confirmed the cross-origin attack was rejected before any request was issued. The check keyed on the URL as written, though, not on the response that came back. A same-origin URL skipped the allowlist entirely and was then fetched with the browser default redirect: 'follow', so a single open redirect anywhere on the deployment origin restored the original attack. We added one to the harness as an nginx return 302 $arg_to location and captured the clinician's token again on two separate query calls. Redirect surfaces of that shape are ordinary in the tier that sits in front of a clinical viewer: oauth2-proxy style gateways, auth_request flows, SSO portals, and OIDC callback handlers.
We reported the gap the same day. Joe opened #5985 against master and #5978 as the 3.12 backport the next morning, collapsing the branching in fetchConfigJson so that one set of hardened options applies to every fetch.
secureConfigFetch.js:99-107 (v3.12.2)
async function fetchConfigJson(normalizedPolicy) { const { normalizedUrl } = normalizedPolicy; const response = await fetch(normalizedUrl, { method: 'GET', mode: 'cors', credentials: 'same-origin', redirect: 'error', referrerPolicy: 'no-referrer', });redirect: 'error' rejects the fetch on any 30x, which closes the redirect path; credentials: 'same-origin' and referrerPolicy: 'no-referrer' limit what a configuration fetch can carry outward regardless of where it lands. We built both pull-request heads locally on 2026-04-29 and re-ran the harness against each: cross-origin and same-origin-redirect both blocked, on both branches, with no regression. The backport commit 838f5195 is an ancestor of the v3.12.2 tag, so the released version carries the redirect hardening and not only the allowlist.
Impact
- An attacker who delivers a crafted link to an authenticated OHIF user can cause that user's OIDC bearer token to be transmitted to an attacker-controlled server. The captured token can be replayed against the imaging backend to access the patient studies (PHI) the user is authorised to view. Exploitation requires only a single click by an already-authenticated user and no credentials on the attacker's side.
- The link is not distinguishable from a legitimate study-sharing URL: same host, same path structure, only the data source segment differs. The affected routes exist in the official distribution image without operator configuration, so a deployment is exposed by default rather than through a misconfiguration.
Mitigation
Upgrade to OHIF Viewer v3.12.2 or later, which validates the fetched URL against an operator-configured origin allowlist and rejects redirects on the configuration fetch. Operators who need the affected data sources in authenticated deployments must additionally configure that allowlist; deployments that do not use them should remove the unused data sources from their configuration. Deployments on v3.12.0 or earlier remain affected until upgraded.
Defender's Checklist
Remove data sources you do not use.
If
dicomwebproxyanddicomjsonare not part of your workflow, delete them fromapp-config.js. The routes are generated from the registered data sources, so an unregistered source has no route.Configure the origin allowlist if you do use them.
The fix fails closed: with no allowlist configured, an authenticated cross-origin fetch is rejected. Set the allowlist explicitly to the origins you intend, rather than leaving it empty and discovering the behaviour in production.
Audit your own origin for open redirects.
Any endpoint on the viewer's origin that 302s to a caller-supplied parameter re-opens this class of attack against a same-origin URL. Check auth proxies, ingress rules, SSO portals, and OIDC callback handlers for parameterised redirect targets.
Keep viewer access tokens short-lived and audience-scoped.
A token that is only valid for the imaging backend and expires in minutes limits what a single captured credential is worth. Check whether your IdP issues the viewer a token that is also accepted elsewhere.
Severity Reasoning
References
- CVE-2026-12473
- CISA ICSMA-26-176-02 (OHIF Viewers DICOM)
- OHIF Viewer v3.12.2 (fixed release)
- OHIF Viewers PR #5963 (initial hardening)
- OHIF Viewers PR #5973 (trust policy refinement)
- OHIF Viewers PR #5985 (fetch hardening, master)
- OHIF Viewers PR #5978 (fetch hardening, 3.12 backport)
- OHIF Viewer Releases
- OHIF Viewers Repository
- ohif/app on Docker Hub
- OHIF Project
How We Can Help
Who We Are
The security researchers behind this advisory.

Dr. rer. nat. Simon Weber
Senior Pentester & MedSec Researcher
I evaluate your SaMD with the same industry-defining security insight I contributed to the BAK MV for the revision of the B3S standard.
- PhD on Hospital Cybersecurity
- Critical vulnerabilities found in hospital systems
- Alumni of THB MedSec Research Group
- gematik Security Hero

Dipl.-Inf. Volker Schönefeld
Senior Application Security Expert
As a former CTO and developer turned pentester, I work alongside your team to uncover vulnerabilities and find solutions that fit your architecture.
- 20+ years as CTO, 50M+ app downloads
- Architected and secured large-scale IoT fleets
- Certified Web Exploitation Specialist
- gematik Security Hero
Looking for a Penetration Test?
Machine Spirits specializes in security assessments for medical devices and healthcare IT. From MDR penetration testing to C5 cloud compliance, we help MedTech companies meet regulatory requirements.
