Summary
An authenticated admin can retrieve the root user's reusable management access_token from the admin-only user listing/search APIs and then use that token in the Authorization header to access Root-only endpoints. This creates a practical admin -> root privilege-escalation chain and results in full compromise of Root-only configuration APIs.
Details
The repository defines admin and root as distinct privilege levels:
RoleAdminUser = 10
RoleRootUser = 100
The Root-only configuration API is explicitly protected with RootAuth():
GET /api/option/
PUT /api/option/
However, the admin-only user batch APIs return user objects that still include the access_token field:
GET /api/user/
GET /api/user/search
Relevant code paths:
model/user.go
User.AccessToken is JSON-serializable
GetAllUsers(...) omits password but not access_token
SearchUsers(...) omits password but not access_token
router/api.go
/api/user/ and /api/user/search are protected by AdminAuth()
/api/option/ is protected by RootAuth()
middleware/auth.go
Authorization is accepted as a management access_token
- once valid, the caller inherits the target user's role and identity
This means an ordinary admin can:
- call the admin-only user list/search API
- read the
root user's access_token from the JSON response
- replay that token in
Authorization
- successfully cross the
RootAuth() boundary and invoke Root-only APIs
This appears unintended for two reasons:
- the codebase clearly separates
admin and root
- the single-user read path already contains stronger protection:
GetUser(...) blocks admins from reading same-level or higher-privilege users
GetUserById(..., false) omits access_token
So the vulnerable behavior is specifically the batch/list/search path leaking a reusable higher-privilege credential.
PoC
Verified locally with Docker on a fresh instance.
Environment
- Image:
justsong/one-api:latest
- Fresh database
- Default bootstrap root account present
Steps
-
Start a fresh instance.
-
Log in as the default root account:
- username:
root
- password:
123456
-
Create a normal user, for example:
- username:
adm1n
- password:
password123
-
Promote that user to admin.
-
Log in as adm1n.
-
Request the admin-only user list:
GET /api/user/
Cookie: <admin session cookie>
- Observe that the response includes all listed users'
access_token values, including the root user's token. Example response fragment:
{
"id": 1,
"username": "root",
"role": 100,
"access_token": "<root_access_token>"
}
- Control check: try to access the Root-only options API using only the admin session:
GET /api/option/
Cookie: <admin session cookie>
Observed result:
{"message":"无权进行此操作,权限不足","success":false}
- Reuse the leaked root token:
GET /api/option/
Authorization: <root_access_token>
Observed result:
- request succeeds
- Root-only configuration data is returned
This proves the leaked credential is directly reusable for privilege escalation.
Impact
This is an authenticated privilege-escalation vulnerability affecting any deployment where a user has admin access but should not have root access.
Impact includes:
admin -> root privilege escalation
- unauthorized access to Root-only APIs
- exposure of sensitive global configuration
- potential unauthorized modification of system-wide settings
- compromise of the highest-privilege application account
Suggested CWE mapping:
CWE-200 — Exposure of Sensitive Information to an Unauthorized Actor
CWE-269 — Improper Privilege Management
Summary
An authenticated
admincan retrieve therootuser's reusable managementaccess_tokenfrom the admin-only user listing/search APIs and then use that token in theAuthorizationheader to access Root-only endpoints. This creates a practicaladmin -> rootprivilege-escalation chain and results in full compromise of Root-only configuration APIs.Details
The repository defines
adminandrootas distinct privilege levels:RoleAdminUser = 10RoleRootUser = 100The Root-only configuration API is explicitly protected with
RootAuth():GET /api/option/PUT /api/option/However, the admin-only user batch APIs return user objects that still include the
access_tokenfield:GET /api/user/GET /api/user/searchRelevant code paths:
model/user.goUser.AccessTokenis JSON-serializableGetAllUsers(...)omitspasswordbut notaccess_tokenSearchUsers(...)omitspasswordbut notaccess_tokenrouter/api.go/api/user/and/api/user/searchare protected byAdminAuth()/api/option/is protected byRootAuth()middleware/auth.goAuthorizationis accepted as a managementaccess_tokenThis means an ordinary admin can:
rootuser'saccess_tokenfrom the JSON responseAuthorizationRootAuth()boundary and invoke Root-only APIsThis appears unintended for two reasons:
adminandrootGetUser(...)blocks admins from reading same-level or higher-privilege usersGetUserById(..., false)omitsaccess_tokenSo the vulnerable behavior is specifically the batch/list/search path leaking a reusable higher-privilege credential.
PoC
Verified locally with Docker on a fresh instance.
Environment
justsong/one-api:latestSteps
Start a fresh instance.
Log in as the default root account:
root123456Create a normal user, for example:
adm1npassword123Promote that user to
admin.Log in as
adm1n.Request the admin-only user list:
access_tokenvalues, including therootuser's token. Example response fragment:{ "id": 1, "username": "root", "role": 100, "access_token": "<root_access_token>" }Observed result:
{"message":"无权进行此操作,权限不足","success":false}Observed result:
This proves the leaked credential is directly reusable for privilege escalation.
Impact
This is an authenticated privilege-escalation vulnerability affecting any deployment where a user has
adminaccess but should not haverootaccess.Impact includes:
admin -> rootprivilege escalationSuggested CWE mapping:
CWE-200— Exposure of Sensitive Information to an Unauthorized ActorCWE-269— Improper Privilege Management