aws-dynamodb-put-resource-policy
dynamodb:PutResourcePolicy allows rewriting the table/stream resource policy, enabling self-grant of data-plane access or cross-account grant - a direct privilege escalation primitive.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
node_type(?resource) in [
NoSQLDatabase, Messaging]
?resource.provider_type in ['AWS::DynamoDB::Table', 'AWS::DynamoDB::GlobalTable', 'AWS::DynamoDB::Stream']
?principal has EFFECTIVE dynamodb:PutResourcePolicy on ?resource ARN
emit
| source type | Identity |
|---|---|
| target type | node_type(?resource) |
| source | ?principal |
| target | ?resource |
| permissions | dynamodb:PutResourcePolicy |
| conditions | iam_permission scp_or_org_policy |
| state logic | ACTIVE when dynamodb:PutResourcePolicy is confirmed EFFECTIVE on the table (or stream) ARN and no SCP deny exists. CONDITIONAL(scp_or_org_policy) when an SCP may restrict PutResourcePolicy but hasn't been resolved. BLOCKED by an explicit SCP or permission-boundary deny on dynamodb:PutResourcePolicy. Note: PutResourcePolicy requires a resource policy block (cannot be an empty policy — the API requires a syntactically valid policy document). The new policy takes effect immediately for subsequent API calls. An SCP with aws:ResourceTag conditions may prevent policy changes on specific tables — honor tag-based SCPs. |
Narrative
{principal.name} can rewrite the resource policy of {resource.name} (dynamodb:PutResourcePolicy), self-granting full data-plane access (GetItem, Scan, PutItem) or granting access to an external account - a direct privilege escalation without requiring an IAM role assumption.
Raw rule rules/derived/aws/dynamodb.yaml
id: aws-dynamodb-put-resource-policy
emits: CanModifyPolicy
description: "dynamodb:PutResourcePolicy allows rewriting the table/stream resource policy, enabling self-grant\
\ of data-plane access or cross-account grant \u2014 a direct privilege escalation primitive."
match:
- - principal: null
- HasPermission
- resource: null
where:
- node_type(?resource) in [NoSQLDatabase, Messaging]
- ?resource.provider_type in ['AWS::DynamoDB::Table', 'AWS::DynamoDB::GlobalTable', 'AWS::DynamoDB::Stream']
- ?principal has EFFECTIVE dynamodb:PutResourcePolicy on ?resource ARN
emit:
source_type: Identity
target_type: node_type(?resource)
source: ?principal
target: ?resource
permissions:
- dynamodb:PutResourcePolicy
conditions:
- iam_permission
- scp_or_org_policy
state_logic: "ACTIVE when dynamodb:PutResourcePolicy is confirmed EFFECTIVE on the table (or stream)\
\ ARN and no SCP deny exists. CONDITIONAL(scp_or_org_policy) when an SCP may restrict PutResourcePolicy\
\ but hasn't been resolved. BLOCKED by an explicit SCP or permission-boundary deny on dynamodb:PutResourcePolicy.\
\ Note: PutResourcePolicy requires a resource policy block (cannot be an empty policy \u2014 the API\
\ requires a syntactically valid policy document). The new policy takes effect immediately for subsequent\
\ API calls. An SCP with aws:ResourceTag conditions may prevent policy changes on specific tables\
\ \u2014 honor tag-based SCPs."
confidence: min(contributing_confidences) * 0.95
derived_from:
- ?principal HasPermission ?resource (dynamodb:PutResourcePolicy effective permission)
false_positive_note: "PutResourcePolicy is a control-plane action; it does NOT require data-plane permissions\
\ (GetItem/Scan) to grant escalation. Even a principal with no existing data-plane access who has\
\ PutResourcePolicy can escalate to full data access in one call. The policy must be syntactically\
\ valid JSON and comply with IAM resource policy limits (20 KB max). Cross-account grants via PutResourcePolicy\
\ require the principal in the foreign account to ALSO have a matching identity-policy grant in their\
\ own account (resource policy alone does not override the need for an identity-based allow in cross-account\
\ scenarios \u2014 unlike same-account resource policies that can grant access without identity policy).\
\ Model cross-account PutResourcePolicy escalation as ACTIVE for the SAME-account self-grant. CrossAccountTrust\
\ edges are emitted by the explicit normalization rule aws-dynamodb-cross-account-table-policy when\
\ the resource policy is collected. Stream vs. table targeting: when the resource is a stream (Messaging\
\ node_type), emit target_type: Messaging; when the resource is a table (NoSQLDatabase node_type),\
\ emit target_type: NoSQLDatabase."
narrative: "{principal.name} can rewrite the resource policy of {resource.name} (dynamodb:PutResourcePolicy),\
\ self-granting full data-plane access (GetItem, Scan, PutItem) or granting access to an external\
\ account \u2014 a direct privilege escalation without requiring an IAM role assumption."