VAITP Dataset

← Back to the dataset

CVE-2026-45296

OpenReplay API allows cross-tenant access to session data via API keys.

  • CVSS 7.7
  • CWE-284
  • Authentication, Authorization, and Session Management
  • Remote

OpenReplay is a self-hosted session replay suite. Prior to 1.26.0, OpenReplay's Python API exposes several app_apikey routes that trust a caller-provided projectKey after validating only that the API key itself is valid and that the target projectKey exists. The authorization flow does not verify that the authenticated API key and the requested project belong to the same tenant. Because the public tracker design exposes projectKey to browser-side code, an attacker who owns any valid API key for their own tenant can target another tenant's project by reusing that public projectKey. The vulnerable routes allow the attacker to enumerate victim user sessions and then retrieve sensitive session event data across the tenant boundary. This vulnerability is fixed in 1.26.0.

CVSS base score
7.7
Published
2026-05-28
OWASP
A01 Broken Access Control
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Authentication, Authorization, and Session Management
Subcategory
Insecure Direct Object References (IDOR)
Accessibility scope
Remote
Impact
Information Disclosure
Affected component
OpenReplay

Solution

Upgrade OpenReplay to version 1.26.0 or later.

Vulnerable code sample

from flask import Flask, request, jsonify

# This code is a simplified representation of the vulnerability described in
# CVE-2026-45296 for educational purposes. It does not use the actual
# OpenReplay codebase but mimics the flawed authorization logic.

app = Flask(__name__)

# --- Simulated Database ---
# In a real application, this data would be in a database.

# We have two tenants: Tenant 1 (Attacker) and Tenant 2 (Victim)
API_KEYS = {
    "attacker-api-key-for-tenant-1": {"tenant_id": 1},
    "victim-api-key-for-tenant-2": {"tenant_id": 2}
}

PROJECTS = {
    "attacker-project-A1": {"tenant_id": 1},
    "victim-public-project-B1": {"tenant_id": 2} # This key is publicly exposed
}

SESSIONS = {
    "attacker-project-A1": [
        {"sessionId": "attacker-session-123", "data": "some normal data"}
    ],
    "victim-public-project-B1": [
        {"sessionId": "victim-session-xyz", "data": "sensitive user PII and session events"}
    ]
}

# --- Vulnerable API Endpoint ---

@app.route("/sessions-vulnerable", methods=["GET"])
def get_project_sessions():
    """
    This endpoint is vulnerable to CVE-2026-45296.
    An attacker can use their own valid API key to access sessions from
    another tenant's project.
    """
    auth_header = request.headers.get("Authorization")
    if not auth_header or not auth_header.startswith("Bearer "):
        return jsonify({"error": "Authorization header is missing or invalid"}), 401

    api_key = auth_header.split(" ")[1]

    # Check if the API key is valid at all.
    if api_key not in API_KEYS:
        return jsonify({"error": "Invalid API Key"}), 401

    # Get the target projectKey from the query parameters.
    # An attacker would provide the victim's public projectKey here.
    project_key = request.args.get("projectKey")
    if not project_key:
        return jsonify({"error": "projectKey parameter is required"}), 400

    # VULNERABILITY: The code checks if the API key is valid and if the project exists,
    # but it does NOT verify that the API key is authorized for the requested project's tenant.
    # It fails to check if the tenant_id of the api_key matches the tenant_id of the project_key.

    # 1. Authenticate: The API key is valid (exists in API_KEYS).
    #    -> Attacker uses "attacker-api-key-for-tenant-1", which is valid.
    # 2. Check existence: The project key is valid (exists in PROJECTS).
    #    -> Attacker uses "victim-public-project-B1", which is valid.
    # 3. Authorize: THIS STEP IS MISSING.
    #    -> The code should check if API_KEYS[api_key]['tenant_id'] == PROJECTS[project_key]['tenant_id']
    #       but it does not.

    if project_key in PROJECTS:
        # Because the cross-tenant authorization check is missing, the code
        # proceeds to fetch and return data from the victim's project.
        project_sessions = SESSIONS.get(project_key, [])
        return jsonify(project_sessions), 200
    else:
        return jsonify({"error": "Project not found"}), 404

if __name__ == '__main__':
    # To test the vulnerability:
    # 1. Run this script.
    # 2. Use a tool like curl to send the following request:
    #
    # curl -X GET 'http://127.0.0.1:5000/sessions-vulnerable?projectKey=victim-public-project-B1' \
    # -H 'Authorization: Bearer attacker-api-key-for-tenant-1'
    #
    # The response will incorrectly return the sensitive session data from the victim's project.
    app.run(debug=False)

Patched code sample

import dataclasses

# --- Mock Data Structures and Database ---
# In a real application, these would be database models.

@dataclasses.dataclass
class ApiKey:
    """Represents an API key with its associated tenant."""
    key: str
    tenant_id: int

@dataclasses.dataclass
class Project:
    """Represents a project with its associated tenant."""
    project_key: str
    tenant_id: int

# Mock database tables to simulate a multi-tenant environment.
# Tenant 1 has an API key and a project.
# Tenant 2 has only a project.
MOCK_API_KEYS_DB = {
    "key_for_tenant_1": ApiKey(key="key_for_tenant_1", tenant_id=1),
}
MOCK_PROJECTS_DB = {
    "project_key_for_tenant_1": Project(project_key="project_key_for_tenant_1", tenant_id=1),
    "project_key_for_tenant_2": Project(project_key="project_key_for_tenant_2", tenant_id=2),
}

# --- Mock Service/Database Lookups ---

def get_api_key_from_db(api_key: str) -> ApiKey | None:
    """Simulates retrieving API key details from the database."""
    return MOCK_API_KEYS_DB.get(api_key)

def get_project_from_db(project_key: str) -> Project | None:
    """Simulates retrieving project details from the database."""
    return MOCK_PROJECTS_DB.get(project_key)

# --- Fixed API Endpoint Logic ---

def get_project_sessions(api_key_string: str, project_key: str) -> dict:
    """
    This function represents an API route that retrieves session data.
    It includes the authorization fix for CVE-2026-45296.

    The vulnerable version would perform the first two checks (API key validity
    and project existence) but would fail to verify that both belong to the
    same tenant.
    """
    # Step 1: Validate the API key itself exists.
    api_key_obj = get_api_key_from_db(api_key_string)
    if not api_key_obj:
        return {"status": 401, "error": "Unauthorized: Invalid API Key"}

    # Step 2: Validate the target project exists.
    project_obj = get_project_from_db(project_key)
    if not project_obj:
        return {"status": 404, "error": "Not Found: Project does not exist"}

    # --- THE FIX ---
    # Step 3: Verify that the API key's tenant matches the project's tenant.
    # This crucial check prevents a valid API key from one tenant from
    # accessing the resources of another tenant.
    if api_key_obj.tenant_id != project_obj.tenant_id:
        return {"status": 403, "error": "Forbidden: Not authorized for this project"}
    # --- END FIX ---

    # If all checks pass, the user is authorized. Proceed to fetch data.
    return {
        "status": 200,
        "data": f"Sensitive session data for project '{project_key}'"
    }

Payload

curl -X GET "https://openreplay.victim.com/api/app/sessions?projectKey=VICTIM_PROJECT_KEY" -H "Authorization: Bearer ATTACKER_API_KEY"

Cite this entry

@misc{vaitp:cve202645296,
  title        = {{OpenReplay API allows cross-tenant access to session data via API keys.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-45296},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-45296/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::