☁ Engagement — Operation: OPEN BUCKET
You've just been hired as the first dedicated Cloud Security Engineer at Meridian Retail Group, an e-commerce company that moved fast to AWS three years ago and never looked back to check what it left open. Their first SOC 2 audit is in 90 days. Your job: audit identity, storage, and logging across their account, automate what you find, and get them ready to expand into Azure and GCP — all using free-tier sandboxes and intentionally vulnerable practice environments before you ever touch their real infrastructure.
Cloud security has two failure modes new learners hit: testing infrastructure you don't own, and accidentally running up a real bill on infrastructure you do. Every lab in this workbook is built to avoid both.
This is general training guidance, not legal or financial advice. Cloud provider free-tier terms change — always check current limits before deploying a lab.
| Layer | Provider's Job | Yours |
|---|---|---|
| Physical security, hypervisor | Always | — |
| OS patching (EC2/IaaS) | — | You patch the instance OS |
| IAM configuration | — | Entirely yours, in every model |
| Data encryption settings | — | You choose to enable it |
| Network ACLs / Security Groups | — | You configure them |
| Managed DB engine patching (RDS) | Provider patches the engine | You still configure access to it |
Meridian Retail Group moved to AWS three years ago. The provider has held up its half perfectly — there's no datacenter breach in this story. Every single thing you'll find in this workbook lives in the "yours" column.
Use a personal email, never a work one.
Then never use root again for daily work.
AWS Budgets console, alert at $1 and $10 — before you do anything else.
With AdministratorAccess for lab purposes, and generate access keys for it.
Using that IAM user's keys, never root's.
You'll use it from Module 4 onward.
# verify your CLI is talking to the right account aws sts get-caller-identity # install Prowler pip install prowler
| Check | Expected Result |
|---|---|
| get-caller-identity | Returns YOUR IAM user ARN, never "root" |
| Billing alarm | Active in AWS Budgets, even before your first lab |
| MFA on root | Enabled, root access keys deleted if any existed |
| Target | Provider | Used In | Needs Your Own Account? |
|---|---|---|---|
| flAWS.cloud | AWS | Module 1 | No — hosted, temp creds per level |
| flAWS2.cloud | AWS | Module 2 | Yes, for the final container-escape level |
| CloudGoat | AWS | Module 1 | Yes — deploys into your free-tier account |
| AzureGoat | Azure | Module 5 | Yes — Azure free account |
| GCPGoat | GCP | Module 5 | Yes — GCP free trial |
Find one attached to any role in the IAM console.
What does each Action/Resource pair actually permit?
"*" in either the Action or Resource field.
Simulate a specific action and see whether it's actually allowed.
Any policy whose name undersells what it actually permits.
// danger sign #1: wildcard action AND wildcard resource together { "Effect": "Allow", "Action": "*", "Resource": "*" } // danger sign #2: LOOKS like read access — iam:PassRole + compute is a privesc primitive { "Effect": "Allow", "Action": ["iam:PassRole", "lambda:CreateFunction", "lambda:InvokeFunction"], "Resource": "*" }
| Pattern | Why It's Dangerous |
|---|---|
| "Action": "*" | Allows every action on whatever resources are matched |
| "Resource": "*" | Allows the action against every resource the account has |
| iam:PassRole + a compute service | Classic privilege-escalation primitive — hand a more powerful role to a service you control |
| No Condition key | No restriction by IP, MFA status, or time — the policy means exactly what it says |
At flaws.cloud — it's written like a real engagement note, on purpose.
Find a publicly listable S3 bucket and read its contents directly.
Same misconfiguration class, slightly less obvious entry point.
Find credentials accidentally exposed inside the bucket, then use them with the AWS CLI.
One sentence per level — that sentence is the skeleton of a real finding.
# levels 1/2 pattern: list a bucket's contents directly via its REST endpoint aws s3 ls s3://[bucket-name] --no-sign-request # level 3 pattern: once you have leaked keys, check what they can see aws sts get-caller-identity --profile flaws aws s3 ls --profile flaws
| Level | Root Misconfiguration |
|---|---|
| 1 | S3 bucket allows public LIST, revealing its contents to anyone |
| 2 | Same bucket-policy mistake, with a less obvious entry point |
| 3 | Credentials left inside a publicly-readable object, reused to pivot further |
And install its Python requirements.
Never root — and configure an AWS CLI profile for it.
CloudGoat locks every scenario's vulnerable resources to your IP only.
E.g. iam_privesc_by_rollback or iam_privesc_by_attachment.
Starting from the credentials CloudGoat gives you, find the escalation path.
Verify admin-equivalent access, then immediately tear the scenario down.
git clone https://github.com/RhinoSecurityLabs/cloudgoat.git cd cloudgoat pip3 install -r ./core/python/requirements.txt # deploy a privilege-escalation scenario into YOUR account ./cloudgoat.py create iam_privesc_by_rollback --profile cloudgoat # when you're done — every time, no exceptions ./cloudgoat.py destroy iam_privesc_by_rollback --profile cloudgoat
| Step | Expected Result |
|---|---|
| cloudgoat.py create | Outputs starting IAM credentials and a hint |
| Enumerate your own permissions | Reveals a policy-rollback or role-attachment escalation path |
| After escalation | get-caller-identity shows admin-equivalent access |
| cloudgoat.py destroy | Every CloudGoat-created resource removed |
Three layers stack to decide bucket access: Bucket Policy (resource-based), ACLs (legacy, mostly deprecated), and Block Public Access (an account- and bucket-level override switch). AWS now defaults Block Public Access ON for new buckets — but Meridian's buckets predate that default.
Or Meridian's shared practice set, if your instructor has provided one.
For each bucket.
For any Principal: "*" statement.
For "AllUsers" or "AuthenticatedUsers" grants.
For any bucket that's actually public — Policy, ACL, or a disabled Block Public Access switch.
# check Block Public Access at the bucket level aws s3api get-public-access-block --bucket [bucket-name] # check the bucket policy itself aws s3api get-bucket-policy --bucket [bucket-name]
| Layer | What To Check | Red Flag |
|---|---|---|
| Block Public Access | get-public-access-block output | Any of the four settings set to false |
| Bucket Policy | get-bucket-policy output | "Principal": "*" with no Condition |
| Object ACLs | get-bucket-acl / per-object | Grantee URI ending in AllUsers |
At flaws2.cloud — the second half requires your own AWS account.
Focused on S3/CloudFront misconfiguration.
You're given access to a container and need to find a way out.
Privileges, mounted volumes, or metadata service access.
Same one-sentence format as Lab 1.2.
| Path | Skill Tested |
|---|---|
| Path 1 | CloudFront/S3 origin misconfiguration |
| Path 2 | Container privilege/escape, IMDS credential theft |
On every S3 bucket in your account.
SSE-S3 (AWS-managed) or SSE-KMS (a key you control and can audit).
At the account/region level.
SSE-S3 is fine for low-sensitivity data; SSE-KMS is what lets you audit exactly who can decrypt.
# check a bucket's default encryption configuration aws s3api get-bucket-encryption --bucket [bucket-name] # check account-level EBS encryption-by-default aws ec2 get-ebs-encryption-by-default
| Setting | What It Tells You |
|---|---|
| SSE-S3 | Encrypted, but AWS holds and manages the key entirely |
| SSE-KMS | Encrypted with a key you control — every decrypt is auditable via CloudTrail |
| EBS encryption-by-default: false | New volumes from here on will NOT be encrypted unless told to be |
Many accounts have only the default 90-day event history, not a full trail.
Logging to an S3 bucket, if one doesn't already exist.
AWS's managed threat detection — mindful of its free trial period and cost after.
Not just configured — actually logging.
# check for an existing multi-region trail aws cloudtrail describe-trails # enable GuardDuty (mindful of cost after the free trial period) aws guardduty create-detector --enable
| Check | Expected Result |
|---|---|
| describe-trails | At least one trail with IsMultiRegionTrail: true |
| CloudTrail S3 bucket | Receiving new log files every few minutes |
| GuardDuty detector | Status: ENABLED |
Or query the S3-stored logs if you disabled the default history.
Around when you ran Lab 1.3's CloudGoat scenario.
Look for iam:AttachRolePolicy, iam:CreatePolicyVersion, or sts:AssumeRole.
Which identity made which call, in what order, leading to elevated access.
3–4 sentences describing exactly what you found.
# search Event History for a specific event type around your CloudGoat exercise
aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=AttachRolePolicy
| Event Name | What It Usually Means |
|---|---|
| AttachRolePolicy | An identity attached a (possibly more powerful) policy to a role |
| CreatePolicyVersion | A new policy version was created — check if it's more permissive |
| AssumeRole | One identity assumed another role — trace why it had permission to |
| PutUserPolicy | An inline policy attached directly to a user — often undocumented |
Lab 1.2's flAWS findings, Lab 1.3's CloudGoat escalation, Lab 3.2's CloudTrail evidence.
How would an attacker chain these three weaknesses together, start to finish, in a real account?
What would have stopped it — Block Public Access, least-privilege IAM, MFA, alerting on AttachRolePolicy?
One paragraph, written for someone who has never seen a terminal.
| Chain Step | Control That Would Stop It |
|---|---|
| Public bucket discovery (Lab 1.2) | Block Public Access enabled by default |
| Leaked credentials reused | Secrets scanning, short-lived credentials instead of long-lived keys |
| IAM privilege escalation (Lab 1.3) | Least-privilege policies, no wildcard actions/resources |
| Escalation went undetected | CloudTrail + an alert on sensitive API calls like AttachRolePolicy |
If you haven't already from Lab 0.2.
Attach the AWS-managed SecurityAudit and ViewOnlyAccess policies to your lab IAM user — enough to see everything, not enough to change anything.
Against your account.
And open it.
Note how many map directly to what you found manually in Modules 1–2.
# run a full AWS scan with HTML output
prowler aws --output-formats html json --output-directory ./prowler-results
| Output | What To Do With It |
|---|---|
| HTML report | Best for a first read-through — filterable by severity and service |
| JSON output | Best for feeding into another tool or tracking findings over time |
| High/Critical findings | Cross-check against your Module 1–2 manual findings — overlap confirms you found real issues |
Prowler supports several compliance frameworks for AWS.
E.g. CIS or NIST 800-53.
Identify, Protect, Detect, Respond, Recover — using what you already know.
This mapping is exactly what auditors expect walking into Meridian's SOC 2 review.
# list available compliance frameworks for AWS prowler aws --list-compliance # run scoped to a specific framework prowler aws --compliance cis_2.0_aws
| Finding Type | Typical NIST CSF Function |
|---|---|
| Public S3 bucket | Protect |
| No MFA on IAM users | Protect |
| CloudTrail disabled | Detect |
| No incident response runbook for cloud | Respond |
| No tested backup/restore for critical data | Recover |
From Lab 4.1.
The finding, the business risk in plain language, the specific fix, an owner role, a target date relative to the 90-day deadline.
Fixable in under an hour, versus needing a structural project (e.g. account-wide key rotation).
Quick wins first, so visible progress happens before the audit even if everything isn't finished.
| Column | What Belongs There |
|---|---|
| Finding | Exactly what Prowler/your manual review found, in one line |
| Business risk | What actually happens if this isn't fixed — no jargon |
| Fix | The specific configuration change, not "improve security" |
| Owner role | "Platform team", not "fix it" — someone has to own it |
| Target date | Tied to the 90-day deadline, not open-ended |
And set a budget alert immediately — same discipline as Lab 0.2.
And log in.
Into a dedicated resource group.
App registrations and managed identities — the closest analog to Module 1's AWS IAM work.
Completely, when finished.
git clone https://github.com/ine-labs/AzureGoat.git cd AzureGoat # log in and create a dedicated resource group first az login az group create --name azuregoat_app --location eastus terraform init terraform apply
| Concept | AWS Equivalent (Earlier Modules) |
|---|---|
| Azure AD App Registration | IAM Role |
| Managed Identity | EC2 Instance Profile |
| Storage Account public access | S3 Bucket public access |
| Azure Activity Log | CloudTrail |
And set a budget alert.
And authenticate.
Into a dedicated project.
The three areas its modules focus on.
And consider deleting the dedicated project entirely.
git clone https://github.com/ine-labs/GCPGoat.git
cd GCPGoat
gcloud auth login
gcloud config set project [your-dedicated-project-id]
terraform init
terraform apply
| Concept | AWS Equivalent | Azure Equivalent |
|---|---|---|
| GCP IAM Role | IAM Policy | Azure RBAC Role |
| Cloud Storage Bucket | S3 Bucket | Storage Account / Blob |
| Cloud Functions | Lambda | Azure Functions |
| Cloud Audit Logs | CloudTrail | Activity Log |
Against any of the three accounts you still have available.
Based on what you actually saw, not general reputation.
Which provider's defaults would you trust most out of the box, and why?
Confirm via each provider's console, not just CLI output.
| Provider | Secure-By-Default Today | Still Requires Explicit Config |
|---|---|---|
| AWS | S3 Block Public Access (new buckets) | IAM least-privilege, CloudTrail beyond 90-day default |
| Azure | Storage account public access often off on newer accounts | RBAC scoping, diagnostic logging |
| GCP | Cloud Storage uniform bucket-level access | IAM role bindings, Cloud Audit Logs export |