Cloud Security

Introduction to Attack Paths

  • 6 min read
Introduction to Attack Paths

A List Versus a Route

Run a security scan against a busy AWS account and you get a list: a few hundred findings, each with a severity, sorted from critical to low. It is a useful list. It is also a misleading one, because it treats every finding as if it stood alone.

Attackers do not work from that list. They do not ask which of your misconfigurations is the most severe. They ask what they can reach from where they are now, and then what they can reach from there. A finding matters to them only as a step towards something they want.

That chain of steps is an attack path. Learning to see your account as a set of paths, not a list of findings, changes which problems you fix first.

What an Attack Path Is

An attack path has three parts.

  • An entry point. Somewhere an attacker can start: a port reachable from the internet, a public endpoint, or a credential that has leaked into a repository or a CI log.
  • A series of hops. Each hop is a configuration or a permission that lets the attacker move from one resource to the next: from the network to an instance, from the instance to a role, from the role to another role.
  • A target. Something worth the effort: customer data, encryption keys, secrets, or administrative control of the account.

A path only exists when every link in it holds. Remove any single hop and the attacker is stranded at the step before it. That property is what makes paths useful for prioritising, and we will come back to it.

One Path, Step by Step

Here is a path that turns up in real accounts more often than anyone would like.

An example attack path The public internet reaches an EC2 instance through an open security group; that instance assumes a role which can read a private S3 bucket. your account Internet 0.0.0.0/0 Security group port 22 open EC2 instance assumes role S3 bucket private, readable via role Each step looks minor on its own. Together, they let the internet read your bucket.

Read it from left to right.

  1. A security group allows SSH from 0.0.0.0/0. Somebody opened it to debug an instance and never closed it.
  2. The instance behind it is reachable, so a weak key, a leaked key or an unpatched SSH service is enough to get a shell.
  3. The instance has an instance profile. Any process on it can fetch that role's temporary credentials from the metadata service.
  4. The role's policy grants s3:GetObject on a bucket the application reads. The bucket itself is private, with Block Public Access switched on.

Now look at how each of those would appear in a findings list. The open security group is a medium. The instance role is not a finding at all: the application genuinely needs it. The bucket passes every check, because it is private. Nothing on the list says critical, yet the internet can read your bucket.

Why Severity Alone Misleads

Severity describes a finding in isolation. Risk depends on what the finding is connected to, and the same finding can sit at either end of the scale.

Take that open security group. On an instance with no role, in a subnet with nothing else in it, it is a genuine but contained problem: an attacker who gets in finds very little. On an instance whose role reaches your customer data, it is the front door. The finding is identical in both cases. Only the path is different.

It cuts the other way too. A finding marked high can sit on a resource that nothing can reach: no public exposure, no identity that leads to it. It still deserves fixing, but it can wait behind the medium that completes a path to production data.

This is why teams that work strictly by severity often feel busy without feeling safer. They are closing findings in an order that has little to do with how an attack would actually unfold.

The Hops to Look For in AWS

Most AWS attack paths are built from a small number of hop types. Knowing them makes paths much easier to spot.

Network to compute

Security groups and network ACLs open to the internet, instances and containers with public IP addresses, and load balancers that forward traffic to something that was meant to be internal. This is where most paths begin.

Compute to identity

Anything that runs code in AWS usually runs as a role: EC2 instance profiles, Lambda execution roles, ECS task roles. Getting code execution on the resource means getting its credentials. Instance metadata that still allows IMDSv1 makes this easier still, because a server-side request forgery bug in the application can be enough to read the role's keys without a shell.

Identity to identity

Roles whose trust policies allow other principals to assume them, and permissions such as iam:PassRole that let one identity hand a more powerful role to a new resource. These hops are easy to miss because each policy looks reasonable when you read it alone. The escalation only appears when you follow one into the next.

Identity to data

The final hop: a role that can read S3 objects, decrypt with a KMS key, fetch from Secrets Manager or connect to an RDS database. Wildcards matter most here, because s3:* on * quietly turns one compromised role into access to every bucket in the account.

Breaking a Path

Because a path needs every link to hold, you only have to break one of them. That gives you a choice, and the right answer is usually the cheapest link to cut, not the most severe one.

In the example above you could close port 22, move the instance off its public IP, narrow the role to the one prefix the application reads, or enforce IMDSv2. Any one of those breaks this particular path. Closing the port is a one-line change; rewriting the role might need a code change and a release.

Look, too, for links that many paths share. A single over-permissive role used by a dozen instances, or a security group attached to a whole fleet, can sit in the middle of many paths at once. Fixing one of those choke points removes all of them together. This is how a backlog of hundreds of findings can come down to a handful of changes that actually reduce your exposure.

Where to Start

You can begin thinking in paths without any new tooling:

  1. Write down the handful of things in each account you most need to protect: the data stores, keys and secrets an attacker would want.
  2. Write down the entry points: everything reachable from the internet, and every long-lived credential that lives outside AWS.
  3. For each target, ask which identities can reach it, and work backwards from those identities to the compute and network that leads to them.

Doing this by hand works for a small account and a short afternoon. It does not keep up with an environment that changes every day, where a security group edited on Tuesday can complete a path that did not exist on Monday.

GuardKite does this continuously. It runs its checks across your AWS accounts every day, then connects the results into attack paths, so you can see which findings combine into a route from the internet to your data. The first link in the example above is a real check, security groups should not allow ingress from 0.0.0.0/0 to port 22, and so is the IMDS hop: EC2 instances should use IMDSv2.