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

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"

Indicators

These rows show field, operator, and value matches.

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"
}