{"slug": "zero-permissions-on-secrets-manager-full-access-to-your-secrets", "title": "Zero Permissions on Secrets Manager. Full Access to Your Secrets.", "summary": "A CloudGoat walkthrough by VirajMathpati demonstrates a privilege escalation chain that bypasses permission-based scanners, allowing an IAM user with zero Secrets Manager permissions to retrieve a secret via a credential-value flow through SNS, API Gateway, and Lambda. The attack exploits the absence of resource policies and the data-flow of credentials, which is invisible to per-service policy checks. The Z3 prover in the stave project confirms the chain and identifies the missing policy checks.", "body_md": "✓ Human-authored analysis; AI used for formatting and proofreading.\n\nA 2025 CloudGoat walkthrough by VirajMathpati documents a privilege escalation that no permission-based scanner catches. The principal with a synthetic IAM user named `cg-sns-user-cgidxi93qpes3g`\n\nhas zero permissions on Secrets Manager. Run their identity through IAM Access Analyzer; it confirms no access. Run it through PMapper; same answer. Run it through any policy-based review; the IAM user cannot read any secret in the account.\n\nThe user retrieves a secret in seven API calls.\n\nThe trick: the user's permissions don't connect to Secrets Manager directly. The connection is a *credential-value flow* through four services. SNS publishes an API key as a message payload. The user subscribes to the topic and receives the key in their inbox. The key authenticates to an API Gateway. The gateway's Lambda integration reads from Secrets Manager. The secret comes back as the HTTP response body.\n\nNo policy connects SNS to Secrets Manager. The connection is the credential value itself. Every step is individually authorized. The compound chain is invisible to any tool that checks permissions service-by-service.\n\n```\n[1] IAM user policy\n    Allow:  sns:Subscribe, sns:Receive, sns:ListTopics, …,\n            apigateway:GET            (Resource: *)\n    Deny:   apigateway:GET on 7 specific paths\n\n[2] SNS topic\n    Topic ARN:    public-topic-cgidxi93qpes3g\n    Topic policy: NONE (defaults to IAM-only gate)\n    Publishes:    a message containing an API Gateway key\n\n[3] API Gateway\n    Auth:         API key only (no IAM, no Cognito, no Lambda authorizer)\n    Integration:  Lambda\n\n[4] Lambda\n    Reads:        the target secret in Secrets Manager\n```\n\nEach one is fine by per-service checklist criteria. The user's IAM policy is broad but doesn't grant Secrets Manager. The SNS topic has no resource policy — the standard \"topic owner only\" default. The API Gateway uses key-only auth, which is a documented authentication option. The Lambda accesses Secrets Manager via its execution role, the standard pattern.\n\nThe bug is the connection.\n\nThe Z3 prover in `stave/examples/sns-secrets-compound-chain/z3prove/`\n\nruns four queries against the writeup configuration and four against a remediated version.\n\nThe user's IAM policy admits `sns:Subscribe`\n\non `Resource: \"*\"`\n\n. The topic has no resource policy. In AWS, the IAM identity policy is the only gate when no topic policy is configured. The user wins.\n\n```\n--- Finding 1: SNS topic subscribable, publishes credential ---\n  subscribable + sensitive topics: 1 / 1\n  verdict: SAT — witness: arn:aws:sns:us-east-1:676206926638:public-topic-cgidxi93qpes3g\n           (publishes api_key targeting arn:aws:apigateway:us-east-1::/restapis/x93anl9mj7)\n```\n\nThe data-flow annotation `publishes_credential_type=api_key`\n\non the topic asset makes this a *security* concern rather than a *subscription pattern* concern. Stave's existing broad-subscribe control checks the topic's resource policy. This issue lives in the IAM identity policy plus the absence of a topic policy plus the data-flow fact about what the topic publishes.\n\nThe user's policy grants `apigateway:GET`\n\non `Resource: \"*\"`\n\nand denies seven specific patterns:\n\n```\n{\n  \"Effect\": \"Deny\",\n  \"Action\": \"apigateway:GET\",\n  \"Resource\": [\n    \"/apikeys\",\n    \"/apikeys/*\",\n    \"/restapis/*/resources/*/methods/GET\",\n    \"/restapis/*/methods/GET\",\n    \"/restapis/*/resources/*/integration\",\n    \"/restapis/*/integration\",\n    \"/restapis/*/resources/*/methods/*/integration\"\n  ]\n}\n```\n\nSeven patterns. The author was thinking carefully. They identified specific high-sensitivity paths (API keys, method bodies, integration internals) and denied them. By any reasonable security review, this deny is well-considered.\n\nZ3 walks 24 known API Gateway management paths and finds 21 of them reachable:\n\n```\n--- API Gateway Deny coverage analysis ---\n  OPEN      : /restapis\n  OPEN      : /restapis/{id}\n  OPEN      : /restapis/{id}/resources\n  OPEN      : /restapis/{id}/resources/{resource_id}\n  OPEN      : /restapis/{id}/resources/{resource_id}/methods/{method}\n  BLOCKED   : /restapis/{id}/resources/{resource_id}/methods/{method}/integration\n  OPEN      : /restapis/{id}/stages\n  OPEN      : /restapis/{id}/stages/{stage}\n  OPEN      : /restapis/{id}/deployments\n  OPEN      : /restapis/{id}/deployments/{deployment_id}\n  OPEN      : /restapis/{id}/models\n  OPEN      : /restapis/{id}/authorizers\n  ... (more)\n```\n\nThe author blocked the API key endpoint. Reasonable. But the principal can enumerate the rest: `/restapis`\n\nlists every API ID. `/restapis/{id}/stages`\n\nlists each API's stages. `/restapis/{id}/resources`\n\nlists the resource paths. With those three calls, the attacker has the full URL. They don't need `/methods/GET`\n\n(denied) or `/integration`\n\n(denied). They have the API key from Finding 1. They invoke the URL directly.\n\nThe deny was correct *for the things the author thought to protect*. AWS API Gateway has more management endpoints than any individual reviewer holds in their head. The deny-list approach loses the race to the service surface area, just like the autoscaling iteration.\n\nThe most novel modeling in this article. The Z3 program computes five booleans:\n\n```\nhop 1: principal can subscribe to topic                       true\nhop 2: topic publishes credential                             true\nhop 3: API Gateway accepts credential, no IAM auth required   true\nhop 4: API Gateway integrates with downstream function        true\nhop 5: function reads Secrets Manager                         true\n```\n\nEach hop is a fact read from a different service's observation. The conjunction is the data-flow chain. Z3's `And`\n\nof five booleans tells us the chain is satisfiable.\n\nThis is the iteration's distinguishing feature. The five facts come from four different observation assets: the IAM user, the SNS topic, the API Gateway, the Lambda + one cross-asset claim (\"function reads Secrets Manager\"). No single CEL predicate can express this. The composition is necessarily multi-asset.\n\n```\n--- Finding 3: data-flow credential chain (5 hops) ---\n  hop 1 (principal can subscribe to topic):                 true\n  hop 2 (topic publishes credential):                       true\n  hop 3 (API Gateway accepts credential, no IAM auth):      true\n  hop 4 (API Gateway integrates with downstream function):  true\n  hop 5 (function reads Secrets Manager):                   true\n  verdict:  SAT — full credential-flow chain is reachable\n            (zero direct Secrets Manager permissions; access via\n             credential value flowing through SNS → API GW → Lambda)\n```\n\nF1 ∧ F2 ∧ F3. The complete escalation:\n\n```\nsns:ListTopics                  → discover the topic\nsns:Subscribe                   → subscribe with attacker email\n(receive SNS message)           → extract the API key\napigateway:GET /restapis        → enumerate API IDs\napigateway:GET /restapis/.../stages    → enumerate stages\napigateway:GET /restapis/.../resources → enumerate paths\ncurl https://<api>.execute-api.../user-data\n    -H \"x-api-key: <leaked-key>\"  → invoke API\n                                  → API GW invokes Lambda\n                                  → Lambda reads Secrets Manager\n                                  → secret returned in HTTP body\n```\n\nSeven calls. Three services touched directly. A fourth (Secrets Manager) reached transitively. Zero direct permissions on the target.\n\nA permission-based reasoner such as IAM Access Analyzer, Zelkova, PMapper or any policy resolver works by walking the policy graph: \"principal P has policy that grants action A on resource R.\" The graph is complete for *permission edges*. But:\n\n`sns:Subscribe`\n\n. The key is a string in a message body. Receiving the key isn't a permission grant; it's A permission graph cannot represent \"principal P *receives* credential C *via* mechanism M, and C authenticates to service S.\" That isn't a permission relationship. It's a data-flow relationship. The information is in the *configuration of what flows where*, not in *who is allowed to do what*.\n\nThis iteration extends the model to include data-flow facts as observation annotations. The Z3 program reasons over both permission facts and data-flow facts uniformly. The chain reaches a service the user has no permission on, but the user reaches the service's *output*.\n\nThree changes, each closing one finding:\n\n```\n // 1. Scope sns:Subscribe to specific topic prefixes.\n {\n-  \"Action\": \"sns:Subscribe\",\n-  \"Resource\": \"*\"\n+  \"Action\": \"sns:Subscribe\",\n+  \"Resource\": \"arn:aws:sns:us-east-1:676206926638:ops-alerts-*\"\n }\n\n // 2. Remove apigateway:GET. The user's data-science\n //    role doesn't need it. If a future use case\n //    arises, scope to the specific API ARN.\n\n // 3. Stop publishing credentials through SNS. The\n //    correct architecture is to put the API key in\n //    Secrets Manager, grant the consumer's IAM\n //    role secretsmanager:GetSecretValue on the\n //    specific secret ARN, and let the consumer\n //    retrieve it via the SDK at runtime. Messaging\n //    services are not credential transport.\n```\n\nPlus changing the API Gateway authorization to `AWS_IAM`\n\nso the resource policy and IAM identity policy both gate access, adding a defense in depth even if a future credential leak occurs.\n\nAfter remediation, all four Z3 queries return UNSAT. The chain is broken at hop 1 (the user can no longer subscribe to the credential-publishing topic).\n\nThe published configuration is a teaching exercise. CloudGoat is intentionally vulnerable. But the *shape* of the bug is real. Production AWS environments do this: a CI/CD pipeline publishes a deployment notification to SNS. The notification includes a deployment URL. The URL is a presigned S3 URL that grants read access to a build artifact. Anyone subscribed to the topic can fetch the artifact. Credential value flowing through messaging that subscribers harvest.\n\nThe architectural rule: **messaging services are not credential transport.** If a credential needs to flow between services, put it in Secrets Manager (or SSM Parameter Store) and grant the consuming service `secretsmanager:GetSecretValue`\n\non the specific secret ARN. The credential never traverses a notification path; the consumer retrieves it via SDK calls authenticated by IAM.\n\nTwo reasons:\n\n`sns:Subscribe`\n\n. Anyone with that permission joins the recipient list.When the credential is in Secrets Manager and the consumer's IAM grants `secretsmanager:GetSecretValue`\n\non a specific ARN, the access pattern is:\n\nNone of these properties hold for a credential delivered via SNS message. The audit trail shows a generic \"publish\" event; the principal that received it is a subscriber identifier, not an IAM principal; rotation requires re-publishing.\n\nRun an IAM policy review on `cg-sns-user`\n\n. The output is \"this user has SNS read access and API Gateway read access. No access to Secrets Manager.\" Correct.\n\nRun a posture scan on the SNS topic. The output is \"topic has no resource policy; access governed by IAM.\" Correct.\n\nRun an API Gateway scan. The output is \"API uses key-only auth; consider IAM authorization for defense in depth.\" Useful but not foundational.\n\nEach per-service tool is correct *for what it checks*. None of them composes the four answers into \"this principal can read your secrets.\"\n\nThe Z3 prover does. The five hops conjunct into one boolean, and the boolean returns SAT.\n\n`sns:Subscribe`\n\nin IAM identity policies is scoped to specific topic ARN patterns, not `Resource: \"*\"`\n\n`AWS_IAM`\n\nauthorization, not `API_KEY`\n\nonlyThe IAM user has no Secrets Manager permission. The secret comes back anyway. That's not a bug in any single service. It's a bug in the *composition*. The permission graph is silent. The credential graph talks. Z3 reads both.\n\nA framework benchmark in [ turbot/steampipe-mod-aws-compliance](https://hub.powerpipe.io/mods/turbot/aws_compliance) will correctly report the IAM user as having zero Secrets Manager permissions. That's what the IAM policy says, and the framework controls are accurately reading the IAM API. The verdict isn't wrong; it's just on the wrong question. The compositional finding (\"the secret comes back because a Lambda subscribed to an SNS topic the user can publish to\") isn't representable in per-resource framework controls because no framework section names \"subscribe to a topic whose subscriber holds a permission you don't.\" That's the class of finding Stave's compound + chain catalog covers. Frameworks catch what the IAM policy permits; Stave catches what the cross-service composition delivers. Both run against the same snapshot; both render in Powerpipe. Comparison:\n\n*The example is at stave/examples/sns-secrets-compound-chain/. Two binaries side by side: a CEL evaluation via pkg/stave.Apply (the existing CTL.SNS.POLICY.SUBSCRIBE.BROAD.001 per-topic control reports clean on both fixtures because the risk lives in the IAM identity policy and absence of topic policy, not in a topic-side broad subscribe) and a Z3 SAT prover that runs the four queries from this article and prints the API Gateway management deny coverage table. The Z3 binary lives in a sibling Go module so its libz3 link stays out of Stave's main vendored tree. Stave detects this pattern and 31 other H1-grounded scenarios from local AWS configuration snapshots, without cloud credentials.*", "url": "https://wpnews.pro/news/zero-permissions-on-secrets-manager-full-access-to-your-secrets", "canonical_source": "https://dev.to/bala_paranj_059d338e44e7e/zero-permissions-on-secrets-manager-full-access-to-your-secrets-49o", "published_at": "2026-08-24 11:55:27+00:00", "updated_at": "2026-08-24 12:13:38.305322+00:00", "lang": "en", "topics": ["ai-safety", "ai-ethics"], "entities": ["CloudGoat", "VirajMathpati", "AWS", "Secrets Manager", "SNS", "API Gateway", "Lambda", "Z3"], "alternates": {"html": "https://wpnews.pro/news/zero-permissions-on-secrets-manager-full-access-to-your-secrets", "markdown": "https://wpnews.pro/news/zero-permissions-on-secrets-manager-full-access-to-your-secrets.md", "text": "https://wpnews.pro/news/zero-permissions-on-secrets-manager-full-access-to-your-secrets.txt", "jsonld": "https://wpnews.pro/news/zero-permissions-on-secrets-manager-full-access-to-your-secrets.jsonld"}}