Detection rules › Panther
Panther rules: system
| Rule | Severity |
|---|---|
| EKS Audit Log Reporting system Namespace is Used From A Public IP | informational |
EKS Audit Log Reporting system Namespace is Used From A Public IP
#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
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Lateral Movement |
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
stageisResponseCompleteresponseStatus.codeis not403any of:
user.groupsdoes not containsystem:nodesuser.groupsdoes not containsystem:authenticated
This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
user.groups | contains | system:authenticated | excludes:user.groups field:"user.groups" value:"system:authenticated" |
user.groups | contains | system:nodes | excludes:user.groups field:"user.groups" value:"system:nodes" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
responseStatus.code | ne |
| field:"responseStatus.code" kind:ne value:"403" |
stage | eq |
| field:"stage" kind:eq value:"ResponseComplete" |
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"
}