mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 04:21:52 +00:00
646 lines
40 KiB
Plaintext
646 lines
40 KiB
Plaintext
---
|
|
title: "Attack Paths"
|
|
description: "Identify privilege escalation chains and security misconfigurations across cloud environments using graph-based analysis."
|
|
---
|
|
|
|
import { VersionBadge } from "/snippets/version-badge.mdx"
|
|
|
|
<VersionBadge version="5.17.0" />
|
|
|
|
Attack Paths analyzes relationships between cloud resources, permissions, and security findings to detect how privileges can be escalated and how misconfigurations can be exploited by threat actors.
|
|
|
|
By mapping these relationships as a graph, Attack Paths reveals risks that individual security checks cannot detect on their own — such as an IAM role that can escalate its own permissions, or a chain of policies that grants unintended access to sensitive resources.
|
|
|
|
<Note>
|
|
Attack Paths is currently available for **AWS** providers. Support for
|
|
additional providers is planned.
|
|
</Note>
|
|
|
|
## Prerequisites
|
|
|
|
The following prerequisites are required for Attack Paths:
|
|
|
|
- **An AWS provider is configured** with valid credentials in Prowler App. For setup instructions, see [Getting Started with AWS](/user-guide/providers/aws/getting-started-aws).
|
|
- **At least one scan has completed** on the configured AWS provider. Attack Paths scans run automatically alongside regular security scans — no separate configuration is required.
|
|
|
|
## How Attack Paths Scans Work
|
|
|
|
Attack Paths scans are generated automatically when a security scan runs on an AWS provider. Each completed scan produces graph data that maps relationships between IAM principals, policies, trust configurations, and other resources.
|
|
|
|
Once the scan finishes and the graph data is ready, the scan appears in the Attack Paths scan table with a **Completed** status. Scans that are still processing display as **Executing** or **Scheduled**.
|
|
|
|
<Note>
|
|
Since Prowler scans all configured providers every **24 hours** by default,
|
|
Attack Paths data stays up to date automatically.
|
|
</Note>
|
|
|
|
## Accessing Attack Paths
|
|
|
|
To open Attack Paths, click **Attack Paths** in the left navigation menu.
|
|
|
|
<img
|
|
src="/images/prowler-app/attack-paths/navigation.png"
|
|
alt="Attack Paths navigation menu entry"
|
|
width="700"
|
|
/>
|
|
|
|
The main interface is divided into two areas:
|
|
|
|
- **Left panel:** A table listing all available Attack Paths scans
|
|
- **Right panel:** The query selector, parameter form, and execute controls
|
|
|
|
## Selecting a Scan
|
|
|
|
The scans table displays all Attack Paths scans with the following columns:
|
|
|
|
- **Provider / Account:** The AWS provider alias and account identifier
|
|
- **Last scan date:** When the scan completed
|
|
- **Status:** Current state of the scan (Completed, Executing, Scheduled, or Failed)
|
|
- **Progress:** Completion percentage for in-progress scans
|
|
- **Duration:** Total scan time
|
|
|
|
To select a scan for analysis, click **Select** on any row with a **Completed** status.
|
|
|
|
<img
|
|
src="/images/prowler-app/attack-paths/scan-list-table.png"
|
|
alt="Attack Paths scan list table showing completed scans"
|
|
width="700"
|
|
/>
|
|
|
|
<Note>
|
|
Only scans with a **Completed** status and ready graph data can be selected.
|
|
Scans that are still executing or have failed appear with disabled action
|
|
buttons.
|
|
</Note>
|
|
|
|
## Choosing a Query
|
|
|
|
After selecting a scan, the right panel activates a query dropdown. Each query targets a specific type of privilege escalation or misconfiguration pattern.
|
|
|
|
To choose a query, click the dropdown and select from the available options. Each option displays:
|
|
|
|
- **Query name:** A descriptive title (e.g., "IAM Privilege Escalation via AssumeRole")
|
|
- **Short description:** A brief summary of what the query detects
|
|
|
|
<img
|
|
src="/images/prowler-app/attack-paths/query-selector.png"
|
|
alt="Attack Paths query selector dropdown showing available queries"
|
|
width="700"
|
|
/>
|
|
|
|
Once selected, a description card appears below the dropdown with additional context about the query, including attribution links to external references when available.
|
|
|
|
## Configuring Query Parameters
|
|
|
|
Some queries accept optional or required parameters to narrow the scope of the analysis. When a query has parameters, a dynamic form appears below the query description.
|
|
|
|
- **Required fields** are marked with an asterisk (\*) and must be filled before executing
|
|
- **Optional fields** refine the query results but are not mandatory
|
|
|
|
If a query requires no parameters, the form displays a message confirming that the query is ready to execute.
|
|
|
|
<img
|
|
src="/images/prowler-app/attack-paths/query-parameters.png"
|
|
alt="Attack Paths query parameter form with fields"
|
|
width="700"
|
|
/>
|
|
|
|
## Writing Custom openCypher Queries
|
|
|
|
In addition to the built-in queries, Attack Paths supports custom read-only [openCypher](https://opencypher.org/) queries. Custom queries provide direct access to the underlying graph so security teams can answer ad-hoc questions, prototype detections, or extend coverage beyond the built-in catalogue.
|
|
|
|
To write a custom query, select **Custom openCypher query** from the query dropdown. A code editor with syntax highlighting and line numbers appears, ready to receive the query.
|
|
|
|
### Constraints and Safety Limits
|
|
|
|
Custom queries are sandboxed to keep the graph database safe and responsive:
|
|
|
|
- **Read-only:** Only read operations are allowed. Statements that mutate the graph (`CREATE`, `MERGE`, `SET`, `DELETE`, `REMOVE`, `DROP`, `LOAD CSV`, `CALL { ... }` writes, etc.) are rejected before execution.
|
|
- **Length limit:** Each query is capped at **10,000 characters**.
|
|
- **Scoped to the selected scan:** Results are automatically scoped to the provider and scan selected on the left panel. There is no need to filter by tenant or scan identifier in the query body.
|
|
|
|
### Example Queries
|
|
|
|
The following examples are read-only and can be pasted directly into the editor. Each one demonstrates a different graph traversal pattern.
|
|
|
|
**Internet-exposed EC2 instances with their security group rules:**
|
|
|
|
```cypher
|
|
MATCH (i:EC2Instance)--(sg:EC2SecurityGroup)--(rule:IpPermissionInbound)
|
|
WHERE i.exposed_internet = true
|
|
RETURN i.instanceid AS instance, sg.name AS security_group,
|
|
rule.fromport AS from_port, rule.toport AS to_port
|
|
LIMIT 25
|
|
```
|
|
|
|
**EC2 instances that can assume IAM roles:**
|
|
|
|
```cypher
|
|
MATCH (i:EC2Instance)-[:STS_ASSUMEROLE_ALLOW]->(r:AWSRole)
|
|
WHERE i.exposed_internet = true
|
|
RETURN i.instanceid AS instance, r.name AS role_name, r.arn AS role_arn
|
|
LIMIT 25
|
|
```
|
|
|
|
**IAM principals with wildcard Allow statements:**
|
|
|
|
```cypher
|
|
MATCH (principal:AWSPrincipal)--(policy:AWSPolicy)--(stmt:AWSPolicyStatement)
|
|
WHERE stmt.effect = 'Allow'
|
|
AND ANY(action IN stmt.action WHERE action = '*')
|
|
RETURN principal.arn AS principal, policy.arn AS policy,
|
|
stmt.action AS actions, stmt.resource AS resources
|
|
LIMIT 25
|
|
```
|
|
|
|
**Critical findings on internet-exposed resources:**
|
|
|
|
```cypher
|
|
MATCH (i:EC2Instance)-[:HAS_FINDING]->(f:ProwlerFinding)
|
|
WHERE i.exposed_internet = true AND f.status = 'FAIL'
|
|
AND f.severity IN ['critical', 'high']
|
|
RETURN i.instanceid AS instance, f.check_id AS check,
|
|
f.severity AS severity, f.status AS status
|
|
LIMIT 50
|
|
```
|
|
|
|
**Roles trusting an AWS service (building block for PassRole escalation):**
|
|
|
|
```cypher
|
|
MATCH (r:AWSRole)-[:TRUSTS_AWS_PRINCIPAL]->(p:AWSPrincipal)
|
|
WHERE p.arn ENDS WITH '.amazonaws.com'
|
|
RETURN r.name AS role_name, r.arn AS role_arn, p.arn AS trusted_service
|
|
LIMIT 25
|
|
```
|
|
|
|
### Advanced Attack Path Scenarios
|
|
|
|
The following scenarios show how to compose graph traversals into real attack-path stories. Each query can be pasted directly into the custom query box: the API auto-scopes them to the selected provider and injects tenant/provider isolation, so there is no need to include account identifiers or `$provider_uid` in the text. All queries are openCypher v9 (Neo4j and Neptune compatible).
|
|
|
|
#### 1. Live attacker on the box that owns the keys
|
|
|
|
**Query story:** Finds an internet-exposed EC2 under an active GuardDuty SSH brute-force whose instance role can assume a higher-privileged role that can read a sensitive S3 bucket.
|
|
|
|
```cypher
|
|
MATCH path_ec2 = (acct:AWSAccount)--(ec2:EC2Instance)
|
|
WHERE ec2.exposed_internet = true
|
|
MATCH p0 = (gd:GuardDutyFinding)-[:AFFECTS]->(ec2)
|
|
MATCH p1 = (ec2)-[:INSTANCE_PROFILE]->(prof:AWSInstanceProfile)-[:ASSOCIATED_WITH]->(low:AWSRole)
|
|
MATCH p2 = (low)-[:STS_ASSUMEROLE_ALLOW]-(high:AWSRole)
|
|
MATCH p3 = (high)--(pol:AWSPolicy)--(stmt:AWSPolicyStatement)
|
|
OPTIONAL MATCH path_net = (internet:Internet)-[:CAN_ACCESS]->(ec2)
|
|
MATCH path_s3 = (acct)--(s3:S3Bucket)
|
|
WHERE high <> low
|
|
AND stmt.effect = 'Allow'
|
|
AND size([a IN stmt.action WHERE
|
|
toLower(a) STARTS WITH 's3:getobject'
|
|
OR toLower(a) STARTS WITH 's3:listbucket'
|
|
OR toLower(a) IN ['s3:*']
|
|
]) > 0
|
|
AND size([r IN stmt.resource WHERE
|
|
r CONTAINS s3.name
|
|
]) > 0
|
|
RETURN path_net, path_ec2, p0, p1, p2, p3, path_s3
|
|
```
|
|
|
|
**How it's built:**
|
|
|
|
- `path_ec2` anchors the graph on the account node and its internet-exposed EC2 instance, via a real account-to-resource edge. This is the visible spine that keeps everything connected.
|
|
- `p0` ties a `GuardDutyFinding` to that instance through the `AFFECTS` edge (the live SSH brute-force alert).
|
|
- `p1` walks the real graph edges from the instance to its instance profile to the role it runs as.
|
|
- `p2` follows the `STS_ASSUMEROLE_ALLOW` edge to the higher-privileged role the low role can assume. It is undirected so it works regardless of how the assume edge was ingested. `high <> low` stops a role matching itself.
|
|
- `p3` walks that role into its policy and policy statement.
|
|
- `path_net` is the optional `Internet -[:CAN_ACCESS]-> instance` edge. It makes "from the internet" literal on screen. Optional so a missing `Internet` node never breaks the query live.
|
|
- `path_s3` connects the sensitive bucket to the same account node, so it draws connected instead of floating. There is no physical edge from a role to a bucket; the grant is logical, enforced in the `WHERE`: the statement must allow an S3 read action (list comprehension over the `action` array) and its resource must cover the bucket (`CONTAINS s3.name`). The account is the shared hub; the bucket hanging off it next to the role chain is the teaching moment — the access exists only in IAM.
|
|
|
|
#### 2. Who can read the crown jewels
|
|
|
|
**Query story:** The sensitive bucket from the previous scenario seen from the data side: every role whose IAM policy can read it, regardless of how the role is reached.
|
|
|
|
```cypher
|
|
MATCH (s3:S3Bucket)
|
|
WHERE toLower(s3.name) CONTAINS 'sensitive'
|
|
MATCH (role:AWSRole)--(pol:AWSPolicy)--(stmt:AWSPolicyStatement)
|
|
WHERE stmt.effect = 'Allow'
|
|
AND size([a IN stmt.action WHERE
|
|
toLower(a) STARTS WITH 's3:get'
|
|
OR toLower(a) STARTS WITH 's3:list'
|
|
OR toLower(a) IN ['s3:*']
|
|
]) > 0
|
|
AND size([r IN stmt.resource WHERE
|
|
r CONTAINS s3.name
|
|
]) > 0
|
|
WITH DISTINCT s3, role
|
|
LIMIT 25
|
|
MATCH path_s3 = (acct:AWSAccount)--(s3)
|
|
MATCH path_role = (acct)--(role)
|
|
RETURN path_s3, path_role
|
|
```
|
|
|
|
**How it's built:** data-centric, not attacker-centric — the same bucket the previous kill chain exfiltrates, approached from the other direction.
|
|
|
|
- The `S3Bucket` is bound first by name (one node), so everything else filters against it.
|
|
- `(role:AWSRole)--(pol:AWSPolicy)--(stmt:AWSPolicyStatement)` reaches statements only *through a role*, never via a global statement scan. A blanket `AWSPolicyStatement` scan also hits resource-policy statements whose shape differs and makes the list comprehension fail outright.
|
|
- The `WHERE` filters in place: an S3 read action plus a resource that names that bucket.
|
|
- `WITH DISTINCT s3, role LIMIT 25` collapses undirected-traversal duplicates and hard-caps the result.
|
|
- `path_s3` and `path_role` attach the account hubs only after the cap, against at most 25 rows, so the bucket and role(s) draw connected through the account instead of floating.
|
|
- No internet or EC2 here; this answers "who has the keys" instead of "how would an attacker get in."
|
|
|
|
#### 3. Lateral reach from an internet-exposed instance
|
|
|
|
**Query story:** The wide-angle view of the live-attacker scenario: every internet-exposed EC2, the role it runs as, and every role that role can assume. The first scenario is one specific exfiltration path inside this reach, under live attack.
|
|
|
|
```cypher
|
|
MATCH path_ec2 = (acct:AWSAccount)--(ec2:EC2Instance)
|
|
WHERE ec2.exposed_internet = true
|
|
MATCH p1 = (ec2)-[:INSTANCE_PROFILE]->(prof:AWSInstanceProfile)-[:ASSOCIATED_WITH]->(low:AWSRole)
|
|
MATCH p2 = (low)-[:STS_ASSUMEROLE_ALLOW]-(high:AWSRole)
|
|
OPTIONAL MATCH path_net = (internet:Internet)-[:CAN_ACCESS]->(ec2)
|
|
WHERE high <> low
|
|
RETURN path_net, path_ec2, p1, p2
|
|
```
|
|
|
|
**How it's built:** widens the lens instead of filtering down. It stops at the assume-role hop and shows every role reachable from any internet-exposed instance, without filtering down to a specific S3 leg.
|
|
|
|
- `path_ec2` is the account-to-instance spine.
|
|
- `p1` walks to the instance role.
|
|
- `p2` fans out to every role that role can assume.
|
|
- `path_net` adds the optional `Internet -[:CAN_ACCESS]->` edge.
|
|
- The first scenario is the specific exfiltration path under live attack; this is the broader privilege reach an attacker inherits the moment they land on the box.
|
|
|
|
#### 4. Role-chain privilege escalation
|
|
|
|
**Query story:** A pure-IAM escalation, no compromised instance: a role that can assume a second role whose policy lets it assume a third, admin-level role.
|
|
|
|
```cypher
|
|
MATCH path_root = (acct:AWSAccount)--(r1:AWSRole)
|
|
MATCH p1 = (r1)-[:STS_ASSUMEROLE_ALLOW]-(r2:AWSRole)
|
|
MATCH p2 = (r2)--(pol:AWSPolicy)--(stmt:AWSPolicyStatement)
|
|
MATCH path_admin = (acct)--(admin:AWSRole)
|
|
WHERE r1 <> r2 AND r1 <> admin AND r2 <> admin
|
|
AND stmt.effect = 'Allow'
|
|
AND size([a IN stmt.action WHERE
|
|
toLower(a) IN ['sts:*', 'sts:assumerole']
|
|
]) > 0
|
|
AND size([res IN stmt.resource WHERE
|
|
res CONTAINS admin.name
|
|
]) > 0
|
|
RETURN path_root, p1, p2, path_admin
|
|
```
|
|
|
|
**How it's built:**
|
|
|
|
- `path_root` anchors role 1 to the account node, the spine that keeps the picture connected.
|
|
- `p1` is the one real assume edge in the chain (role 1 to role 2).
|
|
- `p2` walks role 2 into its policy and statement.
|
|
- `path_admin` connects the target admin role to the same account node so it draws connected. The third hop is not a graph edge: it exists only as `sts:AssumeRole` on that role's ARN inside the statement. The query proves it the same way the first scenario proves S3 access — the statement action must include an assume-role action and its resource list must reference the admin role's name.
|
|
- The three `<>` guards stop a role matching itself at any position.
|
|
|
|
#### 5. External identity trust map
|
|
|
|
**Query story:** Finds external identity providers (SSO, GitHub, GitLab, Terraform Cloud) and the AWS roles they are trusted to assume.
|
|
|
|
```cypher
|
|
MATCH p = (role:AWSRole)-[:TRUSTS_AWS_PRINCIPAL]-(idp:AWSPrincipal)
|
|
WHERE idp.arn CONTAINS 'saml-provider'
|
|
OR idp.arn CONTAINS 'oidc-provider'
|
|
MATCH path_role = (acct:AWSAccount)--(role)
|
|
RETURN p, path_role
|
|
```
|
|
|
|
**How it's built:** federated principals are stored as `AWSPrincipal` nodes whose ARN contains `saml-provider` (SSO) or `oidc-provider` (GitHub, GitLab, Terraform Cloud).
|
|
|
|
- `p` matches the trust edge undirected. It is written `(AWSRole)-[:TRUSTS_AWS_PRINCIPAL]->(AWSPrincipal)`, role to principal, so a directed `principal -> role` match returns nothing; undirected matches regardless of ingest direction.
|
|
- The `WHERE` keeps only SAML or OIDC providers, drawing a fan-out from each external identity provider to every role it can assume (including reserved SSO admin roles).
|
|
- `path_role` ties every trusted role to the account node so the provider stars share one spine instead of drawing as separate islands.
|
|
|
|
#### 6. Federated SSO roles flagged as admin or privesc
|
|
|
|
**Query story:** The dangerous subset of the trust map above — externally-federated SSO roles that Prowler also flags for AdministratorAccess or privilege escalation.
|
|
|
|
```cypher
|
|
MATCH (idp:AWSPrincipal)-[:TRUSTS_AWS_PRINCIPAL]-(role:AWSRole)
|
|
WHERE idp.arn CONTAINS 'saml-provider'
|
|
OR idp.arn CONTAINS 'oidc-provider'
|
|
MATCH (role)-[:HAS_FINDING]-(pf:ProwlerFinding)
|
|
WHERE pf.status = 'FAIL'
|
|
AND pf.check_id IN [
|
|
'iam_inline_policy_allows_privilege_escalation',
|
|
'iam_role_administratoraccess_policy',
|
|
'iam_inline_policy_no_administrative_privileges',
|
|
'iam_user_administrator_access_policy'
|
|
]
|
|
WITH DISTINCT idp, role, pf
|
|
LIMIT 60
|
|
MATCH path_root = (acct:AWSAccount)--(role)
|
|
MATCH p_trust = (idp)-[:TRUSTS_AWS_PRINCIPAL]-(role)
|
|
MATCH p_find = (role)-[:HAS_FINDING]-(pf)
|
|
RETURN path_root, p_trust, p_find
|
|
```
|
|
|
|
**How it's built:** a plain "list every flagged identity" query is a wide fan that draws as a column, and `ProwlerFinding` nodes accumulate across scans with no scan filter available in custom queries.
|
|
|
|
- The first MATCH plus `WHERE` keeps only roles trusted by a SAML or OIDC provider (trust edge undirected, so direction does not matter).
|
|
- The second MATCH plus `check_id IN [...]` keeps only those carrying one of the four privilege-escalation or admin checks.
|
|
- `WITH DISTINCT ... LIMIT 60` collapses duplicate finding nodes and hard-caps the result.
|
|
- `p_trust`, `p_find`, and `path_root` draw it connected three ways: provider to role through the trust edge, role to its finding, and role to the account.
|
|
- The previous scenario shows who can walk in; this shows which of those roles Prowler already flags as over-privileged.
|
|
|
|
#### 7. World-readable S3 buckets
|
|
|
|
**Query story:** Unlike the IAM-gated sensitive bucket in scenarios 1 and 2, these buckets are open to anyone on the internet with no credentials at all.
|
|
|
|
```cypher
|
|
MATCH path_s3 = (acct:AWSAccount)--(s3:S3Bucket)
|
|
WHERE s3.anonymous_access = true
|
|
OPTIONAL MATCH p = (s3)--(stmt:S3PolicyStatement)
|
|
RETURN path_s3, p
|
|
```
|
|
|
|
**How it's built:** the counterpoint to scenarios 1 and 2 — there the sensitive bucket is reachable only through an IAM role chain; here the bucket needs no role at all.
|
|
|
|
- `path_s3` connects each public bucket to its account node so they draw connected. Cartography sets `anonymous_access = true` when a bucket's policy or ACL allows public access.
|
|
- `p` is an optional match that pulls in the `S3PolicyStatement` granting the access where one exists, so the public grant is visible next to the bucket. Buckets that are public via ACL only still show, connected to the account.
|
|
|
|
#### 8. Internet exposure surface
|
|
|
|
**Query story:** The raw external attack surface behind scenarios 1 and 3: every internet-exposed EC2 instance with its security groups and the exact inbound ports left open.
|
|
|
|
```cypher
|
|
MATCH path_ec2 = (acct:AWSAccount)--(ec2:EC2Instance)
|
|
WHERE ec2.exposed_internet = true
|
|
MATCH p1 = (ec2)--(sg:EC2SecurityGroup)--(rule:IpPermissionInbound)
|
|
OPTIONAL MATCH path_net = (internet:Internet)-[:CAN_ACCESS]->(ec2)
|
|
OPTIONAL MATCH p2 = (ec2)-[:INSTANCE_PROFILE]->(:AWSInstanceProfile)-[:ASSOCIATED_WITH]->(:AWSRole)
|
|
RETURN path_net, path_ec2, p1, p2
|
|
```
|
|
|
|
**How it's built:** `exposed_internet = true` is Cartography's computed reachability flag.
|
|
|
|
- `path_ec2` hubs all exposed instances on the account node so they draw as one picture.
|
|
- `p1` joins each instance to its security groups and inbound rules so the open ports are on screen.
|
|
- `path_net` adds the optional `Internet -[:CAN_ACCESS]->` edge so the external reachability is explicit.
|
|
- `p2` optionally adds the instance role, which connects this surface view back to the kill chains in scenarios 1 and 3.
|
|
|
|
### Tips for Writing Queries
|
|
|
|
- Start small with `LIMIT` to inspect the shape of the data before broadening the pattern.
|
|
- Use `RETURN` projections (`RETURN n.name, n.region`) instead of returning whole nodes to keep responses compact.
|
|
- Combine resource nodes with `ProwlerFinding` nodes via `HAS_FINDING` to correlate misconfigurations with the affected resources.
|
|
- When a query times out or returns no rows, simplify the pattern step by step until the first variant runs successfully, then add constraints back.
|
|
|
|
### Cartography Schema Reference
|
|
|
|
Attack Paths graphs are populated by [Cartography](https://github.com/cartography-cncf/cartography), an open-source graph ingestion framework. The node labels, relationship types, and properties available in custom queries follow the upstream Cartography schema for the corresponding provider.
|
|
|
|
For the complete catalogue of node labels and relationships available in custom queries, refer to the official Cartography schema documentation:
|
|
|
|
- **AWS:** [Cartography AWS Schema](https://cartography-cncf.github.io/cartography/modules/aws/schema.html)
|
|
|
|
In addition to the upstream schema, Prowler enriches the graph with:
|
|
|
|
- **`ProwlerFinding`** nodes representing Prowler check results, linked to affected resources via `HAS_FINDING` relationships.
|
|
- **`Internet`** nodes used to model exposure paths from the public internet to internal resources.
|
|
|
|
<Note>
|
|
AI assistants connected through Prowler MCP Server can fetch the exact
|
|
Cartography schema for the active scan via the
|
|
`prowler_app_get_attack_paths_cartography_schema` tool. This guarantees that
|
|
generated queries match the schema version pinned by the running Prowler
|
|
release.
|
|
</Note>
|
|
|
|
## Executing a Query
|
|
|
|
To run the selected query against the scan data, click **Execute Query**. The button displays a loading state while the query processes.
|
|
|
|
If the query returns no results, an informational message appears. Common reasons include:
|
|
|
|
- **No matching patterns found:** The scanned environment does not contain the privilege escalation chain the query targets
|
|
- **Insufficient permissions:** The scan credentials may not have captured all the data the query needs
|
|
|
|
<img
|
|
src="/images/prowler-app/attack-paths/execute-query.png"
|
|
alt="Attack Paths right panel with query selected and execute button"
|
|
width="700"
|
|
/>
|
|
|
|
## Exploring the Graph
|
|
|
|
After a successful execution, the graph visualization renders below the query builder in a full-width panel. The graph maps relationships between cloud resources, IAM entities, and security findings.
|
|
|
|
### Node Types
|
|
|
|
- **Resource nodes** (rounded pills): Represent cloud resources such as IAM roles, policies, EC2 instances, and S3 buckets. Each resource type has a distinct color.
|
|
- **Finding nodes** (hexagons): Represent Prowler security findings linked to resources in the graph. Colors indicate severity level (critical, high, medium, low).
|
|
|
|
### Edge Types
|
|
|
|
- **Solid lines:** Direct relationships between resources (e.g., a role attached to a policy)
|
|
- **Dashed lines:** Connections between resources and their associated findings
|
|
|
|
A **legend** at the bottom of the graph lists all node types and edge types present in the current view.
|
|
|
|
<img
|
|
src="/images/prowler-app/attack-paths/graph-visualization.png"
|
|
alt="Attack Paths graph showing nodes, edges, and legend"
|
|
width="700"
|
|
/>
|
|
|
|
## Interacting with the Graph
|
|
|
|
### Filtering by Node
|
|
|
|
Click any node in the graph to filter the view and display only paths that pass through that node. When a filter is active:
|
|
|
|
- An information banner shows which node is selected
|
|
- Click **Back to Full View** to restore the complete graph
|
|
|
|
<img
|
|
src="/images/prowler-app/attack-paths/graph-filtered.png"
|
|
alt="Attack Paths graph filtered to show paths through a selected node"
|
|
width="700"
|
|
/>
|
|
|
|
### Graph Controls
|
|
|
|
The toolbar in the top-right corner of the graph provides:
|
|
|
|
- **Zoom in / Zoom out:** Adjust the zoom level
|
|
- **Fit to screen:** Reset the view to fit all nodes
|
|
- **Export:** Download the current graph as an SVG file
|
|
- **Fullscreen:** Open the graph in a full-screen modal with a side-by-side node detail panel
|
|
|
|
<Note>
|
|
Use **Ctrl + Scroll** (or **Cmd + Scroll** on macOS) to zoom directly within
|
|
the graph area.
|
|
</Note>
|
|
|
|
## Viewing Node Details
|
|
|
|
Click any node to open the **Node Details** panel below the graph. This panel displays:
|
|
|
|
- **Node type:** The resource category (e.g., "IAM Role," "EC2 Instance")
|
|
- **Properties:** All attributes of the selected node, including identifiers, timestamps, and configuration details
|
|
- **Related findings** (for resource nodes): A list of Prowler findings linked to the resource, with severity, title, and status
|
|
- **Affected resources** (for finding nodes): A list of resources associated with the finding
|
|
|
|
For finding nodes, a "View Finding" button links directly to the finding detail page for further investigation.
|
|
|
|
<img
|
|
src="/images/prowler-app/attack-paths/node-details.png"
|
|
alt="Attack Paths node detail panel showing properties and related findings"
|
|
width="700"
|
|
/>
|
|
|
|
## Fullscreen Mode
|
|
|
|
To expand the graph for detailed exploration, click the fullscreen icon in the graph toolbar. The fullscreen modal provides:
|
|
|
|
- The full graph visualization with all zoom and export controls
|
|
- A side panel for node details that appears when a node is selected
|
|
- All filtering and interaction capabilities available in the standard view
|
|
|
|
<img
|
|
src="/images/prowler-app/attack-paths/fullscreen-mode.png"
|
|
alt="Attack Paths fullscreen mode with graph and node detail side panel"
|
|
width="700"
|
|
/>
|
|
|
|
## Using Attack Paths with the MCP Server and Lighthouse AI
|
|
|
|
Attack Paths capabilities are also available through the [Prowler MCP Server](/getting-started/products/prowler-mcp), enabling interaction with Attack Paths data via AI assistants like Claude Desktop, Cursor, and other MCP clients.
|
|
|
|
[Prowler Lighthouse AI](/getting-started/products/prowler-lighthouse-ai) also supports Attack Paths queries, allowing you to analyze privilege escalation chains and security misconfigurations directly from the chat interface.
|
|
|
|
The following MCP tools are available for Attack Paths:
|
|
|
|
- **`prowler_app_list_attack_paths_scans`** - List and filter Attack Paths scans
|
|
- **`prowler_app_list_attack_paths_queries`** - Discover available queries for a completed scan
|
|
- **`prowler_app_run_attack_paths_query`** - Execute a query and retrieve graph results with nodes and relationships
|
|
- **`prowler_app_get_attack_paths_cartography_schema`** - Retrieve the Cartography graph schema for custom openCypher queries
|
|
|
|
### Example Questions
|
|
|
|
Ask through the MCP Server or Lighthouse AI:
|
|
|
|
- "Find EC2 instances exposed to the internet with access to sensitive S3 buckets"
|
|
- "Are there any IAM roles that can escalate their own privileges?"
|
|
- "Show me all internet-facing resources with open security groups"
|
|
- "Which principals can create Lambda functions with privileged roles?"
|
|
- "List all RDS instances with storage encryption disabled"
|
|
- "Find S3 buckets that allow anonymous access"
|
|
- "Are there any CloudFormation stacks that could be hijacked for privilege escalation?"
|
|
- "Show me all roles that can be assumed for lateral movement"
|
|
|
|
### Supported Queries
|
|
|
|
Attack Paths currently supports the following built-in queries for AWS:
|
|
|
|
#### Custom Attack Path Queries
|
|
|
|
| Query | Description |
|
|
|---|---|
|
|
| **Internet-Exposed EC2 with Sensitive S3 Access** | Find SSH-exposed EC2 instances that can assume roles to read tagged sensitive S3 buckets |
|
|
|
|
#### Basic Resource Queries
|
|
|
|
| Query | Description |
|
|
|---|---|
|
|
| **RDS Instances Inventory** | List all provisioned RDS database instances in the account |
|
|
| **Unencrypted RDS Instances** | Find RDS instances with storage encryption disabled |
|
|
| **S3 Buckets with Anonymous Access** | Find S3 buckets that allow anonymous access |
|
|
| **IAM Statements Allowing All Actions** | Find IAM policy statements that allow all actions via wildcard (\*) |
|
|
| **IAM Statements Allowing Policy Deletion** | Find IAM policy statements that allow iam:DeletePolicy |
|
|
| **IAM Statements Allowing Create Actions** | Find IAM policy statements that allow any create action |
|
|
|
|
#### Network Exposure Queries
|
|
|
|
| Query | Description |
|
|
|---|---|
|
|
| **Internet-Exposed EC2 Instances** | Find EC2 instances flagged as exposed to the internet |
|
|
| **Open Security Groups on Internet-Facing Resources** | Find internet-facing resources with security groups allowing inbound from 0.0.0.0/0 |
|
|
| **Internet-Exposed Classic Load Balancers** | Find Classic Load Balancers exposed to the internet with their listeners |
|
|
| **Internet-Exposed ALB/NLB Load Balancers** | Find ELBv2 (ALB/NLB) load balancers exposed to the internet with their listeners |
|
|
| **Resource Lookup by Public IP** | Find the AWS resource associated with a given public IP address |
|
|
|
|
#### Privilege Escalation Queries
|
|
|
|
These queries are based on research from [pathfinding.cloud](https://pathfinding.cloud) by Datadog.
|
|
|
|
| Query | Description |
|
|
|---|---|
|
|
| **App Runner Service Creation with Privileged Role (APPRUNNER-001)** | Create an App Runner service with a privileged IAM role to gain its permissions |
|
|
| **App Runner Service Update for Role Access (APPRUNNER-002)** | Update an existing App Runner service to leverage its already-attached privileged role |
|
|
| **Bedrock Code Interpreter with Privileged Role (BEDROCK-001)** | Create a Bedrock AgentCore Code Interpreter with a privileged role attached |
|
|
| **Bedrock Code Interpreter Session Hijacking (BEDROCK-002)** | Start a session on an existing Bedrock code interpreter to exfiltrate its privileged role credentials |
|
|
| **CloudFormation Stack Creation with Privileged Role (CLOUDFORMATION-001)** | Create a CloudFormation stack with a privileged role to provision arbitrary AWS resources |
|
|
| **CloudFormation Stack Update for Role Access (CLOUDFORMATION-002)** | Update an existing CloudFormation stack to leverage its already-attached privileged service role |
|
|
| **CloudFormation StackSet Creation with Privileged Role (CLOUDFORMATION-003)** | Create a CloudFormation StackSet with a privileged execution role to provision arbitrary resources across accounts |
|
|
| **CloudFormation StackSet Update with Privileged Role (CLOUDFORMATION-004)** | Update an existing CloudFormation StackSet to inject malicious resources using a privileged execution role |
|
|
| **CloudFormation Change Set Privilege Escalation (CLOUDFORMATION-005)** | Create and execute a change set on an existing stack to leverage its privileged service role |
|
|
| **CodeBuild Project Creation with Privileged Role (CODEBUILD-001)** | Create a CodeBuild project with a privileged role to execute arbitrary code via a malicious buildspec |
|
|
| **CodeBuild Buildspec Override for Role Access (CODEBUILD-002)** | Start a build on an existing CodeBuild project with a buildspec override to execute code with its privileged role |
|
|
| **CodeBuild Batch Buildspec Override for Role Access (CODEBUILD-003)** | Start a batch build on an existing CodeBuild project with a buildspec override to execute code with its privileged role |
|
|
| **CodeBuild Batch Project Creation with Privileged Role (CODEBUILD-004)** | Create a CodeBuild project configured for batch builds with a privileged role to execute arbitrary code via a malicious buildspec |
|
|
| **Data Pipeline Creation with Privileged Role (DATAPIPELINE-001)** | Create a Data Pipeline with a privileged role to execute arbitrary commands on provisioned infrastructure |
|
|
| **EC2 Instance Launch with Privileged Role (EC2-001)** | Launch EC2 instances with privileged IAM roles to gain their permissions via IMDS |
|
|
| **EC2 Role Hijacking via UserData Injection (EC2-002)** | Inject malicious scripts into EC2 instance userData to gain the attached role's permissions |
|
|
| **Spot Instance Launch with Privileged Role (EC2-003)** | Launch EC2 Spot Instances with privileged IAM roles to gain their permissions via IMDS |
|
|
| **Launch Template Poisoning for Role Access (EC2-004)** | Inject malicious userData into launch templates that reference privileged roles, no PassRole needed |
|
|
| **EC2 Instance Connect SSH Access for Role Credentials (EC2INSTANCECONNECT-003)** | Push a temporary SSH key to an EC2 instance via Instance Connect to access its attached role credentials through IMDS |
|
|
| **ECS Service Creation with Privileged Role (ECS-001 - New Cluster)** | Create an ECS cluster and service with a privileged Fargate task role to execute arbitrary code |
|
|
| **ECS Task Execution with Privileged Role (ECS-002 - New Cluster)** | Create an ECS cluster and run a one-off Fargate task with a privileged role to execute arbitrary code |
|
|
| **ECS Service Creation with Privileged Role (ECS-003 - Existing Cluster)** | Deploy a Fargate service with a privileged role on an existing ECS cluster |
|
|
| **ECS Task Execution with Privileged Role (ECS-004 - Existing Cluster)** | Run a one-off Fargate task with a privileged role on an existing ECS cluster |
|
|
| **ECS Task Start with Privileged Role on EC2 (ECS-005 - Existing Cluster)** | Register a task definition with a privileged role and start it on an EC2 container instance to execute arbitrary code |
|
|
| **ECS Exec Container Hijacking for Role Credentials (ECS-006)** | Shell into a running ECS container via ECS Exec to steal the attached task role's credentials |
|
|
| **Glue Dev Endpoint with Privileged Role (GLUE-001)** | Create a Glue development endpoint with a privileged role attached to gain its permissions |
|
|
| **Glue Dev Endpoint SSH Hijacking via Update (GLUE-002)** | Update an existing Glue development endpoint to inject an SSH public key and access its attached role credentials |
|
|
| **Glue Job Creation with Privileged Role (GLUE-003)** | Create a Glue job with a privileged role and start it to execute arbitrary code with that role's permissions |
|
|
| **Glue Job Creation with Scheduled Trigger and Privileged Role (GLUE-004)** | Create a Glue job with a privileged role and a scheduled trigger to persistently execute arbitrary code |
|
|
| **Glue Job Hijacking via Update with Privileged Role (GLUE-005)** | Update an existing Glue job to attach a privileged role and inject malicious code, then start it to gain that role's permissions |
|
|
| **Glue Job Hijacking with Scheduled Trigger and Privileged Role (GLUE-006)** | Update an existing Glue job to attach a privileged role and inject malicious code, then create a scheduled trigger for persistent automated execution |
|
|
| **Policy Version Override for Self-Escalation (IAM-001)** | Create a new version of an attached policy with administrative permissions, instantly escalating the principal's own privileges |
|
|
| **Access Key Creation for Lateral Movement (IAM-002)** | Create access keys for other IAM users to gain their permissions and move laterally across the account |
|
|
| **Access Key Rotation Attack for Lateral Movement (IAM-003)** | Delete and recreate access keys for other IAM users to bypass the two-key limit and gain their permissions |
|
|
| **Console Login Profile Creation for Lateral Movement (IAM-004)** | Create console login profiles for other IAM users to access the AWS Console with their permissions |
|
|
| **Inline Policy Injection for Self-Escalation (IAM-005)** | Attach an inline policy with administrative permissions to your own role, instantly escalating privileges |
|
|
| **Console Password Override for Lateral Movement (IAM-006)** | Change the console password of other IAM users to log in as them and gain their permissions |
|
|
| **Inline Policy Injection on User for Self-Escalation (IAM-007)** | Attach an inline policy with administrative permissions to your own IAM user, instantly escalating privileges |
|
|
| **Managed Policy Attachment on User for Self-Escalation (IAM-008)** | Attach existing managed policies with administrative permissions to your own IAM user, instantly escalating privileges |
|
|
| **Managed Policy Attachment on Role for Self-Escalation (IAM-009)** | Attach existing managed policies with administrative permissions to your own IAM role, instantly escalating privileges |
|
|
| **Managed Policy Attachment on Group for Self-Escalation (IAM-010)** | Attach existing managed policies with administrative permissions to a group you belong to, escalating privileges for all group members |
|
|
| **Inline Policy Injection on Group for Self-Escalation (IAM-011)** | Attach an inline policy with administrative permissions to a group you belong to, escalating privileges for all group members |
|
|
| **Trust Policy Hijacking for Role Assumption (IAM-012)** | Modify a role's trust policy to allow yourself to assume it, gaining the role's permissions |
|
|
| **Group Membership Hijacking for Privilege Escalation (IAM-013)** | Add yourself to a privileged IAM group to inherit its permissions, gaining access to all policies attached to the group |
|
|
| **Managed Policy Attachment with Role Assumption for Lateral Movement (IAM-014)** | Attach administrative managed policies to another role you can assume, then assume it to gain elevated privileges |
|
|
| **Managed Policy Attachment with Access Key Creation for Lateral Movement (IAM-015)** | Attach administrative managed policies to another IAM user and create access keys for them to gain programmatic access with elevated privileges |
|
|
| **Policy Version Override with Role Assumption for Lateral Movement (IAM-016)** | Create a new version of a customer-managed policy attached to another role with administrative permissions, then assume that role to gain elevated access |
|
|
| **Inline Policy Injection with Role Assumption for Lateral Movement (IAM-017)** | Attach an inline policy with administrative permissions to another role you can assume, then assume it to gain elevated privileges |
|
|
| **Inline Policy Injection with Access Key Creation for Lateral Movement (IAM-018)** | Attach an inline policy with administrative permissions to another IAM user and create access keys for them to gain programmatic access with elevated privileges |
|
|
| **Managed Policy Attachment with Trust Policy Hijacking for Privilege Escalation (IAM-019)** | Attach administrative managed policies to a role and modify its trust policy to allow yourself to assume it, gaining elevated privileges without prior assume-role access |
|
|
| **Policy Version Override with Trust Policy Hijacking for Privilege Escalation (IAM-020)** | Create a new version of a customer-managed policy attached to a role with administrative permissions and modify its trust policy to assume it, without prior assume-role access |
|
|
| **Inline Policy Injection with Trust Policy Hijacking for Privilege Escalation (IAM-021)** | Add an inline policy with administrative permissions to a role and modify its trust policy to allow yourself to assume it, gaining elevated privileges without prior assume-role access |
|
|
| **Lambda Function Creation with Privileged Role (LAMBDA-001)** | Create a Lambda function with a privileged IAM role and invoke it to execute code with that role's permissions |
|
|
| **Lambda Function Creation with Event Source Trigger (LAMBDA-002)** | Create a Lambda function with a privileged IAM role and an event source mapping to trigger it automatically, executing code with the role's permissions |
|
|
| **Lambda Function Code Injection (LAMBDA-003)** | Modify the code of an existing Lambda function to execute arbitrary commands with the function's execution role permissions |
|
|
| **Lambda Function Code Injection with Direct Invocation (LAMBDA-004)** | Modify the code of an existing Lambda function and invoke it directly to execute arbitrary commands with the function's execution role permissions |
|
|
| **Lambda Function Code Injection with Resource Policy Grant (LAMBDA-005)** | Modify the code of an existing Lambda function and grant yourself invocation permission via its resource-based policy to execute code with the function's execution role |
|
|
| **Lambda Function Creation with Resource Policy Invocation (LAMBDA-006)** | Create a Lambda function with a privileged IAM role and grant yourself invocation permission via its resource-based policy to execute code with the role's permissions |
|
|
| **SageMaker Notebook Creation with Privileged Role (SAGEMAKER-001)** | Create a SageMaker notebook instance with a privileged IAM role to execute arbitrary code with the role's permissions via the Jupyter environment |
|
|
| **SageMaker Training Job Creation with Privileged Role (SAGEMAKER-002)** | Create a SageMaker training job with a privileged IAM role to execute arbitrary container code with the role's permissions |
|
|
| **SageMaker Processing Job Creation with Privileged Role (SAGEMAKER-003)** | Create a SageMaker processing job with a privileged IAM role to execute arbitrary container code with the role's permissions |
|
|
| **SageMaker Presigned Notebook URL for Privilege Escalation (SAGEMAKER-004)** | Generate a presigned URL to access an existing SageMaker notebook instance and execute code with its execution role's permissions |
|
|
| **SageMaker Notebook Lifecycle Config Injection (SAGEMAKER-005)** | Inject a malicious lifecycle configuration into an existing SageMaker notebook to execute code with the notebook's execution role during startup |
|
|
| **SSM Session Access for EC2 Role Credentials (SSM-001)** | Start an SSM session on an EC2 instance to access its attached role credentials through IMDS |
|
|
| **SSM Send Command for EC2 Role Credentials (SSM-002)** | Execute commands on an EC2 instance via SSM Run Command to access its attached role credentials through IMDS |
|
|
| **Role Assumption for Privilege Escalation (STS-001)** | Assume IAM roles with elevated permissions by exploiting bidirectional trust between the starting principal and the target role |
|
|
|
|
These tools enable workflows such as:
|
|
- Asking an AI assistant to identify privilege escalation paths in a specific AWS account
|
|
- Automating attack path analysis across multiple scans
|
|
- Combining attack path data with findings and compliance information for comprehensive security reports
|
|
|
|
For the complete list of MCP tools, see the [Tools Reference](/getting-started/basic-usage/prowler-mcp-tools#attack-paths-analysis).
|