Ensure No EC2 Instances Allow Ingress from the Internet to TCP Port 3306 (MySQL)
Overview
This check identifies EC2 instances with security groups that expose MySQL (TCP port 3306) to the entire internet. MySQL is a popular database, and leaving it open to the world is a critical security risk.
Risk
When MySQL is accessible from the internet, attackers can:
- Steal your data by connecting directly to your database
- Modify or delete records if they gain access
- Brute-force credentials to gain unauthorized entry
- Use your database server as a stepping stone to attack other systems in your network
This is rated Critical severity because databases often contain sensitive information and should never be directly exposed to the public internet.
Remediation Steps
Prerequisites
You need permission to modify EC2 security groups. Typically this means having the ec2:RevokeSecurityGroupIngress and ec2:AuthorizeSecurityGroupIngress permissions.
AWS Console Method
-
Open the EC2 Console
-
In the left navigation, click Security Groups (under Network & Security)
-
Find the security group attached to your EC2 instance:
- You can search by security group ID or name
- Or go to Instances, select your instance, and look at the Security tab to see which groups are attached
-
Select the security group and click the Inbound rules tab
-
Look for any rule that:
- Has Port range of
3306 - Has Source of
0.0.0.0/0(IPv4) or::/0(IPv6)
- Has Port range of
-
Click Edit inbound rules
-
Delete the rule that allows MySQL from the internet by clicking the Delete button next to it
-
(Optional) Add a new rule to allow MySQL only from trusted sources:
- Click Add rule
- Type: MySQL/Aurora
- Source: Choose Custom and enter a specific IP range (e.g.,
10.0.0.0/16for your VPC) or select another security group
-
Click Save rules
AWS CLI Method
Step 1: Find security groups with exposed MySQL port
aws ec2 describe-security-groups \
--region us-east-1 \
--filters "Name=ip-permission.from-port,Values=3306" \
"Name=ip-permission.to-port,Values=3306" \
"Name=ip-permission.cidr,Values=0.0.0.0/0" \
--query "SecurityGroups[*].[GroupId,GroupName]" \
--output table
Step 2: Remove the internet-facing rule (IPv4)
aws ec2 revoke-security-group-ingress \
--region us-east-1 \
--group-id <SECURITY_GROUP_ID> \
--protocol tcp \
--port 3306 \
--cidr 0.0.0.0/0
Step 3: Remove the internet-facing rule (IPv6, if present)
aws ec2 revoke-security-group-ingress \
--region us-east-1 \
--group-id <SECURITY_GROUP_ID> \
--protocol tcp \
--port 3306 \
--cidr ::/0
Step 4: (Optional) Add a restricted rule
Replace <YOUR_TRUSTED_CIDR> with your application subnet or VPN range:
aws ec2 authorize-security-group-ingress \
--region us-east-1 \
--group-id <SECURITY_GROUP_ID> \
--protocol tcp \
--port 3306 \
--cidr <YOUR_TRUSTED_CIDR>
CloudFormation
This CloudFormation template creates a security group that allows MySQL access only from a specified trusted CIDR range (not the internet).
AWSTemplateFormatVersion: '2010-09-09'
Description: Security group for MySQL with restricted access
Parameters:
VpcId:
Type: AWS::EC2::VPC::Id
Description: VPC where the security group will be created
TrustedCidr:
Type: String
Description: CIDR range allowed to access MySQL (e.g., 10.0.0.0/16)
AllowedPattern: ^(\d{1,3}\.){3}\d{1,3}/\d{1,2}$
ConstraintDescription: Must be a valid CIDR range
Resources:
MySQLSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: MySQL access restricted to trusted sources only
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
CidrIp: !Ref TrustedCidr
Description: MySQL access from trusted network
Tags:
- Key: Name
Value: mysql-restricted-sg
Outputs:
SecurityGroupId:
Description: ID of the MySQL security group
Value: !Ref MySQLSecurityGroup
To deploy:
aws cloudformation deploy \
--region us-east-1 \
--template-file mysql-security-group.yaml \
--stack-name mysql-restricted-sg \
--parameter-overrides VpcId=<YOUR_VPC_ID> TrustedCidr=10.0.0.0/16
Terraform
This Terraform configuration creates a security group that restricts MySQL access to trusted sources only.
variable "vpc_id" {
description = "VPC ID where the security group will be created"
type = string
}
variable "trusted_cidr_blocks" {
description = "List of CIDR blocks allowed to access MySQL"
type = list(string)
default = ["10.0.0.0/16"]
}
resource "aws_security_group" "mysql_restricted" {
name = "mysql-restricted-sg"
description = "MySQL access restricted to trusted sources only"
vpc_id = var.vpc_id
ingress {
description = "MySQL access from trusted network"
from_port = 3306
to_port = 3306
protocol = "tcp"
cidr_blocks = var.trusted_cidr_blocks
}
egress {
description = "Allow all outbound traffic"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "mysql-restricted-sg"
}
}
output "security_group_id" {
description = "ID of the MySQL security group"
value = aws_security_group.mysql_restricted.id
}
To apply:
terraform init
terraform apply -var="vpc_id=vpc-xxxxxxxxx"
Verification
After making changes, verify the fix:
- Go back to the Security Groups page in the EC2 Console
- Select the security group you modified
- Check the Inbound rules tab
- Confirm there is no rule allowing port 3306 from
0.0.0.0/0or::/0
CLI Verification
Run this command to check if any security groups still expose MySQL to the internet:
aws ec2 describe-security-groups \
--region us-east-1 \
--filters "Name=ip-permission.from-port,Values=3306" \
"Name=ip-permission.to-port,Values=3306" \
"Name=ip-permission.cidr,Values=0.0.0.0/0" \
--query "SecurityGroups[*].GroupId" \
--output text
If the output is empty, no security groups expose MySQL to the internet.
You can also re-run the Prowler check:
prowler aws --checks ec2_instance_port_mysql_exposed_to_internet
Additional Resources
- AWS Security Groups Documentation
- AWS Best Practices for Security Groups
- MySQL Security Best Practices
- Prowler Check Documentation
Notes
-
Application impact: Before removing the rule, ensure your applications can still reach the database. If applications connect from the internet, you will need to set up a VPN, bastion host, or use AWS RDS with proper network configuration instead.
-
Better architecture: Consider moving your MySQL database to Amazon RDS in a private subnet. RDS provides managed backups, patching, and can be configured to only accept connections from your application servers.
-
Port ranges: Some security groups may have rules that include port 3306 within a broader range (e.g., 0-65535). Review all rules carefully.
-
IPv6: Remember to check for and remove both IPv4 (
0.0.0.0/0) and IPv6 (::/0) rules. -
Compliance frameworks: This check maps to C5, ISO27001, KISA-ISMS-P, NIS2, and SOC2 compliance requirements.