CVE case study

CVE-2026-61826: Bazarr's Plex OAuth API Lacked Authentication

Seventeen Plex API methods lacked Bazarr's normal API-key gate, exposing persistent configuration changes and, on configured instances, token-bearing server requests.

Severity
Critical (10.0)
Scoring
CVSS 3.1
Weakness
CWE-306 · CWE-352 · CWE-918
Affected
Bazarr 1.5.6 and earlier
Remediation state
Upgrade to 1.5.7-beta.14 or later
Advisory published
24 Jun 2026

Official vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H

Why it matters

Bazarr protects API methods with a per-method @authenticate decorator. The Plex OAuth module exposed seventeen HTTP methods without that decorator, so a network client could reach them without Bazarr's API key.

The exposed methods did more than return OAuth status. They could change persistent Plex integration settings and, when the integration already had a stored Plex token, make a server-side request carrying it in an X-Plex-Token header.

How I found it

I mapped the Plex OAuth API namespace method by method and compared it with authenticated Bazarr API resources. All seventeen HTTP methods in the module lacked the shared @authenticate decorator.

I then followed the unauthenticated methods to their effects. Several wrote persistent Plex settings. When the integration already had a stored token, the connection-test method could send it to a supplied server address in an X-Plex-Token header.

The authentication patch already existed on the development branch when I reported the stable-release exposure. I verified that it covered all seventeen methods, then recorded the affected and fixed versions in the advisory.

Root cause

Authentication was opt-in at each resource method, and the Plex OAuth namespace omitted the normal decorator on every method. There was no global API hook to close the gap.

The PIN-check endpoint also logged a mismatched OAuth state instead of rejecting it. That is a separate defense-in-depth concern: the published patch fixed the missing API authentication and did not change the state logic.

Source-to-sink trace

  1. 01
    Network entrybazarr/api/plex/oauth.py

    Seventeen GET and POST resource methods are registered below /api/plex without the normal authentication decorator.

  2. 02
    Missing policy gatebazarr/api/utils.py · authenticate()

    Authentication is enforced by a decorator on individual methods; the framework does not apply it globally to this namespace.

  3. 03
    Persistent state sinkswrite_config() calls in Plex OAuth resources

    Unauthenticated methods can clear, replace, or bind Plex credentials and server settings.

  4. 04
    Server-side request sinkPlexTestConnection.post()

    When a Plex token is already configured, the method requests the supplied server URI with the decrypted token in the request headers.

Safe proof of concept

Prerequisites

  • Git and Python 3.
  • Network access only to clone the public Bazarr repository; all analysis after that is local.

Step-by-step reproduction

  1. Create a unique temporary directory, clone the public repository there, and export the Plex OAuth module from the vulnerable 1.5.6 tag and the published fix commit. Keep the same shell open for the remaining commands.
  2. Run the AST census below against both files. It counts HTTP methods on Flask-RESTful resource classes and checks each method for @authenticate.
  3. Confirm that the vulnerable file contains seventeen methods and no authentication decorators.
  4. Use the targeted source search to locate persistent configuration writes and the token-bearing request sink. This is source evidence only; do not send exploit requests.
  5. Repeat the census on the fixed file and confirm that all seventeen methods carry the shared authentication gate.

Export the vulnerable and fixed modules

LAB_DIR=$(mktemp -d "\${TMPDIR:-/tmp}/bazarr-cve.XXXXXX")
git clone https://github.com/morpheus65535/bazarr.git "$LAB_DIR/bazarr"
git -C "$LAB_DIR/bazarr" show v1.5.6:bazarr/api/plex/oauth.py \
  > "$LAB_DIR/vulnerable.py"
git -C "$LAB_DIR/bazarr" show f9a8a9514944be0e7b826e67cde042cebfe9c97e:bazarr/api/plex/oauth.py \
  > "$LAB_DIR/fixed.py"

Save inside the lab directory as check-auth.py

import ast
import sys

for filename in sys.argv[1:]:
    tree = ast.parse(open(filename, encoding="utf-8").read())
    methods = 0
    authenticated = 0

    for node in tree.body:
        if not isinstance(node, ast.ClassDef):
            continue
        if not any(isinstance(base, ast.Name) and base.id == "Resource" for base in node.bases):
            continue
        for member in node.body:
            if not isinstance(member, (ast.FunctionDef, ast.AsyncFunctionDef)):
                continue
            if member.name not in {"get", "post", "patch", "delete"}:
                continue
            methods += 1
            names = {item.id for item in member.decorator_list if isinstance(item, ast.Name)}
            authenticated += int("authenticate" in names)

    print(f"{filename}: methods={methods} authenticated={authenticated}")

Run the census and inspect the named sinks

python3 "$LAB_DIR/check-auth.py" \
  "$LAB_DIR/vulnerable.py" "$LAB_DIR/fixed.py"

rg -n -C 8 \
  'class PlexTestConnection|uri.*args|get_decrypted_token|requests\.get|X-Plex-Token' \
  "$LAB_DIR/vulnerable.py"

Expected evidence

  • The vulnerable file reports methods=17 authenticated=0.
  • The fixed file reports methods=17 authenticated=17.
  • The focused extract shows the supplied URI, the stored-token precondition, the X-Plex-Token header, and the outbound request together without contacting a running service.
Negative control

The fixed source is the negative control. It has the same seventeen HTTP methods, but every method carries @authenticate.

Fixed-version re-test

On 1.5.7-beta.14 or later, the source census must show seventeen authenticated methods. An owned integration test should additionally confirm that unauthenticated calls to each method fail before any write or outbound request occurs.

Impact

A reachable Bazarr instance could accept unauthenticated changes to its Plex configuration, including logout, API-key replacement, server selection, and OAuth token binding.

When Bazarr already had a stored Plex token, the connection-test route could request an attacker-selected server URL and include the decrypted token. A fresh instance without a stored token returned 401 before the request. The exact consequence also depends on network reachability and token privileges; this article does not claim host command execution.

Fix and retest

Upgrade to Bazarr 1.5.7-beta.14 or later. The fix imports the shared authentication decorator and applies it to all seventeen Plex OAuth API methods.

Retest by enumerating every HTTP method in the namespace, not only the most obvious write route. Consider enforcing authentication at the API namespace boundary and rejecting OAuth state mismatches as additional safeguards.

Engineering lesson

When authentication is attached per method, use an automated route census so a new namespace cannot silently ship without the expected gate.

References