Phase 09 WARMUP — Cloud & Container Red Team

Operation Cedar Lattice, Phase 09. Before the labs, build the mental model that makes the graph-solver meaningful. This guide takes you from first principles — what is an IAM principal, why does the evaluation order matter, what does a Linux namespace actually isolate — to the practitioner level where you can look at a permission graph and immediately see the lowest-detection path, or look at a container config and immediately see the escape vector and the Falco rule that catches it.

Safety boundary (unchanged). Every offensive concept ends in its detection. No working exploits, no shellcode, no deployable bypasses appear in this document.


Table of Contents

  1. Chapter 1: Cloud IAM Mental Model
  2. Chapter 2: AWS IAM Privilege Escalation Paths
  3. Chapter 3: IMDS v1 vs v2
  4. Chapter 4: Azure RBAC
  5. Chapter 5: Container Security Model
  6. Chapter 6: Container Escape Paths
  7. Chapter 7: CloudTrail and Activity Log Telemetry
  8. Chapter 8: Misconceptions
  9. Lab Walkthrough
  10. Success Criteria
  11. Common Mistakes
  12. Interview Q&A
  13. References

Chapter 1: Cloud IAM Mental Model

What IAM is, from zero

Identity and Access Management (IAM) is the authorization layer between a principal (who is making a request) and a resource (what they are trying to act on). Every API call in AWS — whether it comes from a human logging into the console, a Lambda function running in us-east-1, or a CI/CD runner calling s3:PutObject — is evaluated by IAM before the service processes it.

A principal is anything that can make an authenticated request: an IAM user, an IAM role (assumed by a service or a human), an AWS service acting on your behalf, or a federated identity from an external IdP. Principals are identified by their ARN (Amazon Resource Name).

ARN format:

arn:partition:service:region:account-id:resource-type/resource-id

Examples:

arn:aws:iam::114700000000:user/alice
arn:aws:iam::114700000000:role/meridian-cicd-runner
arn:aws:iam::114700000000:policy/AdminPolicy
arn:aws:s3:::meridian-freight-manifests

The region field is empty for IAM resources because IAM is global. The account ID is the 12-digit AWS account number.

Policy types

AWS has six policy types, evaluated in a strict order:

#TypeWho attaches itScope
1Service Control Policy (SCP)AWS Organizations (management account)All principals in OU or account
2Resource-based policyResource ownerWho can access the resource
3Identity-based policyPrincipal (inline or managed)What the principal can do
4Permission boundaryAdministrator on the principalMaximum allowed, not granted
5Session policyCaller at sts:AssumeRole timeLimit a session's effective permissions
6VPC endpoint policyVPC endpointRequests through that endpoint only

The golden rule: deny always wins

The evaluation logic has one overriding rule: an explicit Deny in any applicable policy always wins, regardless of how many Allows exist elsewhere. The order of evaluation is:

  1. If any policy has an explicit Deny matching the action+resource → DENY (final).
  2. If no Deny, is there an explicit Allow in any applicable policy? → potential ALLOW.
  3. If no Allow → implicit DENY (default).

But the evaluation happens in layers:

Request arrives
       │
       ▼
┌─────────────────────────────────────────────────────┐
│  Layer 1: SCP evaluation                            │
│  Does the organization SCP allow this action?       │
│  NO → DENY (end)   YES → continue                  │
└─────────────────────────────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────────────────────┐
│  Layer 2: Resource-based policy                     │
│  Does the resource have a policy? Does it Allow?    │
│  Explicit Deny → DENY (end)                        │
│  Explicit Allow (and no SCP block) → ALLOW (end)  │
│  Not present or no Allow → continue                │
└─────────────────────────────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────────────────────┐
│  Layer 3: Identity-based policy                     │
│  Does the principal's policy Allow this action?     │
│  Explicit Deny → DENY (end)                        │
│  No Allow → implicit DENY (end)                    │
│  Allow → continue to boundaries                    │
└─────────────────────────────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────────────────────┐
│  Layer 4: Permission boundary (if set)              │
│  Does the boundary also Allow this action?          │
│  NO → DENY   YES → continue                        │
└─────────────────────────────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────────────────────┐
│  Layer 5: Session policy (if AssumeRole was used)   │
│  Does the session policy also Allow?                │
│  NO → DENY   YES → ALLOW (final)                  │
└─────────────────────────────────────────────────────┘

Key insight for attackers and defenders alike: SCPs are the organization-wide ceiling. A developer cannot grant themselves more than the SCP allows, no matter how permissive their identity policy is. But most organizations do not use SCPs for fine-grained control — they set a coarse "Effect": "Deny", "Action": "organizations:LeaveOrganization" and call it done, which means the identity-based policy layer is where the real escalation happens.

Resource vs identity policies

Identity-based policy — attached to the principal (user, role, group). Answers: "what can this principal do?" Example: a policy attached to meridian-cicd-runner that allows s3:PutObject on arn:aws:s3:::meridian-artifacts/*.

Resource-based policy — attached to the resource itself. Answers: "who can access this resource?" S3 bucket policies, KMS key policies, SQS queue policies, Lambda function policies are all resource-based. They can grant cross-account access directly without requiring the other account to have an identity-based policy that mirrors it.

The cross-account exception: For cross-account access, BOTH the resource-based policy (which must allow the foreign principal) AND the identity-based policy in the foreign account (which must allow the action) must agree. For same-account access, a resource-based policy Allow alone is sufficient without a matching identity-based policy.

Condition keys

Policies can have Condition blocks that restrict when the Allow or Deny applies. Common condition keys used in defensive policies:

  • aws:RequestedRegion — restrict to specific regions
  • aws:SourceVpc and aws:SourceVpce — restrict to calls from a specific VPC
  • aws:MultiFactorAuthPresent — require MFA
  • aws:PrincipalTag/CostCenter — tag-based ABAC
  • iam:PassedToService — restrict what services a role can be passed to

From an attacker's perspective, conditions are bypass opportunities. A policy that says "Condition": {"StringEquals": {"aws:RequestedRegion": "us-east-1"}} only applies to us-east-1 requests — the same action in eu-west-1 may be unconstrained.


Chapter 2: AWS IAM Privilege Escalation Paths

Privilege escalation in AWS IAM does not require a vulnerability in AWS itself. It requires only that an identity has one or more permissions that, individually, sound harmless but in combination allow creating or modifying a policy to grant admin access. These paths were systematically catalogued by Rhino Security Labs (see References).

The key insight: any permission that lets you modify policy attachments or create new policy versions is a privilege-escalation primitive.

Path 1: iam:CreatePolicyVersion

What you need: iam:CreatePolicyVersion on a managed policy that is attached to any principal with broad permissions (or that you can attach to yourself).

What you do: Create a new version of an existing managed policy with "Action": "*" and "Resource": "*", and set it as the default version. The policy now grants admin to everything it is attached to.

CloudTrail event: CreatePolicyVersion in iam.amazonaws.com. The request parameters include the policy ARN and the new policy document. The response includes the new version ID.

Detection signal: CreatePolicyVersion events where the policy document contains "*" in Action or Resource are extremely high-signal. GuardDuty has a finding for this: Policy:IAMUser/RootCredentialUsage (adjacent). A custom CloudTrail Insights alert on CreatePolicyVersion by non-automation principals catches most cases.

Path 2: iam:AttachUserPolicy

What you need: iam:AttachUserPolicy (optionally scoped to Resource: "*").

What you do: Attach the AWS-managed AdministratorAccess policy (arn:aws:iam::aws:policy/AdministratorAccess) to yourself or to a user you control.

CloudTrail event: AttachUserPolicy in iam.amazonaws.com. Request parameters include userName (the target) and policyArn.

Detection signal: Any AttachUserPolicy event where policyArn contains AdministratorAccess or the attaching principal is not a known administrative automation role is a critical alert. This is one of the most obvious escalations — it appears in CloudTrail with both the actor and the target user in the same event.

Path 3: iam:PassRole + lambda:CreateFunction + lambda:InvokeFunction

What you need: iam:PassRole on a high-privileged role, lambda:CreateFunction, and lambda:InvokeFunction.

What you do: Create a Lambda function and pass it a privileged IAM role as its execution role. The Lambda function then runs as that role. Invoke the function with a payload that calls iam:AttachUserPolicy or iam:CreatePolicyVersion on behalf of the privileged role. The privilege escalation happens inside Lambda, attributed to the Lambda function's ARN.

CloudTrail events (sequence):

  1. PassRole — logged as an IAM event in the requestParameters of CreateFunction
  2. CreateFunction in lambda.amazonaws.com
  3. InvokeFunction in lambda.amazonaws.com
  4. Inside the Lambda: the actual IAM write (e.g., AttachUserPolicy) attributed to the Lambda function's role, not the original attacker ARN.

Detection signal: This is a multi-step chain. The key detection is correlating CreateFunction (where role in requestParameters is a high-privilege role ARN) with subsequent IAM writes attributed to that Lambda function's ARN. Lambda creation by non-standard principals should alert.

Path 4: iam:CreateLoginProfile

What you need: iam:CreateLoginProfile on IAM users.

What you do: Create a console-access login profile (username + password) for an IAM user that has no login profile configured but has high-privilege policies attached. You then log in as that user via the AWS console.

CloudTrail event: CreateLoginProfile in iam.amazonaws.com. Request parameters include userName. No password is logged (AWS sanitizes it).

Detection signal: CreateLoginProfile events for users that previously had no console access. GuardDuty's UnauthorizedAccess:IAMUser/ConsoleLoginSuccess.B (console login from unusual IP) is the downstream signal. The CreateLoginProfile call itself is the earlier, higher-confidence indicator.

Path 5: sts:AssumeRole chains

What you need: sts:AssumeRole on a role, and that role has permissions not available to your current principal. Chains of AssumeRole let you hop across accounts or assume roles with broader permissions.

What you do: Assume a role, use that role's permissions (including potentially another sts:AssumeRole), and repeat. In Meridian Freight's environment, meridian-cicd-runner can assume meridian-deploy-staging, which can assume meridian-ops-admin in the production account.

CloudTrail events: One AssumeRole event per hop in sts.amazonaws.com. Each event logs:

  • The roleArn being assumed
  • The principalArn doing the assuming
  • The sourceIdentity (if set)
  • The resulting session ARN

Detection signal: Unusual AssumeRole chains — especially cross-account ones — are one of the highest-value correlation opportunities. A SIEM rule that flags AssumeRole where the source principal is not in the expected list of automation ARNs catches most human-driven lateral movement. GuardDuty's UnauthorizedAccess:IAMUser/TorIPCaller and CredentialAccess:IAMUser/AnomalousBehavior findings are adjacent.

Summary table

PathPermission neededCloudTrail eventNoise level
CreatePolicyVersioniam:CreatePolicyVersionCreatePolicyVersion (IAM)Very high
AttachUserPolicyiam:AttachUserPolicyAttachUserPolicy (IAM)Very high
PassRole + Lambdaiam:PassRole + lambda:CreateFunction + lambda:InvokeFunctionCreateFunction, InvokeFunctionMedium
CreateLoginProfileiam:CreateLoginProfileCreateLoginProfile (IAM)High
AssumeRole chainsts:AssumeRoleAssumeRole (STS)Low (very common)

Chapter 3: IMDS v1 vs v2

What IMDS is

The Instance Metadata Service (IMDS) is an HTTP endpoint available from within every EC2 instance at the link-local address 169.254.169.254. It provides metadata about the instance: the instance ID, availability zone, security groups, user-data, and — critically — the temporary credentials for the IAM role attached to the instance (the instance profile).

The credentials endpoint:

http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name>

This returns a JSON object containing:

  • AccessKeyId
  • SecretAccessKey
  • Token (the session token for the temporary credentials)
  • Expiration (typically 6 hours from now)

These credentials are equivalent to calling sts:AssumeRole as the instance profile role. If the role has broad permissions, obtaining these credentials from inside the instance (or from any process that can reach the IMDS endpoint) gives the attacker those permissions.

IMDSv1: no authentication

IMDSv1 is a simple HTTP GET. Any process on the instance — or any process that can issue HTTP requests to 169.254.169.254 (e.g., via an SSRF vulnerability in a web application) — can retrieve the credentials. There is no token, no authentication, no request signing.

SSRF to IMDSv1 credential theft pattern:

  1. Attacker finds an SSRF vulnerability in a web application running on an EC2 instance.
  2. Attacker uses the SSRF to make the server request http://169.254.169.254/latest/meta-data/iam/security-credentials/.
  3. The response lists the role name.
  4. Attacker fetches http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name>.
  5. The response contains AccessKeyId, SecretAccessKey, Token.
  6. Attacker uses these from outside AWS to make API calls.

This was the exact path in the Capital One breach (2019). SSRF → IMDSv1 → role credentials → S3 data.

IMDSv2: token-based authentication

IMDSv2 requires a two-step process:

Step 1: PUT request to get a session token (TTL-limited):

PUT http://169.254.169.254/latest/api/token
X-aws-ec2-metadata-token-ttl-seconds: 21600

Response body: a token string (opaque, time-limited).

Step 2: GET request with the token in the header:

GET http://169.254.169.254/latest/meta-data/iam/security-credentials/
X-aws-ec2-metadata-token: <token>

The critical security property of IMDSv2 is the hop limit. The PUT request to get the token has a default TTL for the IP packet of hop_limit=1. This means the PUT request cannot be forwarded — it will be dropped by the first routing hop. A web application receiving an SSRF request and forwarding it to 169.254.169.254 will fail to get a token, because the request is coming from the application process, not from a fresh layer-3 hop. The SSRF chain is broken.

When hop limit is set to 2 or higher: The protection is weakened. Requests from containers inside the EC2 instance may need hop_limit=2 if they go through the Docker bridge network. Some EKS configurations set hop_limit=2 as a workaround. This restores the SSRF risk.

What credentials are available

The IAM security credentials response contains:

{
  "Code": "Success",
  "LastUpdated": "2024-01-15T10:00:00Z",
  "Type": "AWS-HMAC",
  "AccessKeyId": "ASIA...",
  "SecretAccessKey": "...",
  "Token": "...",
  "Expiration": "2024-01-15T16:00:00Z"
}

The Token field makes these temporary credentials (STS-style). They expire, typically 6 hours after issuance. The AccessKeyId starts with ASIA (not AKIA which is for long-term user keys).

Detection: IMDS abuse

CloudTrail signals:

  • Credentials obtained from IMDS appear in CloudTrail as userIdentity.type = "AssumedRole" with the arn containing the instance profile role. If the source IP is not the instance's IP (i.e., the credentials are being used from a different machine), this is a strong signal.
  • GetCallerIdentity is often the first call an attacker makes to confirm credentials work. A GetCallerIdentity from an unexpected source IP for an instance-profile role is high-confidence.

VPC flow logs:

  • Flows to 169.254.169.254:80 from unexpected processes or at unusual times.
  • In EKS environments, flows from the container network range (e.g., 10.0.0.0/8) to 169.254.169.254 indicate a container is reaching IMDS.

Process-level detection:

  • Any process in the container namespace making outbound connections to 169.254.169.254 should be anomalous if the container has no legitimate need for it.
  • Falco rule: fd.sip=169.254.169.254 and container.id != "" fires when a containerized process connects to IMDS.

Chapter 4: Azure RBAC

Scope hierarchy

Azure organizes resources in a four-level scope hierarchy:

Management Groups
  └─ Subscriptions
       └─ Resource Groups
            └─ Resources (VMs, storage accounts, etc.)

Role assignments are made at a scope level. A role assigned at a higher scope (e.g., subscription) is inherited by all child scopes (resource groups, resources). A role assigned at the resource group level only applies to resources in that group.

Meridian Freight Azure layout:

  • Management Group: meridian-freight-root
  • Subscriptions: meridian-prod (ERP + TMS), meridian-dev (developer workloads)
  • Resource Groups: meridian-freight-rg (compute), meridian-data-rg (storage), meridian-net-rg (networking)

Built-in roles

Azure has three fundamental built-in roles:

RoleActionsEffect
Owner*Full control including assigning roles to others
Contributor* (minus authorization)Create and manage resources, cannot grant role assignments
Reader*/readView all resources, cannot change anything

There are also over 70 service-specific built-in roles (e.g., Storage Blob Data Contributor, Key Vault Secrets User). The principle of least privilege demands these over the broad roles.

Custom roles

Custom roles are defined with an Actions list (what is allowed), NotActions (deny-list subtracted from Actions), DataActions (data plane access), and AssignableScopes (which scopes the role can be assigned at). Custom roles in Meridian Freight's environment include meridian-ops-read (read across all resource groups) and meridian-deploy (contributor on meridian-dev subscription only).

Dangerous permissions

Microsoft.Authorization/roleAssignments/write — This is the Azure equivalent of iam:AttachUserPolicy. Any principal with this permission can assign themselves (or anyone else) the Owner role at the scope where they have this permission. If held at the subscription scope, this is full account compromise.

Microsoft.Compute/virtualMachines/runCommand/action — Allows executing arbitrary shell commands inside a VM via the Azure portal or API. Even a Reader with this additional permission can execute code on any VM in scope, bypass network controls, and read files from the VM filesystem (including secrets, cached credentials, etc.).

Microsoft.KeyVault/vaults/secrets/read and Microsoft.KeyVault/vaults/secrets/getSecret/action — Access to Key Vault secrets. These may include storage account access keys, database passwords, or service principal credentials — any of which can enable broader access.

Managed identity

A Managed Identity is a service principal that is automatically managed by Azure and attached to a resource (VM, App Service, Function App, AKS pod). There are two types:

  • System-assigned: Tied to the resource lifecycle. Deleted when the resource is deleted.
  • User-assigned: Independent resource, can be attached to multiple resources.

From a security perspective, managed identities are credentials without secrets — you never see the underlying client secret, and Azure handles rotation. But they are still principals with RBAC assignments. If a VM has a system-assigned managed identity with Contributor on the subscription, any code running on that VM (including code in a compromised container) can call Azure Resource Manager APIs using the managed identity.

Abuse path: Code running on the VM calls the Azure Instance Metadata Service (IMDS) at http://169.254.169.254/metadata/identity/oauth2/token to get an access token for the managed identity. This token can be used to call Azure Resource Manager or any Azure service the managed identity has access to.

Detection: Azure Activity Log records Microsoft.Authorization/roleAssignments/write events with the caller's objectId and the new role assignment. Monitor for role assignments made outside of the infrastructure-as-code pipeline (e.g., Terraform or Bicep runs in CI/CD). Alert on any Owner or Contributor assignment at subscription or management-group scope.


Chapter 5: Container Security Model

What makes containers "isolated"

Containers are not virtual machines. They run as processes on the host kernel with isolation provided by Linux kernel features: namespaces (which parts of the system a process can see) and cgroups (how many resources a process can consume). Both are kernel primitives that have been available in Linux since 2008 (namespaces) and 2007 (cgroups). Docker and Kubernetes use these primitives to provide the illusion of isolation.

Understanding what each namespace isolates — and what it does not isolate — is prerequisite knowledge for understanding every container escape technique.

Linux namespaces

Linux has 8 namespace types. Docker uses 6 of them by default:

PID namespace — Each container gets its own PID 1 (typically the container's init process). Processes inside the container cannot see or signal host processes. PID 1 in the container is a different PID on the host. Escape implication: --pid=host removes this isolation — the container can see and ptrace all host processes.

Network namespace — The container gets its own network stack: its own interfaces, routing table, iptables rules, port bindings. eth0 inside the container is a virtual ethernet pair (veth) connected to the host's bridge (docker0). Escape implication: Host networking (--network=host) removes this — the container shares the host's network stack and can bind to host ports directly.

Mount namespace — The container sees a different filesystem root (a copy-on-write overlay). Host mounts are not visible unless explicitly bind-mounted. Escape implication: Bind-mounting host paths (e.g., /:/host) into the container allows the container to read/write the host filesystem. Mounting /var/run/docker.sock is a special case (see Chapter 6).

UTS namespace — The container has its own hostname and domain name. Cosmetic, but it means hostname inside the container does not return the host's hostname.

IPC namespace — The container has its own System V IPC and POSIX message queues. Prevents inter-process communication between host and container via shared memory.

User namespace — Allows mapping container UIDs to different host UIDs. If enabled, a container running as UID 0 (root) can be mapped to a non-privileged UID on the host. Docker does not enable user namespaces by default. This means root inside the container is root on the host from the perspective of any resource that is accessible (e.g., host-bind-mounted files).

Network namespace limitation: Namespace isolation does not prevent a process from making network connections. A container can still reach the host's network via the Docker bridge gateway and can reach external networks via NAT. Namespace isolation is about visibility and resource ownership, not network filtering — that requires firewall rules (iptables/nftables) or network policies (in Kubernetes).

cgroups

Control groups (cgroups) limit how much of a resource a process can use: CPU time, memory, block I/O, number of PIDs. A container configured with --memory=512m --cpus=0.5 has a cgroup that will OOM-kill the container if it tries to allocate more than 512 MB, and will throttle it to 50% of one CPU core.

cgroups do not provide security isolation — they provide resource isolation. A container with no cgroup limits can consume all host memory (OOM-killing other containers and the host itself), all CPU, and can fork-bomb the host. cgroup limits are a stability and fairness mechanism, not a security boundary.

Capabilities

Linux capabilities divide the monolithic root permission into ~40 distinct privileges. Docker drops most capabilities by default and only grants a small subset:

Capabilities Docker grants by default:

  • CAP_CHOWN — change file ownership
  • CAP_DAC_OVERRIDE — bypass file permission checks
  • CAP_FSETID — allow setting SUID/SGID bits
  • CAP_FOWNER — bypass permission checks for file operations
  • CAP_MKNOD — create special files
  • CAP_NET_RAW — use raw sockets (ping, ARP spoofing)
  • CAP_SETGID, CAP_SETUID — change GIDs/UIDs
  • CAP_SETPCAP — modify process capabilities
  • CAP_NET_BIND_SERVICE — bind to ports below 1024
  • CAP_SYS_CHROOT — use chroot
  • CAP_KILL — send signals to any process in namespace

Capabilities Docker drops by default (these are dangerous):

  • CAP_SYS_ADMIN — the "god capability." Mount filesystems, clone namespaces, load kernel modules, use ioctl for disk operations. If a container has this, most escape techniques work.
  • CAP_SYS_PTRACE — ptrace host processes (if --pid=host is set).
  • CAP_NET_ADMIN — modify network stack, intercept traffic.
  • CAP_SYS_MODULE — load/unload kernel modules. Loading a malicious kernel module = full host compromise.
  • CAP_SYS_RAWIO — access /dev/mem, /dev/kmem (raw memory access).

Default seccomp profile

Docker applies a seccomp profile by default that blocks ~44 system calls considered dangerous: keyctl, add_key, request_key (kernel keyring), ptrace, mbind, migrate_pages, move_pages, set_mempolicy (NUMA controls), perf_event_open, reboot, setns, unshare, syslog, acct, settimeofday, swapon, swapoff, mount, umount2, etc.

--privileged disables the seccomp profile entirely.

AppArmor and SELinux

Docker applies the docker-default AppArmor profile on systems where AppArmor is available. This profile denies access to host kernel files, /proc/sysrq-trigger, /proc/sys/kernel/core_pattern, and other host-level paths, even if the container's UID is root.

SELinux (used on RHEL/CentOS-based hosts) applies type enforcement labels. Containers get the container_t type, which confines them to container-specific types. Even a container running as root cannot write to host system files labeled system_u:object_r:lib_t:s0 without an SELinux policy exception.

--privileged disables AppArmor and SELinux confinement as well.

What --privileged disables

When you run a container with --privileged:

  1. All Linux capabilities are granted (including CAP_SYS_ADMIN, CAP_SYS_MODULE, CAP_SYS_RAWIO)
  2. The seccomp filter is removed (all system calls allowed)
  3. AppArmor and SELinux confinement is disabled
  4. All host devices (/dev/*) are accessible inside the container
  5. The container can mount any filesystem that the host kernel supports

The result is that the only remaining isolation is the mount and PID namespace (and user namespace if enabled). A privileged container with access to /dev/sda (the host disk) can mount the host filesystem and modify anything. A privileged container can also call unshare --mount to create a new mount namespace and mount the host filesystem inside it.


Chapter 6: Container Escape Paths

Escape 1: --privileged flag

What it requires: The container was launched with --privileged.

What the attacker does: Inside the privileged container:

  1. List block devices: fdisk -l or examine /dev/ for block devices (e.g., /dev/xvda1).
  2. Create a mount point directory inside the container.
  3. Mount the host root filesystem at the mount point.
  4. Read or modify any file on the host: /etc/shadow, /etc/cron.d, SSH authorized_keys, environment files containing secrets, etc.
  5. Write a cron job or systemd unit to execute code as root on the host at next reboot or cron cycle.

The escape does not require any vulnerability — it is a configuration choice that removes the security boundary.

Falco detection:

- rule: Launch Privileged Container
  desc: Detect the launch of a container with --privileged flag
  condition: >
    container.privileged = true and
    evt.type = container
  output: >
    Privileged container launched (user=%user.name container=%container.name
    image=%container.image.repository:%container.image.tag)
  priority: CRITICAL
  tags: [container, privilege_escalation, T1610]

Escape 2: --pid=host

What it requires: The container was launched with --pid=host, sharing the host PID namespace.

What the attacker does:

  1. Inside the container, ps aux shows all host processes, including PID 1 (systemd or init).
  2. With CAP_SYS_PTRACE (which requires either --privileged or explicit --cap-add), the attacker can ptrace host processes and inject code.
  3. Even without ptrace, seeing host process names reveals running services, their PIDs, and allows sending signals to host processes.
  4. /proc/<host-pid>/root/ from inside the container points to the host's filesystem root for that process, potentially exposing host filesystem paths.

Falco detection:

- rule: Container with Host PID Namespace
  desc: Detect containers running with host PID namespace
  condition: >
    container.host_pid = true and
    evt.type = container
  output: >
    Container launched with host PID namespace (container=%container.name
    image=%container.image.repository)
  priority: HIGH
  tags: [container, privilege_escalation]

Escape 3: Docker socket mounted

What it requires: /var/run/docker.sock bind-mounted into the container.

What the attacker does:

  1. The Docker socket is the Unix socket the Docker daemon listens on. Any process with read/write access to it can send Docker API requests — equivalent to running docker commands as root.
  2. Inside the container: docker -H unix:///var/run/docker.sock run --privileged --pid=host -v /:/host <any-image> chroot /host bash launches a new privileged container from inside the compromised container, with the host filesystem mounted.
  3. This effectively gives the attacker root on the host, even if the original container was running as a non-root user with no special capabilities (just the socket access).

This is also a common Kubernetes misconfiguration: DaemonSets for log collectors or monitoring agents are given Docker socket access so they can inspect running containers, but any compromise of that pod results in host compromise.

Falco detection:

- rule: Docker Socket Mounted in Container
  desc: Detect container with Docker socket mounted
  condition: >
    fd.name = /var/run/docker.sock and
    evt.type in (open, openat) and
    container.id != ""
  output: >
    Docker socket opened from container (user=%user.name container=%container.name
    image=%container.image.repository command=%proc.cmdline)
  priority: CRITICAL
  tags: [container, privilege_escalation, lateral_movement, T1610]

Escape 4: CAP_SYS_ADMIN

What it requires: Container has the cap_sys_admin capability (either from --privileged or --cap-add=SYS_ADMIN).

What the attacker does:

  • mount() system call is allowed: mount the host's cgroup v1 release_agent filesystem, write a path to a writable script, trigger the release_agent with an empty cgroup. This is the "cgroup escape" technique.
  • unshare --mount + mount /dev/<disk>: create a new mount namespace and mount the host disk.
  • Modify /proc/sys/kernel/core_pattern to redirect core dumps to an attacker-controlled program (arbitrary code execution on core dump).

Falco detection:

- rule: Container with CAP_SYS_ADMIN
  desc: Detect container launched with CAP_SYS_ADMIN capability
  condition: >
    container.caps contains cap_sys_admin and
    evt.type = container
  output: >
    Container launched with CAP_SYS_ADMIN (container=%container.name
    image=%container.image.repository)
  priority: HIGH
  tags: [container, privilege_escalation]

Escape 5: Writable /etc mount

What it requires: The host's /etc (or a path containing sensitive configuration) is bind-mounted into the container without the :ro (read-only) flag.

What the attacker does:

  • Overwrite /etc/cron.d/<name> with a cron job entry that runs as root on the host.
  • Overwrite /etc/sudoers or a file in /etc/sudoers.d/ to add the container's user to passwordless sudo.
  • Overwrite /etc/ld.so.preload to inject a shared library that runs as any user the next time any dynamically linked program is executed on the host.
  • Overwrite SSH configuration to allow password authentication or add keys to /etc/ssh/authorized_keys (a non-standard path that some configs use).

The attacker does not need any special capabilities for this — just filesystem write access.

Falco detection:

- rule: Write to Sensitive Host Path from Container
  desc: Detect writes to sensitive host paths mounted in container
  condition: >
    container.id != "" and
    evt.type in (write, writev, rename, link) and
    fd.name pmatch (/etc/cron.d, /etc/sudoers, /etc/ld.so.preload,
                    /etc/passwd, /etc/shadow)
  output: >
    Sensitive file write from container (container=%container.name
    file=%fd.name command=%proc.cmdline)
  priority: HIGH
  tags: [container, persistence, T1543]

Chapter 7: CloudTrail and Activity Log Telemetry

CloudTrail event structure

Every API call to an AWS service that is covered by CloudTrail produces a log record (event) in the CloudTrail log. Events are delivered to an S3 bucket (and optionally CloudWatch Logs) in JSON format.

Key fields in every CloudTrail event:

{
  "eventVersion": "1.08",
  "userIdentity": {
    "type": "AssumedRole",
    "principalId": "AROA...:session-name",
    "arn": "arn:aws:sts::114700000000:assumed-role/meridian-cicd-runner/session-name",
    "accountId": "114700000000",
    "sessionContext": {
      "sessionIssuer": {
        "type": "Role",
        "principalId": "AROA...",
        "arn": "arn:aws:iam::114700000000:role/meridian-cicd-runner"
      }
    }
  },
  "eventTime": "2024-01-15T14:23:41Z",
  "eventSource": "iam.amazonaws.com",
  "eventName": "AttachUserPolicy",
  "awsRegion": "us-east-1",
  "sourceIPAddress": "10.0.1.45",
  "requestParameters": {
    "userName": "alice",
    "policyArn": "arn:aws:iam::aws:policy/AdministratorAccess"
  },
  "responseElements": null,
  "readOnly": false
}

userIdentity is the most important field for attribution. type can be:

  • Root — the AWS root account
  • IAMUser — an IAM user with long-term credentials
  • AssumedRole — temporary credentials from STS AssumeRole (most services, EC2 instance profiles)
  • FederatedUser — federated identity (SAML, OIDC)
  • AWSService — an AWS service acting on behalf of the account

eventName is the specific API action. This is what you correlate in your SIEM. readOnly is true for read-only actions (List*, Describe*, Get*). Write actions have readOnly: false — these are higher priority alerts.

Key events for red team activity

eventNameeventSourceSignificance
AssumeRolests.amazonaws.comRole assumption; log the roleArn and principalArn
GetCallerIdentitysts.amazonaws.comFirst call by attacker confirming credentials; anomalous source IP = alert
ListBucketss3.amazonaws.comReconnaissance; triggered by stolen creds being explored
CreatePolicyVersioniam.amazonaws.comHigh-signal privesc attempt
AttachUserPolicyiam.amazonaws.comHigh-signal privesc; log policyArn
CreateLoginProfileiam.amazonaws.comPersistence; creating console access for a user
CreateFunctionlambda.amazonaws.comLateral movement via Lambda
GetSecretValuesecretsmanager.amazonaws.comCredential theft from Secrets Manager
RunInstancesec2.amazonaws.comInfrastructure abuse; cryptomining, C2 setup
UpdateLoginProfileiam.amazonaws.comPassword change for another user — backdoor

GuardDuty findings

AWS GuardDuty is a threat detection service that consumes CloudTrail, VPC flow logs, and DNS query logs to produce finding objects. Key findings relevant to cloud red team:

  • CredentialAccess:IAMUser/AnomalousBehavior — API calls that deviate from a learned baseline
  • UnauthorizedAccess:IAMUser/ConsoleLoginSuccess.B — console login from unusual geography
  • Recon:IAMUser/TorIPCaller — API calls from a Tor exit node
  • PrivilegeEscalation:IAMUser/AdministrativePermissions — attempts to grant broader permissions
  • UnauthorizedAccess:EC2/MetaDataDNSRebind — DNS rebinding to reach IMDS

CloudTrail Insights

CloudTrail Insights detects unusual write API call rates for an account. If an account normally makes 5 CreatePolicyVersion calls per day and suddenly makes 50 in an hour, Insights generates a finding. This provides anomaly-based detection on top of the rule-based GuardDuty findings.

SIEM correlation rules

Effective SIEM rules for cloud red team detection:

  1. GetCallerIdentity within 60 seconds of AssumeRole from a new source IP → credential theft verification
  2. AttachUserPolicy or CreatePolicyVersion by any principal that is not an IaC automation ARN → privesc attempt
  3. AssumeRole chain depth > 2 (role A assumes role B which assumes role C) from a human session → lateral movement
  4. CreateFunction where role ARN in request parameters has broad permissions → Lambda-based privesc setup
  5. Any IAM write action from an IP outside the corporate egress range → stolen credentials in use

Chapter 8: Misconceptions

Misconception 1: "Root inside a container is not root on the host"

This is partly true and largely dangerous. Without user namespace remapping (which Docker does not enable by default), UID 0 inside the container is UID 0 on the host for any resource that leaks across the namespace boundary. A bind-mounted host path owned by root is fully writable by a root container process. The correct statement is: root inside a container that has no additional isolation (no user namespace, no AppArmor, writable host mounts) is effectively root on the host for those mounted resources.

Misconception 2: "The IMDS endpoint is only accessible from inside the VM"

IMDSv1 is accessible from any process that can reach 169.254.169.254. This includes:

  • Application code running on the EC2 instance (obviously)
  • A web application with an SSRF vulnerability on the instance
  • A container running on the EC2 node if it can route to the link-local range
  • A Lambda function (which has its own IMDS)

The restriction is network-level, not authentication-level. IMDSv2 adds authentication (the token requirement) as a second line of defense.

Misconception 3: "IAM permission boundaries prevent privilege escalation"

Permission boundaries limit what an identity can do, but they must be explicitly set. An administrator who creates a new IAM user without setting a permission boundary leaves that user with only their identity-based policy as the ceiling. More importantly, if an attacker can create a new role (with iam:CreateRole) they choose not to set a boundary on that new role. The boundary only restricts the identity it is attached to — the attacker creates a new identity without one.

Misconception 4: "GuardDuty catches all attacks in real time"

GuardDuty has alerting latency (typically 5-15 minutes) and uses a 14-day baseline period for anomaly detection. A new attacker using credentials for the first time has no baseline — GuardDuty will not generate an anomaly finding until the behavior deviates from the learned pattern, which requires a pattern to have been learned. Rule-based findings (e.g., Tor exit node caller) are faster but require the attacker to use flagged infrastructure.

Misconception 5: "Container images are immutable, so containers are secure"

Image immutability means the base image layers cannot be changed after build — it says nothing about runtime security. A container running an immutable image can still be launched with --privileged, with host mounts, with the Docker socket, or with dangerous capabilities. The image layer and the container runtime configuration are orthogonal security dimensions.

Misconception 6: "Secrets Manager and Parameter Store are always safer than environment variables"

Secrets Manager and Parameter Store are safer for storage (no cleartext in Dockerfiles or source control). But at runtime, the secret must be fetched and made available to the application — often as an environment variable. A container compromise that allows reading /proc/1/environ (the environment of PID 1) will reveal secrets regardless of how they were stored. The improvement is in storage and auditability (Secrets Manager logs every GetSecretValue to CloudTrail), not in runtime isolation.

Misconception 7: "A read-only container filesystem prevents container escape"

Read-only filesystems (--read-only) prevent writes to the container's layered filesystem. They do not prevent:

  • Writing to explicitly mounted writable volumes (including host bind-mounts)
  • Network operations
  • System calls that don't require filesystem writes
  • Capability-based escapes (cap_sys_admin mount operations use syscalls, not writes)

Read-only filesystem is a defense-in-depth measure for persistence, not a container boundary.


Lab Walkthrough

Lab 01: Cloud IAM Attack Path Solver

Goal: Given a synthetic IAM permission graph, implement:

  1. can_reach(graph, start, target) — BFS to determine reachability
  2. attack_path(graph, start, target) — Dijkstra to find lowest-detection-cost path
  3. privilege_escalation_edges(graph) — filter edges by PRIVESC_ACTIONS
  4. detection_for_edge(edge) — look up CloudTrail event for an action

Understanding the graph structure:

graph = {
    "nodes": ["dev-user", "ec2-role", "admin-role", "s3-data"],
    "edges": [
        {"principal": "dev-user", "action": "sts:AssumeRole",
         "resource": "ec2-role", "condition": None},
        {"principal": "ec2-role", "action": "iam:AttachUserPolicy",
         "resource": "admin-role", "condition": None},
        {"principal": "admin-role", "action": "s3:GetObject",
         "resource": "s3-data", "condition": None},
    ]
}

Each edge represents a permission: principal has action on resource. Traversal means: if you can become a principal (by reaching it via a prior edge's resource), you can use that principal's edges.

BFS for can_reach:

  • Start: {start} is the initial reachable set
  • At each step: for every node in the reachable set, follow all edges where principal == node, add resource to reachable
  • Return True if target in reachable when no new nodes are being added

Dijkstra for attack_path:

  • State: current node
  • Cost: cumulative sum of VISIBILITY_COSTS[action] (lower = less detectable)
  • On reaching target: reconstruct path from predecessor map
  • Return list of edge dicts with detection key added (from detection_for_edge)

Step 3 — privilege_escalation_edges: Filter the edges list to only those where edge["action"] in PRIVESC_ACTIONS.

Step 4 — detection_for_edge: Format string as "CloudTrail: <event> | principal=<p> resource=<r>".

Lab 02: IMDS & Container Escape Analyzer

Goal: Given synthetic configuration facts, implement:

  1. imds_findings(facts) — detect IMDSv1 and weak hop-limit
  2. container_escape_findings(facts) — detect escape-enabling misconfigurations
  3. analyze(facts) — combine findings and determine overall risk level
  4. detection_for_finding(finding) — look up Falco rule or CloudTrail event

imds_findings logic:

  • If facts["imds_version"] == "v1" → add finding with id imds_v1_accessible, severity HIGH
  • If facts["imds_version"] == "v2" and facts.get("imds_hop_limit", 1) > 1 → add finding with id imds_v2_no_hop_limit, severity MEDIUM

container_escape_findings logic:

  • "--privileged" in facts["container_flags"] → CRITICAL finding privileged_container
  • "--pid=host" in facts["container_flags"] or "host_pid" in facts["container_flags"] → HIGH finding host_pid_namespace
  • Any volume string containing "/var/run/docker.sock" → CRITICAL finding docker_socket_mounted
  • "cap_sys_admin" in facts["capabilities"] → HIGH finding cap_sys_admin
  • Any volume string containing "/etc" and not ending in ":ro" → HIGH finding writable_etc_mount

analyze logic: Combine both finding lists, sort by severity order (CRITICAL=0, HIGH=1, MEDIUM=2, LOW=3), determine risk_level from highest severity present.


Success Criteria

You are ready to move to Phase 10 when you can:

  • Draw the AWS IAM policy evaluation flowchart from memory, labeling each layer and the deny-always-wins rule.
  • Name five IAM privilege-escalation paths and the specific CloudTrail event each emits, without looking at notes.
  • Explain the IMDSv2 hop-limit mechanism and why it prevents SSRF-to-IMDS chains from containers, in two or three sentences.
  • List the three Linux primitives that provide container isolation and explain what each one prevents (and what it does not prevent).
  • Write a Falco rule condition for detecting a privileged container launch.
  • Given any AWS CloudTrail event JSON, identify the principal, action, source IP, and target resource.
  • Both lab test suites pass with LAB_MODULE=solution pytest -q.

Common Mistakes

Mistake 1: Confusing IAM roles and IAM users Users have long-term credentials (AccessKeyId + SecretAccessKey). Roles have no credentials of their own — they are assumed via sts:AssumeRole to get short-term temporary credentials. Roles are the mechanism for granting permissions to services, Lambda functions, EC2 instances, and cross-account access.

Mistake 2: Thinking permission boundaries block everything Permission boundaries limit the maximum permissions an identity can have, but they must be set. They also do not prevent the creation of new identities (roles, users) that do not inherit the boundary.

Mistake 3: Assuming --no-new-privileges is equivalent to dropping capabilities --no-new-privileges (or the no_new_privs seccomp flag) prevents setuid/setgid binaries and Linux Security Module (AppArmor/SELinux) profile transitions from granting additional privileges. It does not drop capabilities — a container with CAP_SYS_ADMIN and --no-new-privileges still has CAP_SYS_ADMIN.

Mistake 4: Treating CloudTrail as real-time CloudTrail events are delivered to S3 within approximately 15 minutes of the API call. CloudWatch Logs integration is faster (~5 minutes) but still not instant. A detection pipeline built on raw CloudTrail S3 logs may have significant latency. GuardDuty and Security Hub process events faster but are not real-time either.

Mistake 5: Overlooking read-only API calls in investigations ListBuckets, DescribeInstances, GetCallerIdentity are all readOnly: true in CloudTrail. Many SIEM rules filter to readOnly: false for noise reduction, which means reconnaissance activity goes undetected. A complete detection strategy monitors both.

Mistake 6: Dijkstra edge case — unreachable nodes In the lab's attack_path implementation: when there is no path from start to target, the function must return an empty list [], not raise an exception. The Dijkstra loop exits when the priority queue is exhausted without reaching the target — handle this by returning [] after the loop.


Interview Q&A

Q1: What is the difference between IMDSv1 and IMDSv2 and why does it matter for security?

A: IMDSv1 is a simple unauthenticated HTTP GET to http://169.254.169.254/latest/meta-data/. Any process — or any SSRF vulnerability — that can issue an HTTP request to that address can retrieve the IAM role credentials attached to the instance. This was the vector in the Capital One breach: an SSRF vulnerability caused the server to fetch its own IMDS credentials.

IMDSv2 requires a two-step process: a PUT request (with a custom X-aws-ec2-metadata-token-ttl-seconds header) to obtain a session token, then a GET request with that token in the X-aws-ec2-metadata-token header. The critical protection is the IP TTL (hop limit) set to 1 on the PUT request. This means the PUT cannot be forwarded by an application — it is dropped by the first routing layer. An SSRF that causes the server to forward an HTTP request cannot get a token and therefore cannot get the credentials. The protection is network-layer enforcement, not software-layer validation.

Security maturity: IMDSv2 should be enforced at the organization level via an SCP condition ("Condition": {"StringEquals": {"ec2:MetadataHttpTokens": "required"}}) so that no instance can be launched with IMDSv1 enabled.

Q2: Explain the AWS IAM policy evaluation order.

A: AWS evaluates policies in a strictly ordered pipeline. First, Service Control Policies (SCPs) from AWS Organizations set the ceiling — any action not allowed by the SCP is denied regardless of identity policies. Next, resource-based policies are evaluated; an explicit Deny here ends evaluation immediately, while an Allow in the same-account case can be sufficient without a matching identity policy. Then identity-based policies attached to the requesting principal (inline + managed) are checked. If a permission boundary is set on the principal, the action must be allowed by both the identity policy and the boundary — the boundary cannot grant permissions, only restrict them. Finally, if the request uses temporary credentials from sts:AssumeRole, the session policy passed at assume-role time is also intersected.

The single most important rule: an explicit Deny in any policy at any layer terminates evaluation with a denial, overriding all Allows. An implicit Deny (no matching Allow anywhere) also results in denial. Only an explicit Allow that survives all layers results in access.

Q3: What permissions does an attacker need to escalate privileges in AWS IAM?

A: Several single permissions (or small combinations) are sufficient:

  • iam:CreatePolicyVersion alone: create a new version of an existing managed policy with Action: "*", Resource: "*" and set it as default. Every principal with that policy now has admin.
  • iam:AttachUserPolicy alone: attach AdministratorAccess to themselves or any target user.
  • iam:PassRole + lambda:CreateFunction + lambda:InvokeFunction: create a Lambda with a privileged execution role, invoke it to perform IAM writes.
  • iam:CreateRole + iam:AttachRolePolicy: create a new role with no permission boundary, attach admin policy to it, then assume it.
  • iam:CreateLoginProfile: create console credentials for a user with existing admin policy.

The common thread is that any permission that allows modifying policy attachments or creating new policy versions is effectively a privilege escalation if the target policy or role has admin-level permissions — or if the attacker can choose which policy to create.

Q4: How does --privileged in Docker enable container escape?

A: --privileged grants the container all Linux capabilities (including CAP_SYS_ADMIN), removes the seccomp filter, and disables AppArmor/SELinux confinement. It also makes all host devices (block devices, character devices in /dev/) accessible inside the container.

With all host devices accessible and CAP_SYS_ADMIN enabling the mount() system call, the container can mount the host's root filesystem (e.g., /dev/xvda1) at a path inside the container and then read or write any file on the host. This is not a vulnerability in Docker — it is the documented behavior of --privileged. The "escape" is simply mounting the host disk or modifying a host file (cron job, sudoers, SSH keys) to gain persistence or execute code as root on the host outside the container context.

Q5: What is a managed identity and how can it be abused?

A: A managed identity is an Azure Active Directory service principal with credentials automatically managed by Azure — the administrator never sees or handles the client secret. They come in two flavors: system-assigned (tied to a specific resource's lifecycle) and user-assigned (independent resource, attachable to multiple VMs or services).

Abuse path: any code running on a VM with a system-assigned managed identity (or with a user-assigned managed identity attached) can call the Azure IMDS endpoint at http://169.254.169.254/metadata/identity/oauth2/token?resource=https://management.azure.com/ to obtain an OAuth2 access token for that identity. This token can be used to call Azure Resource Manager APIs. If the managed identity has Contributor or Owner RBAC assignments, the attacker can create new resources, exfiltrate data, read Key Vault secrets, or assign roles to themselves to gain persistent access.

Detection: Azure Activity Log records token issuance and Resource Manager API calls. Unusual source IPs for managed identity API calls (credentials used from outside the VM's IP) are the key indicator.

Q6: How would you detect IMDS abuse in a cloud environment?

A: Detection requires correlation across multiple data sources:

First, CloudTrail — credentials obtained from IMDS produce AssumedRole-type userIdentity entries. If those credentials are used from an IP address that is not the EC2 instance's IP (visible in sourceIPAddress), the credentials have been exfiltrated. GetCallerIdentity is typically the first call made by an attacker to verify credentials — correlate GetCallerIdentity events from unexpected source IPs with instance profile role ARNs.

Second, VPC flow logs — flows to 169.254.169.254:80 from container subnet CIDR ranges (not the host IP) indicate container-to-IMDS access, which is anomalous unless the container has a legitimate reason.

Third, process-level telemetry (Falco, eBPF-based agents) — detect network connections from container PIDs to 169.254.169.254. The Falco condition fd.sip = 169.254.169.254 and container.id != "" fires on any containerized process connecting to IMDS.

Fourth, GuardDutyUnauthorizedAccess:EC2/MetaDataDNSRebind catches DNS-based IMDS bypass attempts.

Q7: What is the difference between an IAM role and an IAM user?

A: An IAM user is a persistent identity with long-term credentials: an Access Key ID and Secret Access Key that are valid indefinitely (until rotated or deleted). IAM users are intended for human access or legacy automation that cannot use roles.

An IAM role is an identity without its own credentials. It is assumed via sts:AssumeRole, which returns temporary credentials (AccessKeyId starting with ASIA, SecretAccessKey, and a Session Token) that expire after a configured duration (15 minutes to 12 hours). Roles are the intended mechanism for granting permissions to:

  • AWS services (EC2 instance profiles, Lambda execution roles)
  • Applications running on EC2/EKS/ECS
  • Cross-account access
  • Federation (SAML, OIDC)

Security implications: long-term user credentials that are compromised remain valid until explicitly revoked. Temporary role credentials expire automatically. GuardDuty and CloudTrail can detect AssumeRole by anomalous principals, making role assumption more auditable than static key usage.

Q8: How does Falco detect container escape attempts?

A: Falco is a runtime security tool for Linux that uses eBPF (or kernel module) to capture system calls and kernel events. It evaluates these events against a set of rules written in YAML with a condition language that can reference fields like container.id, container.privileged, fd.name, proc.cmdline, and evt.type.

For container escape detection, Falco uses several mechanisms:

Container launch events (evt.type = container): Falco captures the container metadata at launch time, including whether container.privileged = true, what capabilities are set, and what volumes are mounted. Rules fire immediately at container start.

System call monitoring: For escape techniques that happen at runtime (e.g., a process in the container calling mount()), Falco rules can match on evt.type = mount and container.id != "" to detect mount operations from inside containers. Similarly, openat events for /var/run/docker.sock from a container context indicate Docker socket abuse.

Process spawning: proc.name = nsenter and container.id != "" detects namespace entry attempts from inside a container. proc.cmdline contains chroot and container.id != "" catches chroot escape attempts.

The key limitation: Falco detects behaviors, not intentions. A legitimate monitoring tool that mounts the Docker socket will also fire these rules. Effective deployment requires tuning (allowlisting known-good containers) and alert correlation with IAM and CloudTrail data.


References

  • AWS IAM Policy Evaluation Logic: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic.html
  • Rhino Security Labs IAM Privilege Escalation Research: https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/
  • AWS IMDS Documentation (IMDSv2): https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html
  • Falco Rules Documentation: https://falco.org/docs/rules/
  • Falco Default Rules: https://github.com/falcosecurity/rules/blob/main/rules/falco_rules.yaml
  • MITRE ATT&CK T1078.004 — Valid Accounts: Cloud Accounts: https://attack.mitre.org/techniques/T1078/004/
  • MITRE ATT&CK T1552.005 — Unsecured Credentials: Cloud Instance Metadata API: https://attack.mitre.org/techniques/T1552/005/
  • MITRE ATT&CK T1610 — Deploy Container: https://attack.mitre.org/techniques/T1610/
  • Azure RBAC Documentation: https://learn.microsoft.com/en-us/azure/role-based-access-control/overview
  • Azure Managed Identities: https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview
  • AWS GuardDuty Finding Types: https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_finding-types-active.html
  • CloudTrail Event Reference: https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference.html
  • Pacu — AWS Exploitation Framework (defensive reference): https://github.com/RhinoSecurityLabs/pacu
  • Peirates — Kubernetes Penetration Testing Tool (defensive reference): https://github.com/inguardians/peirates