Somewhere in your AWS console there may be a database with a single line in its connectivity panel: Publicly accessible: Yes. It is easy to read that line two ways. One reading is alarm: the database is on the internet. The other is shrug: it is behind a security group, so it is fine. Both are partly wrong, and the gap between them is exactly where real incidents live.
This post is about reading that line correctly. We will take one concrete scenario, a managed relational database flagged as publicly accessible, in a subnet whose route table sends all non-local traffic to an internet gateway, with a security group that allows a few specific source addresses, in a VPC that is peered with others, and walk through what an attacker would need, what stops them, and what a defender should check. You will leave with a five-gate model for reachability, a set of commands to verify each gate in about fifteen minutes, an explanation of how lateral movement works once something is reachable, and a ladder of fixes from quick to structural.
It is written for cloud security engineers, platform teams, and the security leaders who sign off on risk acceptances. If you want the general concept first, our explainers on attack path analysis and why isolated findings mislead you are good companions. This post is the worked example.
Three words that do not mean what they appear to mean
“Publicly accessible.” On Amazon RDS and Aurora, this setting controls whether the database instance gets a public IP address and a DNS name that resolves to it from outside the VPC. It does not, by itself, open a path from the internet. It is a precondition for one.
“Public subnet.” A subnet is public because of its route table, not its name. If the route table contains a route sending 0.0.0.0/0 to an internet gateway (target starting igw-), resources in that subnet can send and receive traffic directly to and from the internet, provided they have public addresses. If the default route points to a NAT gateway (nat-), traffic can leave but unsolicited traffic cannot come in. That single difference, igw- versus nat-, is one of the most important things to read correctly in a route table.
“Open to the world.” Usually shorthand for a security group rule with source 0.0.0.0/0 on a database port. But a tightly scoped rule can still be dangerous if the source it names is shared by many workloads. We will come to that.
The five gates between the internet and your data
For a database to be exploitable from the internet, all five of these must be true. Each is a place to stop an attack.
| Gate | Question | Where to look | What closes it |
|---|---|---|---|
| 1. Address | Does the database have a public IP and DNS name? | The PubliclyAccessible attribute | Set to false |
| 2. Route | Can traffic from the internet reach that subnet? | Route table of the subnet(s) in the DB subnet group | Remove the igw- route; use private subnets |
| 3. Firewall (stateful) | Does a security group allow the source on the DB port? | Security groups attached to the instance | Narrow or remove inbound rules |
| 4. Firewall (stateless) | Does the network ACL allow the traffic both ways? | NACL on the subnet | Tighten the NACL (an extra layer, not a primary control) |
| 5. Authentication | Can the client prove who it is? | Database users, TLS, IAM database authentication | Strong auth, no shared credentials, no default users |
Seeing the gates this way changes how you triage. A database with gate 1 open and gates 2 and 3 closed is a configuration smell, not an exposure. A database with gates 1, 2, and 3 all open is exposed, and only gate 5 remains. Gate 5 is the weakest wall in practice, because database credentials are often shared, long-lived, reused across environments, or discoverable in code. When people say “it is fine, it needs a password,” they are relying on the least reliable gate to carry the whole load.
Our scenario, gate by gate
Imagine a production MySQL-compatible Aurora cluster for an application. You find:
- Gate 1:
PubliclyAccessible = true. Open. - Gate 2: the subnets in its DB subnet group use a route table with
0.0.0.0/0 → igw-…. Open. - Gate 3: the security group allows port 3306 from a handful of IPv4 addresses and CIDRs: an office range, a bastion, and two internal network ranges. Narrowed, but not closed.
- Gate 4: the network ACL is the default, allowing everything. Not a barrier.
- Gate 5: database users with passwords, and a few IAM-authenticated users.
This is a very common configuration, and it often exists for a good reason: a contractor needed access, a BI tool needed to connect, or someone wanted to run a query from a laptop. Nobody opened it to the world. They opened it to a list.
Why “an allow-list of IPs” is weaker than it looks
Source IP allow-listing feels like authentication but it is not. Three things go wrong:
- Shared egress addresses. If the allow-list contains the public IP of a NAT gateway used by your production workloads, then every workload behind that NAT can reach the database from the internet side, including one that has been compromised through a server-side request forgery bug. The allow-list gave the whole fleet access.
- Dynamic and home addresses. Ranges added for a person working remotely are rarely removed. They quietly outlive the reason they were added, and the address may later belong to someone else.
- IP is not identity. Anything that can send packets from an allowed address, a compromised laptop, a hijacked office device, a cloud function sharing an egress, is treated as trusted.
So the exposure in our scenario is not “anyone on the internet.” It is “anyone who controls or compromises anything on that list, with a path through gate 5.” That is a smaller group than the world and a larger group than anyone intended.
What an attacker does with the open path
Understanding the next steps makes the defense obvious. Here is a realistic sequence, described at the level of technique, not instructions.
- Discovery. Scanners continuously sweep public address space for database ports. A security group that does not allow the scanner’s address silently drops the packets, so the probe simply times out. A source that is allowed gets a response, and the database engine and version can often be read from the connection handshake. That is why the allow-list in gate 3 matters so much.
- Credential access. The database needs a login. Attackers try credentials harvested from public code repositories, previous breaches, and application configuration files, and they target default or service accounts. Long-lived passwords shared across environments make this easy.
- Data access. With a valid login, the attacker queries. Without query-level controls, a single
SELECTcan read entire tables, and the database returns whatever the account is allowed to see. - Persistence and escalation inside the database. Creating users, altering grants, or reading stored secrets such as API keys or tokens kept in application tables.
- Lateral movement. This is the step that turns a data incident into an infrastructure incident, and it is the one most teams underestimate.
Lateral movement, explained plainly
Lateral movement means using access in one place to gain access in another. From a database, the main routes are:
- Secrets stored in data. Application tables often contain integration tokens, webhook secrets, or encryption keys. Reading them yields access to other systems.
- Associated IAM roles. Managed databases can be given IAM roles, for example to import from or export to object storage. Those permissions are part of the blast radius if the role is broader than necessary.
- Network reach. A database host sits in a network. If security groups elsewhere trust the database’s security group, or if routes exist to other VPCs, the database becomes a stepping stone.
- Credential reuse. The same username and password pattern often appears in staging, production, and tooling.
The principle is the one behind every attack path: individual findings look small, but connected, they form a chain. This is why a flat list of misconfigurations understates risk and why contextual severity matters.
How VPC peering widens the blast radius
VPC peering is a network connection between two VPCs that lets resources communicate using private IP addresses. It is common in multi-account environments where production, staging, and tooling accounts need to talk. A few precise facts help you reason about it:
- Peering is not transitive. If VPC A is peered with B, and B with C, A cannot reach C through B. This is a useful property, and it is also a reason teams build many peering links, which increases the number of connections to review.
- Peering only works if routes and firewalls allow it. There must be route entries pointing the peer’s CIDR to the peering connection (targets starting
pcx-), and the security group must allow the source, either as a CIDR or, in the same region, by referencing the peer’s security group. - A broad CIDR in a rule means a broad set of workloads. Allowing
10.0.0.0/8or an entire peer VPC range on the database port gives every resource in that range a path, including development hosts, jump servers, and anything an attacker lands on. - Peering links tend to outlive the project that created them. Old links, broad routes, and permissive rules accumulate.
The practical consequence is that a database in a peered VPC is reachable from the inside by a much larger set of machines than the diagram in someone’s head. Even if the internet path is closed, an attacker who compromises any host in a trusted range gets a short, private path to the data.
Verify it yourself in fifteen minutes
Run these read-only checks against the database and write down each gate’s state.
1. List databases and their public setting.
aws rds describe-db-instances \
--query 'DBInstances[].{id:DBInstanceIdentifier,engine:Engine,public:PubliclyAccessible,vpc:DBSubnetGroup.VpcId,sg:VpcSecurityGroups[].VpcSecurityGroupId}' \
--output table
2. Find the subnets in the database’s subnet group and their route tables.
aws rds describe-db-subnet-groups \
--query 'DBSubnetGroups[].[DBSubnetGroupName,Subnets[].SubnetIdentifier]'
aws ec2 describe-route-tables \
--filters Name=association.subnet-id,Values=<subnet-id> \
--query 'RouteTables[].Routes[].[DestinationCidrBlock,GatewayId,NatGatewayId,VpcPeeringConnectionId]' \
--output table
Look at the 0.0.0.0/0 row. A GatewayId starting igw- is a public route. A NatGatewayId is outbound only. Rows with a peering connection ID show which networks are directly connected.
3. Read the security group rules.
aws ec2 describe-security-groups --group-ids <sg-id> \
--query 'SecurityGroups[].IpPermissions[].[FromPort,ToPort,IpRanges[].CidrIp,UserIdGroupPairs[].GroupId]'
Flag any source that is 0.0.0.0/0, any /8 to /16 range, and any address you cannot attribute to a named owner and purpose.
4. Ask AWS whether a path exists. Reachability Analyzer evaluates the configuration and tells you whether a source can reach a destination, and which component blocks it if not.
aws ec2 create-network-insights-path \
--source igw-0123456789abcdef0 \
--destination eni-0123456789abcdef0 \
--protocol tcp --destination-port 3306
Then start an analysis for the path and read the result. It is a configuration analysis, so it does not send traffic, and it is the fastest way to turn “we think it is closed” into evidence.
5. Check authentication. Is TLS required? Are there users that can connect from any host? Is IAM database authentication enabled for at least the human users? IAM-issued tokens are short-lived (fifteen minutes), which removes the shared-password problem for people.
Fix it: from quick to structural
You do not have to do everything at once. Work down this ladder and stop when the residual risk is acceptable.
Level 1: close what is clearly unnecessary (hours).
- Remove security group rules for people and tools that no longer need access.
- Replace broad CIDRs with specific sources, or with references to a security group.
- Rotate shared database passwords and remove default accounts.
Level 2: break the public path (a planned change).
- Set the database to not publicly accessible:
aws rds modify-db-instance \
--db-instance-identifier <id> \
--no-publicly-accessible \
--apply-immediately
Test connectivity from every legitimate client first. Applications in the same VPC will keep working; laptops, BI tools, and third parties that connect over the public address will stop, which is the point, and you need a replacement path ready.
- Place the database in a subnet group whose route tables have no
igw-route.
Level 3: replace “open path” with “authorized access” (structural).
- Route human access through a brokered, time-limited path instead of a standing network opening. Options include session-manager style port forwarding through a private host, a VPN or private connectivity for tools, and IAM database authentication.
- Remove standing personal credentials. A person who needs the database for an hour should get access for an hour.
- Add query-level control so that even valid access is governed: masking for sensitive columns, blocking for destructive statements, and a record of who ran what.
Level 4: make it hard to regress.
- Alert when a database becomes publicly accessible or when a route to an internet gateway appears in a database subnet.
- Review peering links and security group CIDRs on a schedule.
- Treat the relationship between exposure and data sensitivity as the severity driver, not the individual finding.
What about legitimate reasons for public access?
There are some: a managed partner needs to read replicas, a BI vendor connects from fixed addresses, a developer needs a temporary connection. These are real needs. The point is to deliver them with a control that has an owner, an expiry, and a log: a time-limited approval, a dedicated narrow rule that is removed automatically, or a private link. “Open until someone remembers” is not a control.
Where Cloudanix fits
Cloudanix is built to read exactly this kind of chain and rank it by what an attacker can actually do.
- Context, not just flags. Cloudanix CSPM evaluates the public database setting together with the subnet route, the security group rules, the peering links, and the sensitivity of the data. A publicly accessible database in a private subnet and one reachable through an internet gateway appear as different severities, because they are. Our attack path view shows the chain from exposure to impact, and contextual severity is the reason the loud finding is not always the urgent one.
- Remove the reason for public access. Database JIT gives engineers keyless, identity-stamped, time-bound access to databases through a proxy that runs inside your own cloud account, without a standing network opening or a shared password. It is the replacement for the “temporary” rule that became permanent.
- Govern what happens after access. Database Activity Monitoring adds query-level guardrails, role-based masking, and a searchable record of every statement, with data staying in your account. If you want the background, read what database activity monitoring is and the data-tier blind spot in cloud security.
- Know your other common RDS mistakes. Our list of top AWS RDS misconfigurations is a good companion audit.
No tool removes the need to understand your own network. If you are not sure why a rule exists, find the owner before you delete it.
A checklist to take away
- List every database where
PubliclyAccessibleis true. - For each, check whether its subnets route
0.0.0.0/0to an internet gateway. - Read every security group rule on the database port. Attribute each source to an owner and a purpose, or remove it.
- Remove NAT or shared egress addresses from database allow-lists unless you are sure that is intended.
- Review peering links and the CIDRs they expose.
- Confirm TLS is required and shared passwords are gone.
- Run Reachability Analyzer for the paths that matter and keep the output as evidence.
- Replace standing exposure with time-limited, logged access.
- Alert on regressions.
- Re-review quarterly.
Closing thought
The most useful thing a security engineer can do with a scary-looking flag is to ask the next question. “Publicly accessible” is the start of an investigation. The answer depends on a route table, a security group, a peering link, and a password, and any one of them can turn a finding from informational into urgent. Teams that learn to read the chain stop arguing about whether a finding is real and start closing the gate that matters.
If you would like help tracing these paths across your accounts, get in touch. We can show you which of your databases are reachable, from where, and what an attacker would find on the other side.
Related Resources
- 15 Top AWS RDS Misconfigurations To Avoid
- What Is an Attack Path, and Why Isolated Security Findings Mislead You
- AWS Networking Fundamentals Crash Course For A Busy Developer
- Securing Databases Behind ECS: The Data Tier Blind Spot in Cloud Security
- How Code-to-Cloud Correlation Works: The Graph Behind the Attack Path
- What Is Attack Path Analysis in Cloud Security?
- JIT DB Onboarding in VPC-Constrained Environments: Deploying Database JIT Without VPC Peering
- Cloudanix Attack Path Analysis
- Cloudanix Database Activity Monitoring