· PathShield Team · Tutorials  · 8 min read

What Is an Attack Path? Why It Matters for Your AWS Setup

Understand attack paths in AWS and why they're the key to real cloud security. Learn how attackers chain misconfigurations to breach your systems.

Understand attack paths in AWS and why they're the key to real cloud security. Learn how attackers chain misconfigurations to breach your systems.

What Is an Attack Path? Why It Matters for Your AWS Setup

You’ve secured your S3 buckets. Your databases are private. IAM policies look good. Yet attackers can still reach your crown jewels through a chain of seemingly innocent misconfigurations. This is the reality of attack paths—and why traditional security scanning isn’t enough.

Attack Paths: The Hacker’s Roadmap

An attack path is a sequence of steps an attacker can take to move from an initial foothold to your sensitive data. Think of it as connecting the dots between misconfigurations that individually seem harmless but together create a highway to your data.

A Real Attack Path Example

Here’s an actual attack path found in a startup’s AWS environment:

1. Public S3 bucket contains old Lambda deployment package

2. Lambda package has hardcoded IAM credentials

3. IAM credentials have read access to "dev" resources

4. Dev EC2 instance has metadata service v1 (vulnerable)

5. EC2 instance role can assume production database role

6. Production database contains all customer data

Individual security scans said: “Everything looks fine” Attack path analysis revealed: “6 steps to complete compromise”

Why Attack Paths Are Your Biggest Blind Spot

Traditional Scanning Misses the Connections

# What traditional scanners see
scan_results = {
    "s3_bucket_public": "LOW - Contains only old code",
    "iam_credentials": "MEDIUM - In development file",
    "ec2_metadata": "LOW - Internal service",
    "role_assumption": "OK - Part of deployment process",
    "database_access": "OK - Production role has access"
}

# What attack path analysis sees
attack_path = {
    "risk": "CRITICAL",
    "steps": 5,
    "time_to_exploit": "< 30 minutes",
    "data_at_risk": "100% of customer records"
}

Real Breaches Follow Attack Paths

The Capital One breach? An attack path:

  1. SSRF vulnerability in web application
  2. Access EC2 metadata service
  3. Retrieve IAM role credentials
  4. Access S3 buckets
  5. Exfiltrate 100 million records

Each step was a “minor” issue. Together, they cost $80 million.

Anatomy of AWS Attack Paths

Common Attack Path Patterns

1. The Credential Chain

GitHub repo → AWS keys → Dev account → Cross-account role → Production

2. The Public Resource Path

Public S3 → Lambda function → Secrets Manager → RDS database

3. The Over-Privileged Service Path

Compromised container → ECS task role → Administrative permissions → Account takeover

4. The Supply Chain Path

Third-party integration → API Gateway → Lambda → DynamoDB

How Attackers Find These Paths

# Attacker's reconnaissance process
def find_attack_paths():
    # Step 1: Enumerate public resources
    public_assets = find_public_s3_buckets() + find_open_ports()
    
    # Step 2: Look for credentials
    for asset in public_assets:
        creds = search_for_credentials(asset)
        if creds:
            accessible_resources = test_credential_access(creds)
    
    # Step 3: Map privilege escalation
    for resource in accessible_resources:
        roles = find_assumable_roles(resource)
        for role in roles:
            map_role_permissions(role)
    
    # Step 4: Find shortest path to data
    paths = calculate_paths_to_sensitive_data()
    return sorted(paths, key=lambda x: x.steps)

Real Attack Paths We’ve Found in Startups

Case 1: The Logging Nightmare

CloudWatch Logs (public read) 
→ Application logs with debug info
→ Database connection strings
→ Direct database access
→ All customer data compromised

Fix required: 1 IAM policy change Time to exploit: 15 minutes Data exposed: Everything

Case 2: The CI/CD Pipeline Path

Public GitHub repo
→ .github/workflows/deploy.yml
→ References secret name
→ Enumerate GitHub Actions logs
→ Find AWS role ARN
→ Attempt role assumption
→ Success: Deploy role has admin access

Fix required: Proper OIDC configuration Time to exploit: 2 hours Impact: Complete infrastructure control

Case 3: The Backup Backdoor

Old EC2 snapshot (public)
→ Mount and examine filesystem
→ Find .aws/credentials file
→ Credentials still valid (never rotated)
→ Access current production S3 buckets
→ Download all backups

Fix required: Snapshot encryption + key rotation Time to exploit: 45 minutes Data exposed: 2 years of backups

Visualizing Attack Paths

Simple Text Representation

Internet → ALB → Web App → API Gateway → Lambda → DynamoDB

         EC2 Metadata

         Task Role

     Secrets Manager

         RDS Admin

Graph Representation

# Attack path as a graph
attack_graph = {
    "nodes": [
        {"id": "internet", "type": "external"},
        {"id": "s3-public", "type": "storage", "risk": "high"},
        {"id": "lambda-old", "type": "compute", "risk": "medium"},
        {"id": "iam-creds", "type": "credential", "risk": "high"},
        {"id": "rds-prod", "type": "database", "risk": "critical"}
    ],
    "edges": [
        {"from": "internet", "to": "s3-public", "method": "public access"},
        {"from": "s3-public", "to": "lambda-old", "method": "download code"},
        {"from": "lambda-old", "to": "iam-creds", "method": "extract from env"},
        {"from": "iam-creds", "to": "rds-prod", "method": "assume role"}
    ]
}

Finding Attack Paths in Your AWS

Manual Attack Path Discovery

# Step 1: Find all public resources
aws s3api list-buckets --query 'Buckets[*].Name' | \
  xargs -I {} aws s3api get-bucket-acl --bucket {} | \
  grep -B 2 "AllUsers"

# Step 2: Check for credential exposure
find . -type f -name "*.env" -o -name "*.yml" -o -name "*.yaml" | \
  xargs grep -l "AKIA\|AWS_ACCESS_KEY_ID"

# Step 3: Map IAM permissions
aws iam get-account-authorization-details > iam-full-report.json

# Step 4: Analyze role trust policies
aws iam list-roles --query 'Roles[*].[RoleName,AssumeRolePolicyDocument]' | \
  grep -A 10 -B 10 '"Service":\|"AWS":'

Automated Attack Path Detection

# attack_path_finder.py
import boto3
import networkx as nx
from collections import defaultdict

class AttackPathFinder:
    def __init__(self):
        self.graph = nx.DiGraph()
        self.sensitive_resources = []
        
    def build_resource_graph(self):
        """Build graph of all AWS resources and their relationships"""
        # Add public entry points
        self.add_public_resources()
        
        # Add IAM relationships
        self.add_iam_relationships()
        
        # Add network paths
        self.add_network_paths()
        
        # Add resource policies
        self.add_resource_policies()
        
    def add_public_resources(self):
        """Find all publicly accessible resources"""
        s3 = boto3.client('s3')
        
        # Check S3 buckets
        for bucket in s3.list_buckets()['Buckets']:
            bucket_name = bucket['Name']
            try:
                acl = s3.get_bucket_acl(Bucket=bucket_name)
                for grant in acl['Grants']:
                    if 'URI' in grant['Grantee'] and 'AllUsers' in grant['Grantee']['URI']:
                        self.graph.add_edge('Internet', f's3:{bucket_name}', 
                                          access_type='public_read')
            except:
                pass
    
    def add_iam_relationships(self):
        """Map IAM roles and their trust relationships"""
        iam = boto3.client('iam')
        
        # Get all roles
        for role in iam.list_roles()['Roles']:
            role_name = role['RoleName']
            trust_policy = role['AssumeRolePolicyDocument']
            
            # Parse trust relationships
            for statement in trust_policy['Statement']:
                if statement['Effect'] == 'Allow':
                    principals = statement['Principal']
                    if isinstance(principals.get('Service'), list):
                        for service in principals['Service']:
                            self.graph.add_edge(service, f'role:{role_name}', 
                                              access_type='assume_role')
    
    def find_attack_paths(self, target_resource):
        """Find all paths from public resources to target"""
        paths = []
        public_nodes = [n for n in self.graph.nodes() 
                       if n == 'Internet' or self.graph.in_degree(n) == 0]
        
        for public_node in public_nodes:
            try:
                all_paths = nx.all_simple_paths(self.graph, 
                                               public_node, 
                                               target_resource,
                                               cutoff=10)
                paths.extend(list(all_paths))
            except nx.NetworkXNoPath:
                continue
                
        return paths
    
    def calculate_risk_score(self, path):
        """Calculate risk score for an attack path"""
        base_score = 10 - len(path)  # Shorter paths are higher risk
        
        # Adjust based on path elements
        for i in range(len(path) - 1):
            edge_data = self.graph.get_edge_data(path[i], path[i+1])
            if edge_data.get('access_type') == 'public_read':
                base_score += 3
            elif edge_data.get('access_type') == 'assume_role':
                base_score += 2
                
        return max(0, min(10, base_score))

Preventing Attack Paths

1. Defense in Depth

# Never rely on single security control
security_layers:
  - network_isolation: "VPC + Security Groups"
  - identity_controls: "IAM + MFA"
  - encryption: "At rest + In transit"
  - monitoring: "CloudTrail + GuardDuty"
  - response: "Automated remediation"

2. Principle of Least Privilege

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["s3:GetObject"],
    "Resource": "arn:aws:s3:::my-bucket/public/*",
    "Condition": {
      "StringEquals": {
        "s3:ExistingObjectTag/public": "true"
      }
    }
  }]
}

3. Regular Attack Path Analysis

# Weekly attack path audit
def weekly_security_audit():
    finder = AttackPathFinder()
    finder.build_resource_graph()
    
    critical_resources = [
        'rds:customer-database',
        's3:customer-documents',
        'secrets:production-keys'
    ]
    
    for resource in critical_resources:
        paths = finder.find_attack_paths(resource)
        if paths:
            alert_security_team(f"Found {len(paths)} attack paths to {resource}")
            for path in paths[:5]:  # Top 5 riskiest
                risk = finder.calculate_risk_score(path)
                print(f"Risk {risk}/10: {' → '.join(path)}")

4. Break Common Attack Chains

Credential Chains

  • Rotate keys automatically
  • Use temporary credentials
  • Never commit credentials

Network Paths

  • Use private subnets
  • Implement micro-segmentation
  • Deploy WAF rules

Permission Chains

  • Avoid role chaining
  • Use permission boundaries
  • Implement SCPs

Attack Path Remediation Playbook

Immediate Actions (Do Today)

# 1. Find and fix public resources
aws s3api list-buckets | jq -r '.Buckets[].Name' | \
  parallel -j 10 aws s3api put-public-access-block \
    --bucket {} \
    --public-access-block-configuration \
    "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

# 2. Enable GuardDuty for runtime detection
aws guardduty create-detector --enable

# 3. Review cross-account roles
aws iam list-roles \
  --query "Roles[?contains(AssumeRolePolicyDocument.Statement[].Principal.AWS, ':root')].RoleName" \
  --output table

Short-term Fixes (This Week)

  1. Implement IMDSv2 everywhere
aws ec2 modify-instance-metadata-options \
  --instance-id i-1234567890abcdef0 \
  --http-tokens required \
  --http-endpoint enabled
  1. Add alerting for suspicious paths
# CloudWatch pattern for role chaining
{
  ($.eventName = AssumeRole) && 
  ($.requestParameters.roleSessionName = *-AssumedRole)
}

Long-term Strategy (This Quarter)

  1. Deploy continuous attack path monitoring
  2. Implement automated remediation
  3. Regular purple team exercises
  4. Security architecture reviews

The Future of Attack Path Security

AI-Powered Path Discovery

Machine learning models that predict novel attack paths based on:

  • Historical breach patterns
  • Your specific architecture
  • Emerging threat intelligence

Automated Remediation

# Future: Self-healing infrastructure
def auto_remediate_attack_path(path):
    risk_score = calculate_risk(path)
    if risk_score > 8:
        for i in range(len(path) - 1):
            edge = (path[i], path[i+1])
            safe_remediations = get_safe_remediations(edge)
            apply_remediation(safe_remediations[0])
            verify_business_continuity()

Conclusion

Attack paths are how real breaches happen. While individual misconfigurations might seem minor, attackers excel at chaining them together. The good news? Once you can see these paths, they’re usually simple to break.

Start by understanding that security isn’t about perfection—it’s about making exploitation paths longer and more complex than attackers are willing to traverse. Focus on breaking the most common chains, and you’ll prevent 95% of real-world attacks.

Your next steps:

  1. Map your critical resources (customer data, credentials, admin access)
  2. Identify paths from the internet to these resources
  3. Break the easiest/shortest paths first
  4. Implement continuous monitoring for new paths

Remember: Attackers think in graphs, not lists. Your defense should too.

Want to visualize attack paths in your AWS environment? Tools like PathShield automatically map these hidden connections and show you exactly where to break the chains before attackers find them.

Back to Blog

Related Posts

View All Posts »