# GHSA-mm2q-qcmx-gw4w: `ListServiceAccount` (`GET /rustfs/admin/v3/list-service-accounts?user=<other>`) authorizes cross-user requests against `UpdateSe

> A low-privileged user can enumerate service accounts and overwrite root secrets due to incorrect authorization checks, leading to full administrative takeover.

- **Severity:** Not scored
- **Exploited in the wild:** Not known
- **Login needed:** Any signed-in user
- **Attackable:** Over the internet
- **Victim action needed:** Not needed
- **Fix available:** Yes
- **Published:** 5 May 2026

## What is GHSA-mm2q-qcmx-gw4w?

GHSA-mm2q-qcmx-gw4w is a missing authorization vulnerability in rustfs, published on 5 May 2026. Any signed-in user can exploit it over the network, with no action from the victim.

### Official description

## Summary

`ListServiceAccount` (`GET /rustfs/admin/v3/list-service-accounts?user=<other>`) authorizes cross-user requests against `UpdateServiceAccountAdminAction` instead of `ListServiceAccountsAdminAction` at `rustfs/src/admin/handlers/service_account.rs:936`. The handler accepts the **wrong** admin action and rejects the **correct** one:

- A user granted only `admin:UpdateServiceAccount` enumerates every service account in the cluster, including the root user's (HTTP 200, full metadata).
- A user granted only `admin:ListServiceAccounts` — the permission name every IAM document treats as "list service accounts" — receives HTTP 403 AccessDenied on the same request.

Because service account access keys act as the identifier a `UpdateServiceAccount` holder needs to rotate a secret, and the `UpdateServiceAccount` handler at `rustfs/src/admin/handlers/service_account.rs:489` performs no ownership check on the target access key, leaking those access keys lets a delegated "service account updater" role overwrite `root-sa-1`'s secret, authenticate as the root user's service account, and create a persistent backdoor admin with `admin:*` + `s3:*`. Proven live end-to-end against `rustfs/rustfs:latest` (1.0.0-alpha.91, revision `d4ea14c2`) — the same revision is byte-identical on current `origin/main`.

## Vulnerability Details

- **Package:** `rustfs` (binary crate `rustfs`)
- **Affected versions:** From `0a2411f` (the initial `service_account.rs` check-in on 2026-03-15) through current HEAD `90e584a`. The vulnerable line has never been touched.
- **Fixed versions:** None
- **Vulnerable file:** `rustfs/src/admin/handlers/service_account.rs`
- **Vulnerable route:** `GET /rustfs/admin/v3/list-service-accounts?user=<other_user>` (`ListServiceAccount::call`)
- **CWE:** CWE-863 (Incorrect Authorization), chained with CWE-620 (Unverified Password Change) to reach CWE-269 (Improper Privilege Management)
- **CVSS (demonstrated chain to full admin):** `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H` = **10.0 Critical**. If scored as Scope:Unchanged the vector is `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H` = **8.8 High**. The list bug alone (no chain) is `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N` = **6.5 Medium** and is what a maintainer would rate it if the Update ownership gap is out of scope for this report.

## Two Distinct Vulnerabilities

This report documents two bugs that chain to full RustFS administrative takeover. Each is independently fixable and independently a security issue:

**Vulnerability A — Wrong action constant in ListServiceAccount (CWE-863)**
`ListServiceAccount::call` at line 936 checks `UpdateServiceAccountAdminAction` instead of `ListServiceAccountsAdminAction`. This is a copy-paste typo: the three sibling list handlers (lines 658, 799, 1095) all use the correct constant. The result is a permission inversion — the correct permission (`admin:ListServiceAccounts`) is rejected, and the wrong one (`admin:UpdateServiceAccount`) is accepted. Independently, this is a Medium-severity cross-user information disclosure.

**Vulnerability B — Missing ownership check in UpdateServiceAccount (CWE-620)**
`UpdateServiceAccount::call` at lines 489-614 authorizes on possession of `admin:UpdateServiceAccount` but never verifies the target `?accessKey=` belongs to the caller or the caller's parent. Lines 522-525 contain a commented-out `get_service_account` call that would have loaded the target for such a check. This means any holder of `admin:UpdateServiceAccount` can overwrite any service account's secret in the cluster, regardless of ownership.

**Chain (A + B) — Full RustFS administrative takeover**
Vulnerability A leaks every service account's access key (including the root administrator's). Vulnerability B allows overwriting any SA's secret given its access key. Together: a user with a single permission (`admin:UpdateServiceAccount`) enumerates the root user's SA access key via the wrong-action list bug, overwrites its secret via the ownership-free update handler, authenticates as the root user's service account, and creates a persistent backdoor admin with full RustFS administrative control.

**Authorization mismatch at a glance:**

Exact policies attached to each test identity (retrieved from running server via `GET /admin/v3/info-canned-policy`):

        legit-list-pol      -> {"Action": ["admin:ListServiceAccounts"],  "Resource": ["arn:aws:s3:::*"]}
        list-sa-probe-pol   -> {"Action": ["admin:UpdateServiceAccount"], "Resource": ["arn:aws:s3:::*"]}
        list-sa-restricted  -> {"Action": ["admin:UpdateServiceAccount"], "Resource": ["arn:aws:s3:::probe-scope/*"]}
        (zero-priv-user has no attached policy)

| Identity | Attached policy | `GET /list-service-accounts?user=rustfsadmin` | Expected |
|---|---|---|---|
| `probe-user` | `list-sa-probe-pol` (`admin:UpdateServiceAccount`) | **200** (full SA metadata) | 403 |
| `legit-list-user` | `legit-list-pol` (`admin:ListServiceAccounts`) | **403** AccessDenied | 200 |
| `restricted-update-user` | `list-sa-restricted` (`admin:UpdateServiceAccount` on `probe-scope/*`) | **200** | 403 |
| `zero-priv-user` | (none) | **403** | 403 |
| (unauthenticated) | n/a | **403** Signature required | 403 |

### Why the correct permission gets 403

The handler at line 936 calls `is_allowed` with the action `AdminAction::UpdateServiceAccountAdminAction`. The IAM engine performs an exact string match between the action in the `is_allowed` call (`admin:UpdateServiceAccount`) and the action in the caller's attached policy:

- `legit-list-user` has policy action `admin:ListServiceAccounts`. This does **not** match `admin:UpdateServiceAccount`. `is_allowed` returns false. The handler returns 403. The user who holds the *correct* permission for listing service accounts is denied.
- `probe-user` has policy action `admin:UpdateServiceAccount`. This **matches** `admin:UpdateServiceAccount`. `is_allowed` returns true. The handler returns 200. The user who holds a *different, unrelated* permission is granted access to a list endpoint.
- `restricted-update-user` has the same action string but resource-scoped to `arn:aws:s3:::probe-scope/*`. Admin-action statements skip resource matching (`crates/policy/src/policy/statement.rs:132`: `&& !self.is_admin() && !self.is_sts()`), so the resource restriction is ignored and `is_allowed` still returns true.

There is no wildcard, superset, or inheritance relationship between these two action strings. They are separate enum variants (`crates/policy/src/policy/action.rs:459-462`) with distinct `strum(serialize)` values. The IAM engine is working correctly; the handler passes the wrong action to it.

Raw request/response for `legit-list-user` (the counterintuitive 403):

        GET /rustfs/admin/v3/list-service-accounts?user=rustfsadmin  HTTP/1.1
        Authorization: AWS4-HMAC-SHA256 Credential=legit-list-user/...

        HTTP/1.1 403
        <?xml version="1.0" encoding="UTF-8"?><Error><Code>AccessDenied</Code><Message>access denied</Message></Error>

**Why this is not "working as intended":**
- `admin:UpdateServiceAccount` and `admin:ListServiceAccounts` are distinct enum variants with distinct string representations. The codebase treats them as orthogonal permissions.
- Three sibling list handlers in the same file (lines 658, 799, 1095) all check `ListServiceAccountsAdminAction`. Only line 936 deviates.
- CVE-2026-22042 / GHSA-vcwh-pff9-64cc is the maintainers' own precedent: `ImportIam` checking `ExportIAMAction` was rated Medium and fixed. The same class of bug applies here.
- A zero-privilege user (no admin policies at all) cannot exploit either vulnerability — both handlers correctly enforce their respective `is_allowed` checks. The bug is that the list handler enforces the *wrong* action constant, not that it skips enforcement entirely.

## Root Cause — Vulnerability A (Wrong Action Constant)

`ListServiceAccount::call` is registered for `GET /rustfs/admin/v3/list-s

## Which versions are affected by GHSA-mm2q-qcmx-gw4w?

2 open source packages ship an affected version, including rustfs.


| Package | Ecosystem | Affected | Fixed in |
| --- | --- | --- | --- |
| `rustfs` | crates.io | All versions | 1.0.0-alpha.98 |
| `rustfs` | crates.io | All versions | 1.0.0-alpha.98 |

## How do I fix GHSA-mm2q-qcmx-gw4w?

Upgrade rustfs to 1.0.0-alpha.98 or later.

## Is GHSA-mm2q-qcmx-gw4w being exploited?

There is no known exploitation of GHSA-mm2q-qcmx-gw4w in the wild and no public exploit code.

## Fix prompt for AI coding agents

```text
Check this codebase for GHSA-mm2q-qcmx-gw4w and fix it if it is affected.

What it is: GHSA-mm2q-qcmx-gw4w is a missing authorization vulnerability in rustfs, published on 5 May 2026. Any signed-in user can exploit it over the network, with no action from the victim.
Exploitation: There is no known exploitation of GHSA-mm2q-qcmx-gw4w in the wild and no public exploit code.

Affected packages and fixed versions:
- rustfs (crates.io): affected all versions; fixed in 1.0.0-alpha.98
- rustfs (crates.io): affected all versions; fixed in 1.0.0-alpha.98

Task:
1. Search the dependency manifests and lockfiles (for example package.json, package-lock.json, requirements.txt, poetry.lock, uv.lock, pom.xml, build.gradle, go.mod, Cargo.lock, Gemfile.lock) and any Dockerfiles for the affected packages, including transitive dependencies.
2. If an affected version is in use, upgrade it to the fixed version for its release line with the project's package manager, so the lockfile is regenerated. Prefer the smallest upgrade that fixes it.
3. If there is no fix, or the upgrade would break the build, do not guess: explain the risk and the safest workaround instead.
4. Run the tests and summarise what changed and why.
5. If nothing in the project is affected, say so and change nothing.

Reference: https://insights.sbomify.com/GHSA-mm2q-qcmx-gw4w/ (Markdown: https://insights.sbomify.com/GHSA-mm2q-qcmx-gw4w/index.md)
```

## Sources

- OSV: https://osv.dev/vulnerability/GHSA-mm2q-qcmx-gw4w

---
From sbomify Insights: https://insights.sbomify.com/GHSA-mm2q-qcmx-gw4w/
Summaries are generated and may contain mistakes; the official description above is the record.
