EKS Anonymous API Access Detected


Description

This rule detects anonymous API requests made to the Kubernetes API server. In production environments, anonymous access should be disabled to prevent unauthorized access to the API server.

Query · python

from ipaddress import ip_address

from panther_aws_helpers import eks_panther_obj_ref


def rule(event):
    src_ip = event.get("sourceIPs", ["0.0.0.0"])  # nosec
    if src_ip == ["127.0.0.1"]:
        return False
    if event.get("userAgent", "") == "ELB-HealthChecker/2.0":
        try:
            if ip_address(src_ip[0]).is_private:
                return False
        except ValueError:
            pass

    # Check if the username is set to "system:anonymous", which indicates anonymous access
    if event.deep_get("user", "username") == "system:anonymous":
        return True
    return False


def title(event):
    # For INFO-level events, just group them all together since they're not that interesting
    if event.deep_get("annotations", "authorization.k8s.io/decision") != "allow":
        return "Failed Anonymous EKS Access Attempt(s) Detected"
    if event.get("requestURI") == "/version":
        return "Anonymous EKS Access to /version Endpoint Detected"
    p_eks = eks_panther_obj_ref(event)
    return (
        f"Anonymous API access detected on Kubernetes API server "
        f"from [{p_eks.get('sourceIPs')[0]}] to [{event.get('requestURI', 'NO_URI')}] "
        f"on [{p_eks.get('p_source_label')}]"
    )


def severity(event):
    if event.deep_get("annotations", "authorization.k8s.io/decision") != "allow":
        return "INFO"
    if event.get("requestURI") == "/version":
        return "INFO"
    return "DEFAULT"


def dedup(event):
    # For INFO-level events, just group them all together since they're not that interesting
    if severity(event) == "INFO":
        return "no dedup"
    p_eks = eks_panther_obj_ref(event)
    return f"anonymous_access_{p_eks.get('p_source_label')}_{event.get('userAgent')}"


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)

Analyst notes

Check the EKS cluster configuration and ensure that anonymous access to the Kubernetes API server is disabled. This can be done by verifying the API server arguments and authentication webhook configuration.

Raw source EKS Anonymous API Access Detected · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
Filename: anonymous_api_access.py
RuleID: "Amazon.EKS.AnonymousAPIAccess"
DisplayName: "EKS Anonymous API Access Detected"
Enabled: true
LogTypes:
  - Amazon.EKS.Audit
Severity: Low
Reports:
  MITRE ATT&CK:
    - "TA0001:T1190" # Initial Access: Exploit Public-Facing Application
Description: >
  This rule detects anonymous API requests made to the Kubernetes API server.
  In production environments, anonymous access should be disabled to prevent
  unauthorized access to the API server.
DedupPeriodMinutes: 60
Reference: 
  https://raesene.github.io/blog/2023/03/18/lets-talk-about-anonymous-access-to-Kubernetes/
Runbook: |
  Check the EKS cluster configuration and ensure that anonymous access
  to the Kubernetes API server is disabled. This can be done by verifying the  API
  server arguments and authentication webhook configuration.
SummaryAttributes:
  - user:username
  - p_any_ip_addresses
  - p_source_label
Tags:
  - EKS
  - Security Control
  - API
  - Initial Access:Exploit Public-Facing Application
Tests:
  - Name: Anonymous API Access
    ExpectedResult: true
    Log:
      {
        "annotations": {
          "authorization.k8s.io/decision": "allow",
          "authorization.k8s.io/reason": "RBAC: allowed by ClusterRoleBinding system:public-info-viewer"
        },
        "apiVersion": "audit.k8s.io/v1",
        "auditID": "abcde12345",
        "kind": "Event",
        "level": "Request",
        "objectRef": {
          "apiVersion": "v1",
          "name": "test-pod",
          "namespace": "default",
          "resource": "pods"
        },
        "p_any_aws_account_ids": [
          "123412341234"
        ],
        "p_any_aws_arns": [
          "arn:aws:iam::123412341234:role/DevAdministrator"
        ],
        "p_any_ip_addresses": [
          "8.8.8.8"
        ],
        "p_any_usernames": [
          "system:anonymous"
        ],
        "p_event_time": "2022-11-29 00:09:04.38",
        "p_log_type": "Amazon.EKS.Audit",
        "p_parse_time": "2022-11-29 00:10:25.067",
        "p_row_id": "2e4ab474b0f0f7a4a8fff4f014a9b32a",
        "p_source_id": "4c859cd4-9406-469b-9e0e-c2dc1bee24fa",
        "p_source_label": "example-cluster-eks-logs",
        "requestReceivedTimestamp": "2022-11-29 00:09:04.38",
        "requestURI": "/api/v1/namespaces/default/pods/test-pod",
        "responseStatus": {
          "code": 200
        },
        "sourceIPs": [
          "8.8.8.8"
        ],
        "stage": "ResponseComplete",
        "user": {
          "username": "system:anonymous"
        },
        "userAgent": "kubectl/v1.25.4"
      }
  - Name: Non-Anonymous API Access
    ExpectedResult: false
    Log:
      {
        "annotations": {
          "authorization.k8s.io/decision": "allow",
          "authorization.k8s.io/reason": "RBAC: allowed by ClusterRoleBinding system:public-info-viewer"
        },
        "apiVersion": "audit.k8s.io/v1",
        "auditID": "abcde12345",
        "kind": "Event",
        "level": "Request",
        "objectRef": {
          "apiVersion": "v1",
          "name": "test-pod",
          "namespace": "default",
          "resource": "pods"
        },
        "p_any_aws_account_ids": [
          "123412341234"
        ],
        "p_any_aws_arns": [
          "arn:aws:iam::123412341234:role/DevAdministrator"
        ],
        "p_any_ip_addresses": [
          "8.8.8.8"
        ],
        "p_any_usernames": [
          "kubernetes-admin"
        ],
        "p_event_time": "2022-11-29 00:09:04.38",
        "p_log_type": "Amazon.EKS.Audit",
        "p_parse_time": "2022-11-29 00:10:25.067",
        "p_row_id": "2e4ab474b0f0f7a4a8fff4f014a9b32a",
        "p_source_id": "4c859cd4-9406-469b-9e0e-c2dc1bee24fa",
        "p_source_label": "example-cluster-eks-logs",
        "requestReceivedTimestamp": "2022-11-29 00:09:04.38",
        "requestURI": "/api/v1/namespaces/default/pods/test-pod",
        "responseStatus": {
          "code": 200
        },
        "sourceIPs": [
          "8.8.8.8"
        ],
        "stage": "ResponseComplete",
        "user": {
          "username": "kubernetes-admin"
        },
        "userAgent": "kubectl/v1.25.4"
      }
  - Name: Anonymous API Access Web Scanner Allowed
    ExpectedResult: true
    Log:
      {
        "annotations": {
          "authorization.k8s.io/decision": "allow",
          "authorization.k8s.io/reason": "RBAC: allowed by ClusterRoleBinding \"system:public-info-viewer\" of ClusterRole \"system:public-info-viewer\" to Group \"system:unauthenticated\""
        },
        "apiVersion": "audit.k8s.io/v1",
        "auditID": "d976bfc6-a2bc-49d5-bdeb-074441e0b875",
        "kind": "Event",
        "level": "Metadata",
        "requestReceivedTimestamp": "2024-11-13 18:34:10.595141000",
        "requestURI": "/version",
        "responseStatus": {
          "code": 200
        },
        "sourceIPs": [
          "44.238.138.237"
        ],
        "stage": "ResponseComplete",
        "stageTimestamp": "2024-11-13 18:34:10.595494000",
        "user": {
          "groups": [
            "system:unauthenticated"
          ],
          "username": "system:anonymous"
        },
        "userAgent": "python-requests/2.31.0",
        "verb": "get"
      }
  - Name: ELB Health Check from 10.0.0.0/8
    ExpectedResult: false
    Log:
      {
        "annotations": {
          "authorization.k8s.io/decision": "allow",
          "authorization.k8s.io/reason": "RBAC: allowed by ClusterRoleBinding system:public-info-viewer"
        },
        "apiVersion": "audit.k8s.io/v1",
        "kind": "Event",
        "level": "Metadata",
        "requestURI": "/healthz",
        "responseStatus": {
          "code": 200
        },
        "sourceIPs": [
          "10.128.5.42"
        ],
        "stage": "ResponseComplete",
        "user": {
          "username": "system:anonymous"
        },
        "userAgent": "ELB-HealthChecker/2.0"
      }
  - Name: ELB Health Check from 172.16.0.0/12
    ExpectedResult: false
    Log:
      {
        "annotations": {
          "authorization.k8s.io/decision": "allow",
          "authorization.k8s.io/reason": "RBAC: allowed by ClusterRoleBinding system:public-info-viewer"
        },
        "apiVersion": "audit.k8s.io/v1",
        "kind": "Event",
        "level": "Metadata",
        "requestURI": "/healthz",
        "responseStatus": {
          "code": 200
        },
        "sourceIPs": [
          "172.20.1.15"
        ],
        "stage": "ResponseComplete",
        "user": {
          "username": "system:anonymous"
        },
        "userAgent": "ELB-HealthChecker/2.0"
      }
  - Name: ELB Health Check from 192.168.0.0/16
    ExpectedResult: false
    Log:
      {
        "annotations": {
          "authorization.k8s.io/decision": "allow",
          "authorization.k8s.io/reason": "RBAC: allowed by ClusterRoleBinding system:public-info-viewer"
        },
        "apiVersion": "audit.k8s.io/v1",
        "kind": "Event",
        "level": "Metadata",
        "requestURI": "/healthz",
        "responseStatus": {
          "code": 200
        },
        "sourceIPs": [
          "192.168.10.20"
        ],
        "stage": "ResponseComplete",
        "user": {
          "username": "system:anonymous"
        },
        "userAgent": "ELB-HealthChecker/2.0"
      }
  - Name: ELB Health Check from Public IP
    ExpectedResult: true
    Log:
      {
        "annotations": {
          "authorization.k8s.io/decision": "allow",
          "authorization.k8s.io/reason": "RBAC: allowed by ClusterRoleBinding system:public-info-viewer"
        },
        "apiVersion": "audit.k8s.io/v1",
        "kind": "Event",
        "level": "Metadata",
        "requestURI": "/healthz",
        "responseStatus": {
          "code": 200
        },
        "sourceIPs": [
          "54.10.20.30"
        ],
        "stage": "ResponseComplete",
        "user": {
          "username": "system:anonymous"
        },
        "userAgent": "ELB-HealthChecker/2.0"
      }
  - Name: Anonymous API Access Web Scanner Denied
    ExpectedResult: true
    Log:
      {
        "annotations": {
          "authorization.k8s.io/decision": "forbid",
          "authorization.k8s.io/reason": ""
        },
        "apiVersion": "audit.k8s.io/v1",
        "auditID": "edf35e8d-92c3-4507-9bc6-4dd9cf068bcf",
        "kind": "Event",
        "level": "Metadata",
        "requestReceivedTimestamp": "2024-11-13 23:50:32.672347000",
        "requestURI": "/vendor/phpunit/src/Util/PHP/eval-stdin.php",
        "responseStatus": {
          "code": 403,
          "message": "forbidden: User \"system:anonymous\" cannot get path \"/vendor/phpunit/src/Util/PHP/eval-stdin.php\"",
          "reason": "Forbidden",
          "status": "Failure"
        },
        "sourceIPs": [
          "8.216.81.10"
        ],
        "stage": "ResponseComplete",
        "stageTimestamp": "2024-11-13 23:50:32.673504000",
        "user": {
          "groups": [
            "system:unauthenticated"
          ],
          "username": "system:anonymous"
        },
        "userAgent": "Custom-AsyncHttpClient",
        "verb": "get"
      }


# ------ paired body: anonymous_api_access.py ------

from ipaddress import ip_address

from panther_aws_helpers import eks_panther_obj_ref


def rule(event):
    src_ip = event.get("sourceIPs", ["0.0.0.0"])  # nosec
    if src_ip == ["127.0.0.1"]:
        return False
    if event.get("userAgent", "") == "ELB-HealthChecker/2.0":
        try:
            if ip_address(src_ip[0]).is_private:
                return False
        except ValueError:
            pass

    # Check if the username is set to "system:anonymous", which indicates anonymous access
    if event.deep_get("user", "username") == "system:anonymous":
        return True
    return False


def title(event):
    # For INFO-level events, just group them all together since they're not that interesting
    if event.deep_get("annotations", "authorization.k8s.io/decision") != "allow":
        return "Failed Anonymous EKS Access Attempt(s) Detected"
    if event.get("requestURI") == "/version":
        return "Anonymous EKS Access to /version Endpoint Detected"
    p_eks = eks_panther_obj_ref(event)
    return (
        f"Anonymous API access detected on Kubernetes API server "
        f"from [{p_eks.get('sourceIPs')[0]}] to [{event.get('requestURI', 'NO_URI')}] "
        f"on [{p_eks.get('p_source_label')}]"
    )


def severity(event):
    if event.deep_get("annotations", "authorization.k8s.io/decision") != "allow":
        return "INFO"
    if event.get("requestURI") == "/version":
        return "INFO"
    return "DEFAULT"


def dedup(event):
    # For INFO-level events, just group them all together since they're not that interesting
    if severity(event) == "INFO":
        return "no dedup"
    p_eks = eks_panther_obj_ref(event)
    return f"anonymous_access_{p_eks.get('p_source_label')}_{event.get('userAgent')}"


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)

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.