Detection rules › Panther
AWS EC2 Multi Instance Connect
Detect when an attacker pushes an SSH public key to multiple EC2 instances.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement | T1021.005 Remote Services: VNC |
Rule body yaml
AnalysisType: rule
Filename: aws_ec2_multi_instance_connect.py
RuleID: "AWS.EC2.MultiInstanceConnect"
DisplayName: AWS EC2 Multi Instance Connect
Enabled: true
LogTypes:
- AWS.CloudTrail
Severity: Info
Reports:
MITRE ATT&CK:
- TA0008:T1021.005 # Lateral Movement: Remote Services: SSH
Stratus Red Team:
- aws.lateral-movement.ec2-instance-connect
Description: >
Detect when an attacker pushes an SSH public key to multiple EC2 instances.
DedupPeriodMinutes: 60
Threshold: 2
Reference: >
https://stratus-red-team.cloud/attack-techniques/AWS/aws.lateral-movement.ec2-instance-connect/
Runbook: |
Followup with the actor to determine if the SSH key is genuine. Consider using a different SSH key for each instance.
SummaryAttributes:
- p_any_actor_ids
- p_any_aws_account_ids
- p_any_aws_instance_ids
- p_any_usernames
Tags:
- AWS CloudTrail
- Lateral Movement
- Remote Services
- SSH
- Lateral Movement:Remote Services
Status: Experimental
Tests:
- Name: Public SSH Key Sent Successfully - Counts Toward Threshold
ExpectedResult: true
Log:
{
"p_event_time": "2025-01-13 19:58:59.000000000",
"p_log_type": "AWS.CloudTrail",
"p_parse_time": "2025-01-13 20:05:54.575569351",
"awsRegion": "us-west-2",
"eventCategory": "Management",
"eventID": "d6d05dd2-d03d-4dce-a88c-02b6a567d889",
"eventName": "SendSSHPublicKey",
"eventSource": "ec2-instance-connect.amazonaws.com",
"eventTime": "2025-01-13 19:58:59.000000000",
"eventType": "AwsApiCall",
"eventVersion": "1.08",
"managementEvent": true,
"readOnly": false,
"recipientAccountId": "111122223333",
"requestID": "25eac5d1-cd24-4156-a49b-f2bf3a20ec9d",
"requestParameters": {
"instanceId": "i-abcdef01234567890",
"instanceOSUser": "ec2-user",
"sSHPublicKey": "ssh-ed25519 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
},
"responseElements": {
"requestId": "25eac5d1-cd24-4156-a49b-f2bf3a20ec9d",
"success": true
},
"sourceIPAddress": "1.2.3.4",
"tlsDetails": {
"cipherSuite": "TLS_AES_128_GCM_SHA256",
"clientProvidedHostHeader": "ec2-instance-connect.us-west-2.amazonaws.com",
"tlsVersion": "TLSv1.3"
},
"userAgent": "stratus-red-team_8b255a24-d33d-4750-bd4b-4007124741df",
"userIdentity": {
"accessKeyId": "SAMPLE_ACCESS_KEY",
"accountId": "111122223333",
"arn": "arn:aws:sts::111122223333:assumed-role/SampleRole/bobson.dugnutt",
"principalId": "SAMPLE_PRINCIPAL_ID:bobson.dugnutt",
"sessionContext": {
"attributes": {
"creationDate": "2025-01-13T19:21:25Z",
"mfaAuthenticated": "false"
},
"sessionIssuer": {
"accountId": "111122223333",
"arn": "arn:aws:iam::111122223333:role/aws-reserved/sso.amazonaws.com/us-west-2/SampleRole",
"principalId": "SAMPLE_PRINCIPAL_ID",
"type": "Role",
"userName": "SampleRole"
},
"webIdFederationData": {}
},
"type": "AssumedRole"
}
}
- Name: Public SSH Key Sent Successfully to Same Instance - Also Counts
ExpectedResult: true
Log:
{
"p_event_time": "2025-01-13 19:58:59.000000000",
"p_log_type": "AWS.CloudTrail",
"p_parse_time": "2025-01-13 20:05:54.575569351",
"awsRegion": "us-west-2",
"eventCategory": "Management",
"eventID": "d6d05dd2-d03d-4dce-a88c-02b6a567d889",
"eventName": "SendSSHPublicKey",
"eventSource": "ec2-instance-connect.amazonaws.com",
"eventTime": "2025-01-13 19:58:59.000000000",
"eventType": "AwsApiCall",
"eventVersion": "1.08",
"managementEvent": true,
"readOnly": false,
"recipientAccountId": "111122223333",
"requestID": "25eac5d1-cd24-4156-a49b-f2bf3a20ec9d",
"requestParameters": {
"instanceId": "i-abcdef01234567890",
"instanceOSUser": "ec2-user",
"sSHPublicKey": "ssh-ed25519 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
},
"responseElements": {
"requestId": "25eac5d1-cd24-4156-a49b-f2bf3a20ec9d",
"success": true
},
"sourceIPAddress": "1.2.3.4",
"tlsDetails": {
"cipherSuite": "TLS_AES_128_GCM_SHA256",
"clientProvidedHostHeader": "ec2-instance-connect.us-west-2.amazonaws.com",
"tlsVersion": "TLSv1.3"
},
"userAgent": "stratus-red-team_8b255a24-d33d-4750-bd4b-4007124741df",
"userIdentity": {
"accessKeyId": "SAMPLE_ACCESS_KEY",
"accountId": "111122223333",
"arn": "arn:aws:sts::111122223333:assumed-role/SampleRole/bobson.dugnutt",
"principalId": "SAMPLE_PRINCIPAL_ID:bobson.dugnutt",
"sessionContext": {
"attributes": {
"creationDate": "2025-01-13T19:21:25Z",
"mfaAuthenticated": "false"
},
"sessionIssuer": {
"accountId": "111122223333",
"arn": "arn:aws:iam::111122223333:role/aws-reserved/sso.amazonaws.com/us-west-2/SampleRole",
"principalId": "SAMPLE_PRINCIPAL_ID",
"type": "Role",
"userName": "SampleRole"
},
"webIdFederationData": { }
},
"type": "AssumedRole"
}
}
- Name: Public SSH Key Not Sent Successfully
ExpectedResult: false
Log:
{
"p_event_time": "2025-01-13 20:46:57.000000000",
"p_log_type": "AWS.CloudTrail",
"p_parse_time": "2025-01-13 20:55:54.194562429",
"awsRegion": "us-west-2",
"errorCode": "AccessDenied",
"errorMessage": "User: arn:aws:sts::111122223333:assumed-role/SampleRole/bobson.dugnutt is not authorized to perform: ec2-instance-connect:SendSSHPublicKey on resource: arn:aws:ec2:us-west-2:111122223333:instance/i-abcdef01234567890 because no identity-based policy allows the ec2-instance-connect:SendSSHPublicKey action",
"eventCategory": "Management",
"eventID": "3ad527a2-8799-4561-8def-963a6dcdbbb5",
"eventName": "SendSSHPublicKey",
"eventSource": "ec2-instance-connect.amazonaws.com",
"eventTime": "2025-01-13 20:46:57.000000000",
"eventType": "AwsApiCall",
"eventVersion": "1.08",
"managementEvent": true,
"readOnly": false,
"recipientAccountId": "111122223333",
"requestID": "cd8f31b6-a30f-4b14-a06a-3b41012228f3",
"sourceIPAddress": "1.2.3.4",
"tlsDetails": {
"cipherSuite": "TLS_AES_128_GCM_SHA256",
"clientProvidedHostHeader": "ec2-instance-connect.us-west-2.amazonaws.com",
"tlsVersion": "TLSv1.3"
},
"userAgent": "stratus-red-team_95b154bb-bcb2-4582-82f4-b3dbb6cfc1d9",
"userIdentity": {
"accessKeyId": "SAMPLE_ACCESS_KEY",
"accountId": "111122223333",
"arn": "arn:aws:sts::111122223333:assumed-role/SampleRole/bobson.dugnutt",
"principalId": "SAMPLE_PRINCIPAL_ID:bobson.dugnutt",
"sessionContext": {
"attributes": {
"creationDate": "2025-01-13T20:46:53Z",
"mfaAuthenticated": "false"
},
"sessionIssuer": {
"accountId": "111122223333",
"arn": "arn:aws:iam::111122223333:role/aws-reserved/sso.amazonaws.com/us-west-2/SampleRole",
"principalId": "SAMPLE_PRINCIPAL_ID",
"type": "Role",
"userName": "SampleRole"
},
"webIdFederationData": {}
},
"type": "AssumedRole"
}
}
- Name: Unrelated Event
ExpectedResult: false
Log:
{
"eventVersion": "1.05",
"userIdentity":
{
"type": "AssumedRole",
"principalId": "1111:tester",
"arn": "arn:aws:sts::123456789012:assumed-role/tester",
"accountId": "123456789012",
"accessKeyId": "1",
"sessionContext":
{
"attributes":
{
"mfaAuthenticated": "true",
"creationDate": "2019-01-01T00:00:00Z",
},
"sessionIssuer":
{
"type": "Role",
"principalId": "1111",
"arn": "arn:aws:iam::123456789012:role/tester",
"accountId": "123456789012",
"userName": "tester",
},
},
},
"eventTime": "2019-01-01T00:00:00Z",
"eventSource": "ec2.amazonaws.com",
"eventName": "CreateNetworkAclEntry",
"awsRegion": "us-west-2",
"sourceIPAddress": "111.111.111.111",
"userAgent": "console.ec2.amazonaws.com",
"requestParameters":
{
"networkAclId": "acl-1",
"ruleNumber": 500,
"egress": true,
"ruleAction": "allow",
"icmpTypeCode": {},
"portRange": {},
"aclProtocol": "-1",
"cidrBlock": "0.0.0.0/0",
},
"responseElements": { "requestID": "1", "_return": true },
"requestID": "1",
"eventID": "1",
"eventType": "AwsApiCall",
"recipientAccountId": "123456789012",
}
Detection logic
Condition
not (errorCode is_not_null or errorMessage is_not_null)
eventName eq "SendSSHPublicKey"
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
errorCode | is_not_null | |
errorMessage | is_not_null |
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
eventName | eq |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field |
|---|
eventName |
eventSource |
awsRegion |
recipientAccountId |
sourceIPAddress |
userAgent |
userIdentity |
actor_user |