Back to Checks
aws

Security group does not have any port open to a specific public IP address

ec2_securitygroup_allow_ingress_from_internet_to_any_port_from_ip
Service: ec2Severity: medium
OfficialBuilt-inVerified
Categories:internet-exposed
Resource Type:AwsEc2SecurityGroup

EC2 security groups with inbound rules allowing traffic from specific globally routable IP addresses to any port or protocol. Wildcard CIDRs (0.0.0.0/0 and ::/0) are excluded as they are covered by the related checks. This targets cases where developers add personal or third-party IPs directly to security groups.

Risk

Ingress rules with specific public IPs can become stale when personnel change or access requirements expire. An attacker with compromised AWS credentials could also add narrow IP rules to gain access on any port, bypassing checks that only look for 0.0.0.0/0 or ::/0.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler aws --checks ec2_securitygroup_allow_ingress_from_internet_to_any_port_from_ip

Recommendation

Review all security group rules with specific public IP sources. Remove stale entries for former employees or expired access. Use VPN, AWS Systems Manager Session Manager, or AWS Client VPN instead of direct IP-based access. For third-party integrations, use VPC endpoints or AWS PrivateLink where possible.

Remediation

Terraform

# Security group with no public IP ingress
resource "aws_security_group" "<example_resource_name>" {
  name   = "<example_resource_name>"
  vpc_id = "<example_resource_id>"
  # No ingress blocks with public IP CIDRs
}

NativeIaC

# CloudFormation: security group without public IP ingress
Resources:
  <example_resource_name>:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: "SG without public IP ingress"
      VpcId: "<example_resource_id>"
      SecurityGroupIngress: []  # No inbound rules with public IPs

Other

1In the AWS Console, go to EC2 > Security Groups
2Select the affected security group
3Open the Inbound rules tab and click Edit inbound rules
4Remove or restrict any rule with a Source that is a public IP address
5Click Save 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.ec2_securitygroup_allow_ingress_from_internet_to_all_ports import (
    ec2_securitygroup_allow_ingress_from_internet_to_all_ports,
)
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_securitygroup_allow_ingress_from_internet_to_any_port_from_ip(Check):
    def execute(self):
        findings = []
        for security_group_arn, security_group in ec2_client.security_groups.items():
            # Check if ignoring flag is set and if the VPC and the SG is in use
            if ec2_client.provider.scan_unused_services or (
                security_group.vpc_id in vpc_client.vpcs
                and vpc_client.vpcs[security_group.vpc_id].in_use
                and len(security_group.network_interfaces) > 0
            ):
                report = Check_Report_AWS(
                    metadata=self.metadata(), resource=security_group
                )
                report.resource_details = security_group.name
                report.status = "PASS"
                report.status_extended = f"Security group {security_group.name} ({security_group.id}) does not have any port open to a public IP address."

                # only proceed if check "..._to_all_ports" did not run or did not FAIL to avoid reporting twice
                if not ec2_client.is_failed_check(
                    ec2_securitygroup_allow_ingress_from_internet_to_all_ports.__name__,
                    security_group_arn,
                ):
                    for ingress_rule in security_group.ingress_rules:
                        # Skip rules that only contain 0.0.0.0/0 or ::/0
                        # (already covered by other SG checks)
                        wildcard_cidrs = ("0.0.0.0/0", "::/0")
                        has_specific_ip = any(
                            r["CidrIp"] not in wildcard_cidrs
                            for r in ingress_rule.get("IpRanges", [])
                        ) or any(
                            r["CidrIpv6"] not in wildcard_cidrs
                            for r in ingress_rule.get("Ipv6Ranges", [])
                        )
                        if has_specific_ip and check_security_group(
                            ingress_rule, "-1", any_address=False, all_ports=True
                        ):
                            report.status = "FAIL"
                            report.status_extended = f"Security group {security_group.name} ({security_group.id}) has a port open to a specific public IP address in ingress rule."
                            break
                else:
                    report.status_extended = f"Security group {security_group.name} ({security_group.id}) has all ports open to the Internet and therefore was not checked against specific public IP ingress rules."

                findings.append(report)

        return findings