Detection rules › Panther

Panther rules: system

EKS Audit Log Reporting system Namespace is Used From A Public IP

#
Severity
informational
Entities
aws_account_ids, aws_arns, ip_addresses, usernames
Log types
Amazon.EKS.Audit
Tags
EKS, Initial Access, Lateral Movement, Exploit Public-Facing Application, Remote Services, Cloud Services
Reference
docs.aws.amazon.com
Source
github.com/panther-labs/panther-analysis

This detection identifies if an activity is recorded in the Kubernetes audit log where the user:username attribute begins with "system:" or "eks:" and the requests originating IP Address is a Public IP Address

MITRE ATT&CK coverage

Detection logic

from ipaddress import ip_address

from panther_aws_helpers import eks_panther_obj_ref
from panther_ipinfo_helpers import get_ipinfo_asn
from panther_misp_helpers import get_misp_warning_lists

# Explicitly ignore eks:node-manager and eks:addon-manager
#  which are run as Lambdas and originate from public IPs
AMZ_PUBLICS = {"eks:addon-manager", "eks:node-manager"}


def is_aws_infrastructure_ip(event):
    """Check if the source IP is from AWS infrastructure based on enrichment data."""
    p_eks = eks_panther_obj_ref(event)
    source_ip = p_eks.get("sourceIPs", [None])[0]

    if not source_ip:
        return False

    # Check MISP warning lists for AWS IP ranges
    misp_data = get_misp_warning_lists(event)
    if misp_data and misp_data.has_warning_list_id(source_ip, "amazon-aws"):
        return True

    # Check ipinfo ASN for Amazon using helper class
    ipinfo_asn_data = get_ipinfo_asn(event)
    if ipinfo_asn_data:
        asn_value = ipinfo_asn_data.asn("sourceIPs")[0]
        domain_value = ipinfo_asn_data.domain("sourceIPs")[0]
        if asn_value == "AS16509" and domain_value == "amazon.com":
            return True

    return False


def is_legitimate_eks_node(event):
    """Check if this is a legitimate EKS node based on username and user groups."""
    p_eks = eks_panther_obj_ref(event)
    actor = p_eks.get("actor", "")

    # Check if it's a system node
    if actor.startswith("system:node:"):
        user_groups = event.deep_get("user", "groups", default=[])
        # Legitimate EKS nodes should be in system:nodes and system:authenticated groups
        return "system:nodes" in user_groups and "system:authenticated" in user_groups

    return False


# Alert if
#   the username starts ( with system: or eks: )
#   and
#   sourceIPs[0] is a Public Address
#   but exclude legitimate EKS nodes from AWS infrastructure IPs
def rule(event):
    if event.get("stage", "") != "ResponseComplete":
        return False
    # We explicitly ignore 403 here. There is another
    #  detection that monitors for 403 volume-by-originating-ip
    if event.get("responseStatus", {}).get("code", 0) == 403:
        return False

    p_eks = eks_panther_obj_ref(event)

    # Ignore AWS managed services (addon-manager, node-manager)
    if (
        p_eks.get("actor") in AMZ_PUBLICS
        and ":assumed-role/AWSWesleyClusterManagerLambda"
        in event.deep_get("user", "extra", "arn", default=["not found"])[0]
    ):
        return False

    if is_legitimate_eks_node(event) and is_aws_infrastructure_ip(event):
        return False

    # Check if this is a system or EKS user from a public IP
    actor = p_eks.get("actor", "")
    if (actor.startswith("system:") or actor.startswith("eks:")) and ip_address(
        p_eks.get("sourceIPs")[0]
    ).is_global:
        return True

    return False


# If not defined, defaults to the rule display name or rule ID.
def title(event):
    p_eks = eks_panther_obj_ref(event)
    return (
        f"[{p_eks.get('actor')}] executed [{p_eks.get('verb')}] "
        f"for resource [{p_eks.get('resource')}] "
        f"in ns [{p_eks.get('ns')}] on "
        f"[{p_eks.get('p_source_label')}] from "
        f"[{p_eks.get('sourceIPs')[0]}]"
    )


def dedup(event):
    p_eks = eks_panther_obj_ref(event)
    return f"{p_eks.get('p_source_label')}_eks_system_namespace_{p_eks.get('sourceIPs')[0]}"


def alert_context(event):
    p_eks = eks_panther_obj_ref(event)
    mutable_event = event.to_dict()
    mutable_event["p_eks"] = p_eks
    return dict(mutable_event)

Rule specification

AnalysisType: rule
Filename: system_namespace_public_ip.py
RuleID: "Amazon.EKS.Audit.SystemNamespaceFromPublicIP"
DisplayName: "EKS Audit Log Reporting system Namespace is Used From A Public IP"
Enabled: true
LogTypes:
  - Amazon.EKS.Audit
Tags:
  - EKS
  - Initial Access
  - Lateral Movement
  - "Exploit Public-Facing Application"
  - Remote Services
  - Cloud Services
Reports:
  MITRE ATT&CK:
    - "TA0001:T1190" # Initial Access: Exploit Public-Facing Application
    - "TA0008:T1201.007" # Lateral Movement: Remote Services: Cloud Services
Reference: https://docs.aws.amazon.com/eks/latest/userguide/network_reqs.html
Severity: Info
CreateAlert: false
Description: >
  This detection identifies if an activity is recorded in the Kubernetes audit log where
  the user:username attribute begins with "system:" or "eks:" and the requests originating
  IP Address is a Public IP Address
DedupPeriodMinutes: 1440 # 24 hours
Threshold: 1
SummaryAttributes:
  - user:username
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit events when all of the conditions below hold.

Condition

  • stage is ResponseComplete
  • responseStatus.code is not 403
  • any of:
    • user.groups does not contain system:nodes
    • user.groups does not contain system:authenticated

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Alert deduplication
repeat matches within 1d group into one alert

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
user.groupscontainssystem:authenticatedexcludes:user.groups field:"user.groups" value:"system:authenticated"
user.groupscontainssystem:nodesexcludes:user.groups field:"user.groups" value:"system:nodes"

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "annotations": {
    "authorization.k8s.io/decision": "allow",
    "authorization.k8s.io/reason": "RBAC: allowed by ClusterRoleBinding \"system:coredns\" of ClusterRole \"system:coredns\" to ServiceAccount \"coredns/kube-system\""
  },
  "apiVersion": "audit.k8s.io/v1",
  "auditID": "e2626946-90e1-4d0c-829e-ad5a78572926",
  "kind": "Event",
  "level": "Metadata",
  "objectRef": {
    "apiGroup": "discovery.k8s.io",
    "apiVersion": "v1",
    "resource": "endpointslices"
  },
  "p_any_ip_addresses": [
    "5.5.5.5"
  ],
  "p_any_usernames": [
    "system:serviceaccount:kube-system:coredns"
  ],
  "p_event_time": "2022-11-29 22:34:06.892",
  "p_log_type": "Amazon.EKS.Audit",
  "p_parse_time": "2022-11-29 22:45:25.024",
  "p_row_id": "c2a7d8dd7c858dcae0a1aaf314b2a207",
  "p_source_id": "4c859cd4-9406-469b-9e0e-c2dc1bee24fa",
  "p_source_label": "example-cluster-eks-logs",
  "requestReceivedTimestamp": "2022-11-29 22:34:06.892",
  "requestURI": "/apis/discovery.k8s.io/v1/endpointslices?allowWatchBookmarks=true&resourceVersion=2528212&timeout=5m56s&timeoutSeconds=356&watch=true",
  "responseStatus": {
    "code": 200
  },
  "sourceIPs": [
    "5.5.5.5"
  ],
  "stage": "ResponseComplete",
  "stageTimestamp": "2022-11-29 22:40:02.903",
  "user": {
    "extra": {
      "authentication_kubernetes_io_slash_pod-name": [
        "coredns-57ff979f67-bl27n"
      ],
      "authentication_kubernetes_io_slash_pod-uid": [
        "5b9488ae-5563-42aa-850b-b0d82edb3e22"
      ]
    },
    "groups": [
      "system:serviceaccounts",
      "system:serviceaccounts:kube-system",
      "system:authenticated"
    ],
    "uid": "5e4461f9-f529-4e66-9343-0b0cc9452284",
    "username": "system:serviceaccount:kube-system:coredns"
  },
  "userAgent": "Go-http-client/2.0",
  "verb": "watch"
}