Detection rules › Panther
AWS Network ACL Restricts SSH
SSH access should only be granted from protected network CIDR ranges.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement | T1021 Remote Services |
Rule body yaml
AnalysisType: policy
Filename: aws_network_acl_restricted_ssh.py
PolicyID: "AWS.NetworkACL.RestrictedSSH"
DisplayName: "AWS Network ACL Restricts SSH"
Enabled: true
ResourceTypes:
- AWS.EC2.NetworkACL
Tags:
- AWS
- Panther
- Lateral Movement:Remote Services
Reports:
MITRE ATT&CK:
- TA0008:T1021
Severity: High
Description: >
SSH access should only be granted from protected network CIDR ranges.
Runbook: >
Remove the NACL rule granting unprotected SSH access.
Reference: https://docs.aws.amazon.com/vpc/latest/userguide/vpc-recommended-nacl-rules.html
Tests:
- Name: Secure VPC SSH Access
ExpectedResult: true
Resource:
{
"AccountId": "123456789012",
"Region": "ap-southeast-2",
"ARN": "arn:aws:ec2:ap-southeast-2:123456789012:network-acl/acl-111222333",
"ID": "acl-111222333",
"Tags": {},
"ResourceID": "arn:aws:ec2:ap-southeast-2:123456789012:network-acl/acl-111222333",
"ResourceType": "AWS.EC2.NetworkACL",
"TimeCreated": null,
"Associations":
[
{
"NetworkAclAssociationId": "aclassoc-111222333",
"NetworkAclId": "acl-111222333",
"SubnetId": "subnet-111222333",
},
],
"Entries":
[
{
"CidrBlock": "8.8.8.8/32",
"Egress": false,
"IcmpTypeCode": null,
"Ipv6CidrBlock": null,
"PortRange": { "From": 22, "To": 22 },
"Protocol": "-1",
"RuleAction": "allow",
"RuleNumber": 99,
},
{
"CidrBlock": "0.0.0.0/0",
"Egress": true,
"IcmpTypeCode": null,
"Ipv6CidrBlock": null,
"PortRange": { "From": 22, "To": 22 },
"Protocol": "-1",
"RuleAction": "allow",
"RuleNumber": 100,
},
],
"IsDefault": true,
"NetworkAclId": "acl-111222333",
"OwnerId": "123456789012",
"VpcId": "vpc-6aa60b12",
}
- Name: Global VPC SSH Access
ExpectedResult: false
Resource:
{
"AccountId": "123456789012",
"Region": "ap-southeast-2",
"ARN": "arn:aws:ec2:ap-southeast-2:123456789012:network-acl/acl-111222333",
"ID": "acl-111222333",
"Tags": {},
"ResourceID": "arn:aws:ec2:ap-southeast-2:123456789012:network-acl/acl-111222333",
"ResourceType": "AWS.EC2.NetworkACL",
"TimeCreated": null,
"Associations":
[
{
"NetworkAclAssociationId": "aclassoc-111222333",
"NetworkAclId": "acl-111222333",
"SubnetId": "subnet-111222333",
},
],
"Entries":
[
{
"CidrBlock": "0.0.0.0/0",
"Egress": false,
"IcmpTypeCode": null,
"Ipv6CidrBlock": null,
"PortRange": { "From": 22, "To": 22 },
"Protocol": "-1",
"RuleAction": "allow",
"RuleNumber": 100,
},
],
"IsDefault": true,
"NetworkAclId": "acl-111222333",
"OwnerId": "123456789012",
"VpcId": "vpc-6aa60b12",
}
- Name: Global IPv6 VPC SSH Access
ExpectedResult: false
Resource:
{
"AccountId": "123456789012",
"Region": "ap-southeast-2",
"ARN": "arn:aws:ec2:ap-southeast-2:123456789012:network-acl/acl-111222333",
"ID": "acl-111222333",
"Tags": {},
"ResourceID": "arn:aws:ec2:ap-southeast-2:123456789012:network-acl/acl-111222333",
"ResourceType": "AWS.EC2.NetworkACL",
"TimeCreated": null,
"Associations":
[
{
"NetworkAclAssociationId": "aclassoc-111222333",
"NetworkAclId": "acl-111222333",
"SubnetId": "subnet-111222333",
},
],
"Entries":
[
{
"CidrBlock": null,
"Egress": false,
"IcmpTypeCode": null,
"Ipv6CidrBlock": "::/0",
"PortRange": { "From": 22, "To": 22 },
"Protocol": "-1",
"RuleAction": "allow",
"RuleNumber": 100,
},
],
"IsDefault": true,
"NetworkAclId": "acl-111222333",
"OwnerId": "123456789012",
"VpcId": "vpc-6aa60b12",
}
- Name: Global VPC SSH Access From Range
ExpectedResult: false
Resource:
{
"AccountId": "123456789012",
"Region": "ap-southeast-2",
"ARN": "arn:aws:ec2:ap-southeast-2:123456789012:network-acl/acl-111222333",
"ID": "acl-111222333",
"Tags": {},
"ResourceID": "arn:aws:ec2:ap-southeast-2:123456789012:network-acl/acl-111222333",
"ResourceType": "AWS.EC2.NetworkACL",
"TimeCreated": null,
"Associations":
[
{
"NetworkAclAssociationId": "aclassoc-111222333",
"NetworkAclId": "acl-111222333",
"SubnetId": "subnet-111222333",
},
],
"Entries":
[
{
"CidrBlock": "0.0.0.0/0",
"Egress": false,
"IcmpTypeCode": null,
"Ipv6CidrBlock": null,
"PortRange": { "From": 0, "To": 1024 },
"Protocol": "-1",
"RuleAction": "allow",
"RuleNumber": 100,
},
],
"IsDefault": true,
"NetworkAclId": "acl-111222333",
"OwnerId": "123456789012",
"VpcId": "vpc-6aa60b12",
}
- Name: Global VPC SSH Access From Unrestricted Port Range
ExpectedResult: false
Resource:
{
"AccountId": "123456789012",
"Region": "ap-southeast-2",
"ARN": "arn:aws:ec2:ap-southeast-2:123456789012:network-acl/acl-111222333",
"ID": "acl-111222333",
"Tags": {},
"ResourceID": "arn:aws:ec2:ap-southeast-2:123456789012:network-acl/acl-111222333",
"ResourceType": "AWS.EC2.NetworkACL",
"TimeCreated": null,
"Associations":
[
{
"NetworkAclAssociationId": "aclassoc-111222333",
"NetworkAclId": "acl-111222333",
"SubnetId": "subnet-111222333",
},
],
"Entries":
[
{
"CidrBlock": "0.0.0.0/0",
"Egress": false,
"IcmpTypeCode": null,
"Ipv6CidrBlock": null,
"Protocol": "-1",
"RuleAction": "allow",
"RuleNumber": 100,
},
],
"IsDefault": true,
"NetworkAclId": "acl-111222333",
"OwnerId": "123456789012",
"VpcId": "vpc-6aa60b12",
}
- Name: Selective SSH Access
ExpectedResult: true
Resource:
{
"AccountId": "123456789012",
"Region": "ap-southeast-2",
"ARN": "arn:aws:ec2:ap-southeast-2:123456789012:network-acl/acl-111222333",
"ID": "acl-111222333",
"Tags": {},
"ResourceID": "arn:aws:ec2:ap-southeast-2:123456789012:network-acl/acl-111222333",
"ResourceType": "AWS.EC2.NetworkACL",
"TimeCreated": null,
"Associations":
[
{
"NetworkAclAssociationId": "aclassoc-111222333",
"NetworkAclId": "acl-111222333",
"SubnetId": "subnet-111222333",
},
],
"Entries":
[
{
"CidrBlock": "0.0.0.0/0",
"Egress": false,
"IcmpTypeCode": null,
"Ipv6CidrBlock": null,
"PortRange": null,
"Protocol": "-1",
"RuleAction": "allow",
"RuleNumber": 100,
},
{
"CidrBlock": "1.1.1.1/32",
"Egress": false,
"IcmpTypeCode": null,
"Ipv6CidrBlock": null,
"PortRange": { "From": 22, "To": 22 },
"Protocol": "-1",
"RuleAction": "allow",
"RuleNumber": 30,
},
{
"CidrBlock": "0.0.0.0/0",
"Egress": false,
"IcmpTypeCode": null,
"Ipv6CidrBlock": null,
"PortRange": { "From": 22, "To": 22 },
"Protocol": "-1",
"RuleAction": "deny",
"RuleNumber": 50,
},
],
"IsDefault": true,
"NetworkAclId": "acl-111222333",
"OwnerId": "123456789012",
"VpcId": "vpc-6aa60b12",
}
- Name: Selective SSH Access From Port Range
ExpectedResult: true
Resource:
{
"AccountId": "123456789012",
"Region": "ap-southeast-2",
"ARN": "arn:aws:ec2:ap-southeast-2:123456789012:network-acl/acl-111222333",
"ID": "acl-111222333",
"Tags": {},
"ResourceID": "arn:aws:ec2:ap-southeast-2:123456789012:network-acl/acl-111222333",
"ResourceType": "AWS.EC2.NetworkACL",
"TimeCreated": null,
"Associations":
[
{
"NetworkAclAssociationId": "aclassoc-111222333",
"NetworkAclId": "acl-111222333",
"SubnetId": "subnet-111222333",
},
],
"Entries":
[
{
"CidrBlock": "0.0.0.0/0",
"Egress": false,
"IcmpTypeCode": null,
"Ipv6CidrBlock": null,
"PortRange": null,
"Protocol": "-1",
"RuleAction": "allow",
"RuleNumber": 100,
},
{
"CidrBlock": "1.1.1.1/32",
"Egress": false,
"IcmpTypeCode": null,
"Ipv6CidrBlock": null,
"PortRange": { "From": 0, "To": 1024 },
"Protocol": "-1",
"RuleAction": "allow",
"RuleNumber": 30,
},
{
"CidrBlock": "0.0.0.0/0",
"Egress": false,
"IcmpTypeCode": null,
"Ipv6CidrBlock": null,
"PortRange": { "From": 22, "To": 22 },
"Protocol": "-1",
"RuleAction": "deny",
"RuleNumber": 50,
},
],
"IsDefault": true,
"NetworkAclId": "acl-111222333",
"OwnerId": "123456789012",
"VpcId": "vpc-6aa60b12",
}
Detection logic
Rule logic imperative Python
import ipaddress
GLOBAL_IPV6 = ipaddress.IPv6Network("::/0")
IPV6_SENTINEL = ipaddress.IPv6Network("::1/128")
def policy(resource):
ingress_entries = sorted(
(entry for entry in resource["Entries"] if not entry["Egress"]),
key=lambda x: x["RuleNumber"],
)
for entry in ingress_entries:
if (
entry.get("CidrBlock") == "0.0.0.0/0"
or ipaddress.IPv6Network(entry.get("Ipv6CidrBlock") or IPV6_SENTINEL) == GLOBAL_IPV6
) and (
not entry.get("PortRange")
or entry["PortRange"]["From"] <= 22 <= entry["PortRange"]["To"]
):
return entry["RuleAction"] == "deny"
return True
The parser cannot express this rule's logic as a field filter; the imperative Python above is the detection.