{"repro_id":"REPRO-2026-00219","version":7,"title":"AutoBangumi before 3.2.8 seeds a default admin account on empty databases, allowing unauthenticated users to log in with publicly known default credentials and gain full control.","repro_type":"security","status":"published","severity":"critical","cvss_score":9.3,"description":"AutoBangumi versions prior to 3.2.8 create a default administrator account when the users table is empty. The credentials are hard-coded and publicly known, enabling unauthenticated attackers to log in via the authentication endpoint and obtain full administrative access to the application.","root_cause":"# Root Cause Analysis — CVE-2026-58466\n\n## Summary\n\nAutoBangumi (a FastAPI-based bangumi/auto-download manager) seeds a hard-coded\ndefault administrator account — `admin` / `adminadmin` — whenever its users\ndatabase table is empty. The seeding happens unconditionally on startup via\n`add_default_user()` in `backend/src/module/database/user.py`. Because the\ncredentials are hard-coded and publicly visible in the source, an unauthenticated\nremote attacker can submit them to the real authentication endpoint\n(`POST /api/v1/auth/login`) on a freshly deployed instance and receive a valid\nadministrator JWT, gaining full administrative control of the application\n(RSS feed configuration, downloader configuration, server logs, and every\nauthenticated API endpoint).\n\n## Impact\n\n- **Package/component affected:** AutoBangumi backend —\n  `module/database/user.py` (`UserDatabase.add_default_user`), invoked from\n  `module/update/startup.py` (`first_run`/`start_up`) during\n  `module/core/program.py` startup.\n- **Affected versions:** AutoBangumi **< 3.2.8** (reproduced on the official\n  Docker image `ghcr.io/estrellaxd/auto_bangumi:3.2.6`).\n- **Risk level:** Critical (CVSS v3 max 9.8 / v4 9.3). CWE-1392 Use of Default\n  Credentials. Remote, unauthenticated, low complexity, full\n  confidentiality/integrity/availability impact on the application.\n- **Consequences:** Complete administrative takeover of a fresh AutoBangumi\n  instance — an attacker can read/change RSS feeds, reconfigure the downloader\n  (including credentials), read server logs, and exercise all authenticated\n  API endpoints.\n\n## Impact Parity\n\n- **Disclosed/claimed maximum impact:** Unauthenticated attacker authenticates\n  as administrator using publicly known default credentials and gains full\n  administrative access (authz bypass / default credentials).\n- **Reproduced impact from this run:** Full parity. Against the real\n  AutoBangumi 3.2.6 product (official Docker image, fresh empty database) the\n  default credentials `admin`/`adminadmin` were submitted to\n  `POST /api/v1/auth/login` and returned **HTTP 200 + an admin JWT**\n  (`sub: admin`). That JWT, sent as the `token` session cookie, granted access\n  to admin-only endpoints (`/api/v1/rss` returned the RSS config list,\n  `/api/v1/log` returned the server log stream) — proving full administrative\n  access via the remote API.\n- **Parity:** `full`\n- **Not demonstrated:** N/A — the claimed impact (default-credential\n  authentication bypass → admin access) was reproduced end-to-end through the\n  real remote API surface.\n\n## Root Cause\n\n`UserDatabase.add_default_user()` queries the `users` table; if it is empty it\ninserts a single user with hard-coded credentials:\n\n```python\n# backend/src/module/database/user.py\ndef add_default_user(self):\n    statement = select(User)\n    ...\n    if len(users) != 0:\n        return\n    user = User(username=\"admin\", password=get_password_hash(\"adminadmin\"))\n    self.session.add(user)\n    self.session.commit()\n    logger.info(\"[Database] Created default admin user\")\n```\n\nThis method is called on every startup from\n`module/update/startup.py` (`start_up` and `first_run`), which are invoked by\n`module/core/program.py` during the FastAPI lifespan startup. On a fresh\ndeployment (no persisted database) the table is empty, so the default\n`admin`/`adminadmin` account is created. The login endpoint\n(`module/api/auth.py`, `POST /api/v1/auth/login`) validates the submitted\ncredentials against the database via `UserDatabase.auth_user`, which uses\n`verify_password`. Because the seeded password hash matches `adminadmin`, the\ndefault credentials authenticate successfully and a JWT (`sub: admin`) is\nissued. The session is registered in the in-memory `active_user` map, and the\n`get_current_user` dependency accepts the `token` cookie for all protected\nendpoints.\n\nThere is no forced password change, no setup-completion gate on the login\nendpoint, and no randomization of the default password, so the publicly known\ncredentials remain valid until an administrator manually changes them through\nthe (unauthenticated-only-before-setup) setup wizard.\n\n**Fix commit referenced by the advisory:**\n`487bdfec545e805ae416e6ddf28651bd274d6a73` (\"fix(api): harden pre-auth setup\nendpoints (#1041, #1044)\"). Note: inspection of that commit shows it hardens\nthe **SSRF** behavior of the pre-auth `/setup/test-*` endpoints and qBittorrent\n5.2 login compatibility — it does **not** remove or randomize the default\n`admin`/`adminadmin` account. Correspondingly, the negative control below shows\nthe default credentials remain exploitable in 3.2.8.\n\n## Reproduction Steps\n\n1. The self-contained script is\n   **`bundle/repro/reproduction_steps.sh`**.\n2. What it does:\n   - Pulls the official AutoBangumi Docker images\n     `ghcr.io/estrellaxd/auto_bangumi:3.2.6` (vulnerable) and\n     `:3.2.8` (claimed patch).\n   - For the vulnerable version, starts **two clean instances** each with a\n     fresh empty Docker volume (so the users table is empty and\n     `add_default_user()` triggers), waits for `Application startup complete`,\n     and captures the startup log (which records\n     `[Database] Created default admin user`).\n   - Drives the **real remote API** from inside each container\n     (the Docker bridge is not routable from the sandbox host, so HTTP probes\n     are executed via `docker exec` against `127.0.0.1:7892`):\n     `POST /api/v1/auth/login` with form `username=admin&password=adminadmin`.\n   - On a 200 + JWT, reuses the `token` session cookie to call the admin-only\n     endpoints `GET /api/v1/rss` and `GET /api/v1/log`.\n   - Runs the same flow against `:3.2.8` as a negative control (two attempts).\n   - Writes all HTTP request/response artifacts to `bundle/artifacts/http/`,\n     startup logs to `bundle/logs/`, and the runtime manifest to\n     `bundle/repro/runtime_manifest.json`.\n3. Expected evidence of reproduction:\n   - Startup log line `[Database] Created default admin user`.\n   - Login response **HTTP 200** with `access_token` JWT and `set-cookie:\n     token=<JWT>; HttpOnly`.\n   - JWT payload decodes to `{\"sub\":\"admin\", ...}`.\n   - `GET /api/v1/rss` → **200** `[]` (authenticated RSS config).\n   - `GET /api/v1/log` → **200** server log text (authenticated).\n\n## Evidence\n\n- **Run log:** `bundle/logs/run.log` (full execution transcript, both runs).\n- **Startup logs:** `bundle/logs/vuln-1-startup.log`,\n  `bundle/logs/vuln-2-startup.log`, `bundle/logs/fixed-1-startup.log`,\n  `bundle/logs/fixed-2-startup.log`.\n- **HTTP artifacts:** `bundle/artifacts/http/vuln-{1,2}-login.json`,\n  `vuln-{1,2}-rss.json`, `vuln-{1,2}-log.json`,\n  `fixed-{1,2}-login.json`.\n- **Runtime manifest:** `bundle/repro/runtime_manifest.json`.\n\nKey excerpts (vulnerable 3.2.6, attempt 1):\n\nStartup seeding:\n```\n[Database] Schema version is now 9.\n[Database] Created default admin user\n[Core] No db file exists, create database file.\nApplication startup complete.\nUvicorn running on http://0.0.0.0:7892\n```\n\nLogin with default credentials (`bundle/artifacts/http/vuln-1-login.json`):\n```json\n{\n  \"method\": \"POST\",\n  \"path\": \"/api/v1/auth/login\",\n  \"status\": 200,\n  \"set_cookie\": \"token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6...}; HttpOnly; Max-Age=86400; Path=/; SameSite=lax\",\n  \"body\": \"{\\\"access_token\\\":\\\"eyJ...sub\\\":\\\"admin\\\"...\\\",\\\"token_type\\\":\\\"bearer\\\"}\"\n}\n```\nThe JWT payload decodes to `{\"sub\":\"admin\",\"exp\":...}` — authenticated as the\nadministrator.\n\nAdmin-only endpoint access with the session cookie\n(`bundle/artifacts/http/vuln-1-rss.json`, `vuln-1-log.json`):\n```json\n{ \"method\": \"GET\", \"path\": \"/api/v1/rss\", \"status\": 200, \"body\": \"[]\" }\n{ \"method\": \"GET\", \"path\": \"/api/v1/log\", \"status\": 200, \"body\": \"[2026-07-03 ...] INFO ... Version 3.2.6 ...\" }\n```\n\n**Environment:** Official Docker images on the host Docker daemon\n(client 29.1.3); AutoBangumi 3.2.6 backend (Python 3.13, uvicorn/FastAPI,\nSQLite) inside Alpine containers; webui port 7892; HTTP probes executed\nin-container via `docker exec python3`.\n\n**Negative control (3.2.8):** The 3.2.8 image **also** logs\n`[Database] Created default admin user` on a fresh database **and** accepts the\n`admin`/`adminadmin` login with **HTTP 200 + an admin JWT** in both attempts\n(`bundle/artifacts/http/fixed-{1,2}-login.json`). This indicates the\nadvisory-referenced fix commit (`487bdfec`) addresses the related SSRF issue\n(#1041) and does not remediate the hard-coded default-credentials seeding; the\n`add_default_user()` source is byte-identical from 3.2.6 through HEAD.\n\n## Recommendations / Next Steps\n\n- **Primary fix:** Do not seed a hard-coded default administrator. Either (a)\n  require the initial setup wizard to create the first admin account with a\n  user-chosen password before any login is possible, or (b) generate a random\n  one-time bootstrap password and display it once in the startup log / a\n  bootstrap file, never reusing a public constant.\n- **Defense in depth:** Gate `POST /api/v1/auth/login` behind setup completion\n  (the existing `.setup_complete` sentinel) so login is rejected until the\n  operator has finished initial configuration.\n- **Upgrade guidance:** Operators should immediately change the admin password\n  on any deployed instance and avoid exposing port 7892 to untrusted networks.\n  Treat 3.2.8 as still affected for this specific CVE until a corrected fix\n  that removes the default seeding is released.\n- **Testing:** Add a regression test asserting that a fresh empty database\n  never contains a login-capable account with a known password, and that\n  `POST /api/v1/auth/login` fails before setup completion.\n\n## Additional Notes\n\n- **Idempotency:** The script removes its containers/volumes on entry and exit\n  (`trap cleanup EXIT`) and was executed twice consecutively; both runs exited\n  `0` with identical positive evidence (`VULN_LOGIN_OK=1`,\n  `VULN_ADMIN_ACCESS_OK=1`).\n- **Networking note:** In this sandbox the Docker bridge network is not\n  routable from the host, so port-mapped requests from the host fail with\n  \"connection refused\". The script therefore performs all HTTP probes from\n  inside the container via `docker exec`, exercising the same real\n  `127.0.0.1:7892` listener that uvicorn serves — this is the genuine\n  application HTTP surface, not a mock.\n- **Scope note:** The claim surface (`api_remote`) was satisfied through the\n  real authentication endpoint and real admin API endpoints of the running\n  product; no sanitizers were used (`sanitizer_used=false`); the JWT is a real\n  HS256 token signed by the running application.\n- **Limitation:** The default credentials were located in the source\n  (`admin`/`adminadmin`) rather than supplied by the NVD entry, as the ticket\n  noted the NVD entry omits them; they are confirmed correct by the runtime\n  login success.\n","cve_id":"CVE-2026-58466","cwe_id":"CWE-1392 Use of Default Credentials","source_url":"https://nvd.nist.gov/vuln/detail/CVE-2026-58466","package":{"name":"EstrellaXD/Auto_Bangumi","ecosystem":"standalone application (Python/FastAPI)","affected_versions":"< 3.2.8","fixed_version":"3.2.8"},"reproduced_at":"2026-07-03T15:53:50.215346+00:00","duration_secs":896.0,"tool_calls":128,"handoffs":2,"total_cost_usd":2.02444937,"agent_costs":{"hypothesis_generator":0.0105576,"judge":0.023350699999999995,"repro":1.0185685499999997,"support":0.05192292,"vuln_variant":0.9200496},"cost_breakdown":{"hypothesis_generator":{"accounts/fireworks/models/glm-5p2":0.0105576},"judge":{"gpt-5.4-mini":0.023350699999999995},"repro":{"accounts/fireworks/routers/glm-5p2-fast":1.0185685499999997},"support":{"accounts/fireworks/routers/glm-5p2-fast":0.05192292},"vuln_variant":{"accounts/fireworks/routers/glm-5p2-fast":0.9200496}},"quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-07-03T15:53:51.081905+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":15207,"category":"reproduction_script"},{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":10828,"category":"analysis"},{"path":"bundle/vuln_variant/reproduction_steps.sh","filename":"reproduction_steps.sh","size":19907,"category":"reproduction_script"},{"path":"bundle/vuln_variant/rca_report.md","filename":"rca_report.md","size":16277,"category":"analysis"},{"path":"bundle/ticket.md","filename":"ticket.md","size":2126,"category":"ticket"},{"path":"bundle/ticket.json","filename":"ticket.json","size":3105,"category":"other"},{"path":"bundle/logs/pull-vuln.log","filename":"pull-vuln.log","size":232,"category":"log"},{"path":"bundle/logs/pull-fixed.log","filename":"pull-fixed.log","size":232,"category":"log"},{"path":"bundle/logs/run.log","filename":"run.log","size":16762,"category":"log"},{"path":"bundle/logs/vuln-1-startup.log","filename":"vuln-1-startup.log","size":2007,"category":"log"},{"path":"bundle/logs/vuln-2-startup.log","filename":"vuln-2-startup.log","size":2007,"category":"log"},{"path":"bundle/logs/fixed-1-startup.log","filename":"fixed-1-startup.log","size":2007,"category":"log"},{"path":"bundle/logs/fixed-2-startup.log","filename":"fixed-2-startup.log","size":2007,"category":"log"},{"path":"bundle/repro/ab_probe.py","filename":"ab_probe.py","size":1489,"category":"script"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":1360,"category":"other"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":923,"category":"other"},{"path":"bundle/logs/vuln_variant/run.log","filename":"run.log","size":2760,"category":"log"},{"path":"bundle/logs/vuln_variant/pull-auto_bangumi:3.2.6.log","filename":"pull-auto_bangumi:3.2.6.log","size":232,"category":"log"},{"path":"bundle/logs/vuln_variant/pull-auto_bangumi:latest.log","filename":"pull-auto_bangumi:latest.log","size":235,"category":"log"},{"path":"bundle/logs/vuln_variant/pull-auto_bangumi:3.3.0-beta.2.log","filename":"pull-auto_bangumi:3.3.0-beta.2.log","size":253,"category":"log"},{"path":"bundle/logs/vuln_variant/vuln-startup.log","filename":"vuln-startup.log","size":2007,"category":"log"},{"path":"bundle/logs/vuln_variant/latest-startup.log","filename":"latest-startup.log","size":2007,"category":"log"},{"path":"bundle/logs/vuln_variant/beta-startup.log","filename":"beta-startup.log","size":2106,"category":"log"},{"path":"bundle/logs/vuln_variant/setup-startup.log","filename":"setup-startup.log","size":2007,"category":"log"},{"path":"bundle/logs/vuln_variant/fixed_version.txt","filename":"fixed_version.txt","size":345,"category":"other"},{"path":"bundle/logs/vuln_variant/latest_version.txt","filename":"latest_version.txt","size":345,"category":"other"},{"path":"bundle/vuln_variant/ab_probe2.py","filename":"ab_probe2.py","size":1725,"category":"script"},{"path":"bundle/vuln_variant/artifacts/vuln-noauth-rss.json","filename":"vuln-noauth-rss.json","size":151,"category":"other"},{"path":"bundle/vuln_variant/artifacts/vuln-login.json","filename":"vuln-login.json","size":478,"category":"other"},{"path":"bundle/vuln_variant/artifacts/vuln-rss.json","filename":"vuln-rss.json","size":124,"category":"other"},{"path":"bundle/vuln_variant/artifacts/latest-noauth-rss.json","filename":"latest-noauth-rss.json","size":151,"category":"other"},{"path":"bundle/vuln_variant/artifacts/latest-login.json","filename":"latest-login.json","size":478,"category":"other"},{"path":"bundle/vuln_variant/artifacts/latest-rss.json","filename":"latest-rss.json","size":124,"category":"other"},{"path":"bundle/vuln_variant/artifacts/beta-noauth-rss.json","filename":"beta-noauth-rss.json","size":151,"category":"other"},{"path":"bundle/vuln_variant/artifacts/beta-login.json","filename":"beta-login.json","size":481,"category":"other"},{"path":"bundle/vuln_variant/artifacts/beta-rss.json","filename":"beta-rss.json","size":124,"category":"other"},{"path":"bundle/vuln_variant/artifacts/setup-status.json","filename":"setup-status.json","size":174,"category":"other"},{"path":"bundle/vuln_variant/artifacts/setup-complete.json","filename":"setup-complete.json","size":276,"category":"other"},{"path":"bundle/vuln_variant/artifacts/setup-login.json","filename":"setup-login.json","size":478,"category":"other"},{"path":"bundle/vuln_variant/artifacts/setup-rss.json","filename":"setup-rss.json","size":124,"category":"other"},{"path":"bundle/vuln_variant/artifacts/setup-oldlogin.json","filename":"setup-oldlogin.json","size":207,"category":"other"},{"path":"bundle/vuln_variant/runtime_manifest.json","filename":"runtime_manifest.json","size":2846,"category":"other"},{"path":"bundle/vuln_variant/patch_analysis.md","filename":"patch_analysis.md","size":6827,"category":"documentation"},{"path":"bundle/vuln_variant/variant_manifest.json","filename":"variant_manifest.json","size":5015,"category":"other"},{"path":"bundle/vuln_variant/validation_verdict.json","filename":"validation_verdict.json","size":4975,"category":"other"},{"path":"bundle/vuln_variant/source_identity.json","filename":"source_identity.json","size":2335,"category":"other"},{"path":"bundle/vuln_variant/root_cause_equivalence.json","filename":"root_cause_equivalence.json","size":3808,"category":"other"}]}