Title: AstrBot OpenAPI username Spoofing Allows chat-Scope API Key Holders to Execute Admin-Only Commands
Description:
AstrBot's OpenAPI chat endpoint authenticates callers with an API key, but later derives the effective command sender identity from the request-body username. A caller holding a valid chat-scope API key can submit username="astrbot" and be treated as an administrator by the message pipeline, allowing execution of admin-only builtin commands over HTTP.
The vulnerable flow starts in the API-key middleware. For /api/v1/* requests, AstrBot correctly authenticates the supplied API key and stores a trusted principal in g.username as api_key:<key_id>:
g.api_key_id = api_key.key_id
g.api_key_scopes = scopes
g.username = f"api_key:{api_key.key_id}"The issue is introduced later in OpenApiRoute.chat_send(). After the caller is already authenticated, the handler accepts a user-controlled JSON username, resolves it, and overwrites the trusted principal with that untrusted value:
original_username = g.get("username", "guest")
g.username = effective_usernameThat same effective_username is then propagated into the WebChat event conversion layer. WebChatAdapter.convert_message() creates the sender object directly from attacker-controlled queue data:
username, cid, payload = data
abm.sender = MessageMember(username, username)From there, AstrMessageEvent.get_sender_id() returns the spoofed username, and WakingCheckStage.process() uses it as the authorization identity:
for admin_id in self.ctx.astrbot_config["admins_id"]:
if str(event.get_sender_id()) == admin_id:
event.role = "admin"
breakThis makes the trust boundary incorrect: OpenAPI authentication proves possession of a valid API key, but authorization for admin-only commands is based on request-body identity. During end-to-end verification on AstrBot v4.25.5, sending POST /api/v1/chat with:
- a valid
chat-scope API key, username="astrbot", andmessage="/name pwned_alias"
caused the admin-only builtin /name command to succeed and persist an alias row with creator_sender_id="astrbot".
The impact is observable both in the streaming HTTP response and in persisted database state. A control request using username="not-admin-user" with the same command is denied, which confirms the exploit hinges on sender ID spoofing rather than ordinary command behavior.
- A running AstrBot instance exposing the dashboard and OpenAPI endpoints.
- A valid dashboard login so an API key can be created through the standard UI/API flow.
- An API key with the
chatscope. - Default or otherwise known administrator ID values. In the verified environment,
astrbotwas present inadmins_id. - Python 3 and the dependencies required by the existing PoC scripts.
- Download the PoC helper from: common.py
- Download the full end-to-end reproducer from: run_experiment.py
- Download the exploit path script from: verification_test.py
- Download the control script from: control_normal_behavior.py
- From the AstrBot repository root, start the isolated verification flow:
python3 llm-enhance/cve-finding/similar/Auth_Bypass/CVE-2026-27484-OpenAPI-Username-Spoofing-exp/run_experiment.py - The script will:
- start the real AstrBot service with an isolated
ASTRBOT_ROOT, - read the initial dashboard password from the service log,
- log in via
POST /api/auth/login, - create a real
chat-scope API key viaPOST /api/apikey/create, - send a control request using
username="not-admin-user", - send an exploit request using
username="astrbot".
- start the real AstrBot service with an isolated
- Equivalent exploit request shape:
curl -N -X POST http://127.0.0.1:6186/api/v1/chat -H 'X-API-Key: <chat-scope-api-key>' -H 'Content-Type: application/json' -d '{"username":"astrbot","session_id":"spoofed-session","message":"/name pwned_alias","enable_streaming":true}' - Observe that the exploit response contains:
UMO name set to: pwned_alias - Confirm that the alias row is persisted in
isolated_root/data/data_v4.dbwithcreator_sender_id="astrbot".
The end-to-end experiment produced the following evidence:
Control response:
您(ID: not-admin-user)的权限不足以使用此指令。通过 /sid 获取 ID 并请管理员添加。
Exploit response:
UMO name set to: pwned_alias
UMO: webchat:FriendMessage:webchat!astrbot!spoofed-session
Runtime log excerpts:
[webchat(webchat)] not-admin-user/not-admin-user: /name control_alias
触发 builtin_commands 时, 用户(ID=not-admin-user) 权限不足。
[webchat(webchat)] astrbot/astrbot: /name pwned_alias
Prepare to send - astrbot/astrbot: UMO name set to: pwned_alias
UMO: webchat:FriendMessage:webchat!astrbot!spoofed-session
Persisted database evidence:
{
"umo": "webchat:FriendMessage:webchat!astrbot!spoofed-session",
"creator_sender_id": "astrbot",
"auto_name": "astrbot",
"user_alias": "pwned_alias"
}This is an authorization bypass caused by identity spoofing across the OpenAPI-to-message-pipeline trust boundary. Any party that legitimately holds a chat-scope API key can impersonate an administrator sender ID and execute admin-only builtin commands. In the verified case, the attacker was able to run /name and persist a forged alias record as if it were created by the administrator. Any other builtin command or plugin gated by the same event.get_sender_id()-based admin check is potentially reachable through the same mechanism.
- Ecosystem: pip
- Package name: astrbot
- Affected versions: <= 4.25.5
- Patched versions:
- Severity: High
- Vector string: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:L
- CWE: CWE-290: Authentication Bypass by Spoofing
| Permalink | Description |
|---|---|
| https://github.com/AstrBotDevs/AstrBot/blob/af70151ff82fbd035f8a93d912699400e15ea5e4/astrbot/dashboard/server.py#L365-L368 | The API-key middleware stores the trusted authenticated principal as g.username = f"api_key:{api_key.key_id}". |
| https://github.com/AstrBotDevs/AstrBot/blob/af70151ff82fbd035f8a93d912699400e15ea5e4/astrbot/dashboard/routes/open_api.py#L145-L173 | chat_send() accepts request-body username and overwrites the trusted principal with g.username = effective_username, creating the spoofing boundary break. |
| https://github.com/AstrBotDevs/AstrBot/blob/af70151ff82fbd035f8a93d912699400e15ea5e4/astrbot/core/platform/sources/webchat/webchat_adapter.py#L203-L212 | The webchat adapter converts attacker-controlled queue data into MessageMember(username, username), making the spoofed identity the canonical sender ID. |
| https://github.com/AstrBotDevs/AstrBot/blob/af70151ff82fbd035f8a93d912699400e15ea5e4/astrbot/core/pipeline/waking_check/stage.py#L95-L100 | The admin authorization check trusts event.get_sender_id() and assigns event.role = "admin" when it matches admins_id. |
| https://github.com/AstrBotDevs/AstrBot/blob/af70151ff82fbd035f8a93d912699400e15ea5e4/astrbot/builtin_stars/builtin_commands/commands/name.py#L35-L42 | The verified admin-only /name command persists attacker-controlled state under the spoofed sender ID once the upstream authorization check is bypassed. |