Back to Checks
aws

EC2 instance does not allow ingress from the Internet to Elasticsearch and Kibana ports (TCP 9200, 9300, 5601)

ec2_instance_port_elasticsearch_kibana_exposed_to_internet
Service: ec2Severity: critical
OfficialBuilt-inVerified
Categories:internet-exposed
Resource Type:AwsEc2Instance

EC2 instances with Elasticsearch/Kibana ports (9200, 9300, 5601) exposed to the Internet through inbound security group rules.

Assesses reachability considering instance public IP and subnet to reflect real exposure.

Risk

Public access to Elasticsearch/Kibana can lead to:

  • Unauthorized queries or dashboard viewing confidentiality loss
  • Index changes or cluster control via 9300 integrity impact
  • Scans and bulk queries availability degradation

Enables data exfiltration and lateral movement.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler aws --checks ec2_instance_port_elasticsearch_kibana_exposed_to_internet

Recommendation

Apply least privilege to network exposure:

  • Restrict 9200, 9300, 5601 to trusted sources or keep them private
  • Use private subnets, VPN/peering, or bastion/SSM for admin access
  • Enforce authentication and TLS on Elasticsearch/Kibana
  • Avoid public IPs unless strictly required

Remediation

CLI

aws ec2 revoke-security-group-ingress --group-id <SECURITY_GROUP_ID> --ip-permissions '[{"IpProtocol":"tcp","FromPort":9200,"ToPort":9200,"IpRanges":[{"CidrIp":"0.0.0.0/0"}]},{"IpProtocol":"tcp","FromPort":9300,"ToPort":9300,"IpRanges":[{"CidrIp":"0.0.0.0/0"}]},{"IpProtocol":"tcp","FromPort":5601,"ToPort":5601,"IpRanges":[{"CidrIp":"0.0.0.0/0"}]}]'

Terraform

# Terraform: restrict Elasticsearch/Kibana ports to a private CIDR (not the Internet)
resource "aws_security_group" "<example_resource_name>" {
  name   = "<example_resource_name>"
  vpc_id = "<example_resource_id>"

  # CRITICAL: restrict sources; do NOT use 0.0.0.0/0 or ::/0
  ingress {
    from_port   = 9200
    to_port     = 9200
    protocol    = "tcp"
    cidr_blocks = ["10.0.0.0/8"]
  }
  ingress {
    from_port   = 9300
    to_port     = 9300
    protocol    = "tcp"
    cidr_blocks = ["10.0.0.0/8"]
  }
  ingress {
    from_port   = 5601
    to_port     = 5601
    protocol    = "tcp"
    cidr_blocks = ["10.0.0.0/8"]
  }
}

NativeIaC

# CloudFormation: restrict Elasticsearch/Kibana ports to a private CIDR (not the Internet)
Resources:
  <example_resource_name>:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Restrict Elasticsearch/Kibana ports
      VpcId: <example_resource_id>
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 9200
          ToPort: 9200
          CidrIp: 10.0.0.0/8  # CRITICAL: do not use 0.0.0.0/0 or ::/0; restrict to internal CIDR
        - IpProtocol: tcp
          FromPort: 9300
          ToPort: 9300
          CidrIp: 10.0.0.0/8  # CRITICAL: restrict source to stop Internet exposure
        - IpProtocol: tcp
          FromPort: 5601
          ToPort: 5601
          CidrIp: 10.0.0.0/8  # CRITICAL: restrict source to stop Internet exposure

Other

1Open the AWS Console and go to EC2 > Security Groups
2Select the security group attached to the instance
3In Inbound rules, find any rule allowing TCP 9200, 9300, or 5601 from 0.0.0.0/0 or ::/0
4Edit inbound rules and either delete those rules or change the source to a restricted CIDR (e.g., your internal network)
5Save rules

Check Code

from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.ec2.ec2_client import ec2_client
from prowler.providers.aws.services.ec2.lib.instance import get_instance_public_status
from prowler.providers.aws.services.ec2.lib.security_groups import check_security_group
from prowler.providers.aws.services.vpc.vpc_client import vpc_client


class ec2_instance_port_elasticsearch_kibana_exposed_to_internet(Check):
    # EC2 Instances with Elasticsearch/Kibana ports open to the Internet will be flagged as FAIL with a severity of medium if the instance has no public IP, high if the instance has a public IP but is in a private subnet, and critical if the instance has a public IP and is in a public subnet.
    def execute(self):
        findings = []
        check_ports = [9200, 9300, 5601]
        for instance in ec2_client.instances:
            report = Check_Report_AWS(metadata=self.metadata(), resource=instance)
            report.status = "PASS"
            report.status_extended = f"Instance {instance.id} does not have Elasticsearch/Kibana ports open to the Internet."
            is_open_port = False
            if instance.security_groups:
                for sg in ec2_client.security_groups.values():
                    if sg.id in instance.security_groups:
                        for ingress_rule in sg.ingress_rules:
                            if check_security_group(
                                ingress_rule, "tcp", check_ports, any_address=True
                            ):
                                # The port is open, now check if the instance is in a public subnet with a public IP
                                report.status = "FAIL"
                                (
                                    report.status_extended,
                                    report.check_metadata.Severity,
                                ) = get_instance_public_status(
                                    vpc_client.vpc_subnets,
                                    instance,
                                    "Elasticsearch/Kibana",
                                )
                                is_open_port = True
                                break
                        if is_open_port:
                            break
            findings.append(report)
        return findings