Detection rules › Panther

AWS Network ACL Restricts SSH

Severity
high
Tags
AWS, Panther, Lateral Movement:Remote Services
Reference
https://docs.aws.amazon.com/vpc/latest/userguide/vpc-recommended-nacl-rules.html
Source
github.com/panther-labs/panther-analysis

SSH access should only be granted from protected network CIDR ranges.

MITRE ATT&CK coverage

TacticTechniques
Lateral MovementT1021 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.