Ensure No EC2 Instances Allow Ingress from the Internet to PostgreSQL (TCP 5432)
Overview
This check verifies that your EC2 instances do not have security group rules allowing inbound PostgreSQL traffic (TCP port 5432) from the public internet (0.0.0.0/0 or ::/0). PostgreSQL is a popular open-source database, and exposing it directly to the internet creates significant security risks.
Risk
Exposing PostgreSQL port 5432 to the internet enables attackers to:
- Brute-force attacks: Automated tools can attempt to guess database credentials
- Data exfiltration: Successful attackers can steal sensitive data stored in the database
- Schema manipulation: Attackers may modify or delete database structures and data
- Lateral movement: A compromised database can be used to attack other resources in your VPC
- Exploit vulnerabilities: Unpatched PostgreSQL versions may have known security flaws that can be exploited remotely
Severity: Critical - This is a high-priority security issue that should be addressed immediately.
Remediation Steps
Prerequisites
You need:
- AWS Console access with permissions to modify EC2 security groups
- Knowledge of which IP addresses or CIDR blocks legitimately need PostgreSQL access
Required IAM permissions
Your IAM user or role needs these permissions:
ec2:DescribeSecurityGroupsec2:DescribeSecurityGroupRulesec2:RevokeSecurityGroupIngressec2:AuthorizeSecurityGroupIngress(if adding replacement rules)
AWS Console Method
-
Open the EC2 Console
- Go to EC2 Console in us-east-1
-
Find the affected instance
- Click Instances in the left sidebar
- Locate the EC2 instance flagged by Prowler
- Click on the instance to view its details
-
Identify the security group
- In the instance details, click the Security tab
- Note the security group(s) attached to the instance
- Click on the security group ID to open it
-
Remove the dangerous rule
- Click the Inbound rules tab
- Find any rule with:
- Port: 5432
- Source: 0.0.0.0/0 or ::/0
- Select the rule and click Delete inbound rules
- Confirm the deletion
-
Add a restricted rule (if needed)
- If legitimate access is required, click Edit inbound rules
- Click Add rule
- Configure:
- Type: PostgreSQL
- Port: 5432
- Source: Your trusted CIDR block (e.g., 10.0.0.0/8 for internal networks)
- Click Save rules
AWS CLI (optional)
Find security groups with exposed PostgreSQL port
First, identify security groups that allow PostgreSQL from the internet:
aws ec2 describe-security-groups \
--region us-east-1 \
--filters "Name=ip-permission.from-port,Values=5432" \
"Name=ip-permission.to-port,Values=5432" \
"Name=ip-permission.cidr,Values=0.0.0.0/0" \
--query 'SecurityGroups[*].[GroupId,GroupName]' \
--output table
Remove the offending rule (IPv4)
aws ec2 revoke-security-group-ingress \
--group-id <SECURITY_GROUP_ID> \
--protocol tcp \
--port 5432 \
--cidr 0.0.0.0/0 \
--region us-east-1
Replace <SECURITY_GROUP_ID> with your actual security group ID (e.g., sg-0123456789abcdef0).
Remove the offending rule (IPv6)
If IPv6 is also exposed:
aws ec2 revoke-security-group-ingress \
--group-id <SECURITY_GROUP_ID> \
--ip-permissions IpProtocol=tcp,FromPort=5432,ToPort=5432,Ipv6Ranges='[{CidrIpv6=::/0}]' \
--region us-east-1
Add a restricted replacement rule (if needed)
aws ec2 authorize-security-group-ingress \
--group-id <SECURITY_GROUP_ID> \
--protocol tcp \
--port 5432 \
--cidr 10.0.0.0/8 \
--region us-east-1
Replace 10.0.0.0/8 with your actual trusted CIDR block.
CloudFormation (optional)
Use this template to create a properly configured security group for PostgreSQL:
AWSTemplateFormatVersion: '2010-09-09'
Description: Security group restricting PostgreSQL access to trusted CIDR blocks
Parameters:
VpcId:
Type: AWS::EC2::VPC::Id
Description: VPC where the security group will be created
TrustedCidr:
Type: String
Default: '10.0.0.0/8'
Description: Trusted CIDR block allowed to access PostgreSQL
AllowedPattern: '^(\d{1,3}\.){3}\d{1,3}/\d{1,2}$'
Resources:
PostgreSQLSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for PostgreSQL instances - restricted access
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
CidrIp: !Ref TrustedCidr
Description: PostgreSQL access from trusted network only
Tags:
- Key: Name
Value: postgresql-restricted-sg
Outputs:
SecurityGroupId:
Description: ID of the created security group
Value: !Ref PostgreSQLSecurityGroup
Deploy with:
aws cloudformation create-stack \
--stack-name postgresql-security-group \
--template-body file://template.yaml \
--parameters ParameterKey=VpcId,ParameterValue=vpc-xxxxxxxx \
ParameterKey=TrustedCidr,ParameterValue=10.0.0.0/8 \
--region us-east-1
Terraform (optional)
variable "vpc_id" {
description = "VPC ID where the security group will be created"
type = string
}
variable "trusted_cidr_blocks" {
description = "List of trusted CIDR blocks allowed to access PostgreSQL"
type = list(string)
default = ["10.0.0.0/8"]
}
resource "aws_security_group" "postgresql" {
name = "postgresql-restricted-sg"
description = "Security group for PostgreSQL - restricted access only"
vpc_id = var.vpc_id
tags = {
Name = "postgresql-restricted-sg"
}
}
resource "aws_security_group_rule" "postgresql_ingress" {
type = "ingress"
from_port = 5432
to_port = 5432
protocol = "tcp"
cidr_blocks = var.trusted_cidr_blocks
security_group_id = aws_security_group.postgresql.id
description = "PostgreSQL access from trusted networks only"
}
output "security_group_id" {
description = "ID of the PostgreSQL security group"
value = aws_security_group.postgresql.id
}
Deploy with:
terraform init
terraform plan -var="vpc_id=vpc-xxxxxxxx"
terraform apply -var="vpc_id=vpc-xxxxxxxx"
Verification
After remediation, verify the fix:
-
In the AWS Console:
- Go to EC2 > Security Groups
- Select the modified security group
- Check Inbound rules tab
- Confirm there are no rules with source 0.0.0.0/0 or ::/0 for port 5432
-
Re-run Prowler:
- Execute the specific check to confirm the issue is resolved
CLI verification commands
Check for remaining exposed PostgreSQL rules:
aws ec2 describe-security-groups \
--region us-east-1 \
--filters "Name=ip-permission.from-port,Values=5432" \
"Name=ip-permission.to-port,Values=5432" \
"Name=ip-permission.cidr,Values=0.0.0.0/0" \
--query 'SecurityGroups[*].GroupId' \
--output text
An empty result indicates no security groups have PostgreSQL exposed to the internet.
Check a specific security group's rules:
aws ec2 describe-security-group-rules \
--region us-east-1 \
--filters "Name=group-id,Values=<SECURITY_GROUP_ID>" \
--query 'SecurityGroupRules[?FromPort==`5432` && ToPort==`5432`]'
Additional Resources
- AWS Documentation: Security Groups for Your VPC
- AWS Documentation: Control traffic to your AWS resources using security groups
- AWS Well-Architected Framework: Security Pillar
- PostgreSQL Security Best Practices
- CIS AWS Foundations Benchmark
Notes
-
Service interruption: Removing a security group rule takes effect immediately. Ensure you have alternative access (VPN, bastion host, or restricted CIDR) before removing the open rule, or you may lose database connectivity.
-
Alternative architectures: Consider these secure alternatives instead of exposing PostgreSQL directly:
- Use AWS RDS with VPC security groups and no public accessibility
- Deploy a bastion host or jump box for database access
- Use AWS Systems Manager Session Manager for secure access without opening ports
- Implement AWS Client VPN or Site-to-Site VPN for secure connectivity
-
Multiple security groups: An EC2 instance can have multiple security groups. Check all attached security groups, as any one of them allowing 0.0.0.0/0 on port 5432 creates the vulnerability.
-
Network ACLs: Security groups are stateful, but Network ACLs are stateless. For defense in depth, also review your subnet NACLs to ensure they don't allow inbound traffic on port 5432 from the internet.
-
Compliance frameworks: This check maps to requirements in C5, ISO27001, KISA-ISMS-P, NIS2, and SOC2 frameworks.