Skip to main content

Building a Vulnerable GCP Pentest Lab with Terraform

Lab Metadata

Ecosystem Fit

This page mirrors the original Medium lab content into the 1200km knowledge base so it remains available inside the 1200km.com documentation ecosystem. Use the linked repository when one exists; otherwise use the deployment commands and configuration blocks preserved below as the lab source of truth.

Deployment Requirements

The full prerequisites, deployment flow, validation commands, screenshots, and operational notes are preserved from the article below. Review the repository metadata above first, then follow the article sections in order.

A complete, step-by-step guide to deploying intentionally misconfigured cloud resources for hands-on security training.

Article screenshot

Introduction

This guide will walk you through building a comprehensive vulnerable cloud lab environment on Google Cloud Platform using Terraform. The lab includes intentionally misconfigured resources designed for cloud penetration testing training.

**⚠️ WARNING:**This lab contains intentional vulnerabilities. Only deploy in isolated test environments with proper authorization.

What You’ll Build:

  • DVWA (Damn Vulnerable Web Application)— Full-featured vulnerable web app with 10+ vulnerability categories (SQL Injection, XSS, Command Injection, File Inclusion, etc.)

  • Internal database server (MariaDB)

  • Publicly accessible storage bucket with sensitive data

  • Vulnerable Cloud Run service

  • Vulnerable Cloud Function with SSRF

  • Overprivileged service accounts

  • Exposed credentials

  • Information disclosure page exposing Cloud Function and Cloud Run URLs

**Estimated Time:**30–45 minutes Estimated Cost:~$12–15/month

Table of Contents

  • Introduction

  • Prerequisites

  • GCP Project Setup

  • Local Environment Setup

  • Terraform Configuration

  • Deployment Process

  • Verification

  • Accessing the Lab

  • Cleanup

Prerequisites

Required Accounts & Services

  • Google Cloud Platform Account

  • Active GCP account with billing enabled

  • Access to create projects and resources

2. Local Machine Requirements

  • Linux, macOS, or Windows with WSL

  • Internet connection

  • Terminal/command line access

Required Software

Before starting, ensure you have the following installed:

1. Google Cloud SDK (gcloud)

Linux:

# Download and install
curl https://sdk.cloud.google.com | bash
exec
-l
$SHELL
# Verify installation
gcloud version

Article screenshot

macOS:

#
Using Homebrew
brew install --cask google-cloud-sdk
#
Or download from:
#
https://cloud.google.com/sdk/docs/install

**Windows:**Download and run the installer from:https://cloud.google.com/sdk/docs/install

2. Terraform

Linux:

# Download Terraform
wget https://releases.hashicorp.com/terraform/1.6.0/terraform_1.6.0_linux_amd64.zip
unzip terraform_1.6.0_linux_amd64.zip
sudo
mv
terraform /usr/local/bin/
rm
terraform_1.6.0_linux_amd64.zip
#or snap
sudo snap install terraform --classic
# Verify
terraform version

Article screenshot

macOS:

brew install terraform

**Windows:**Download from:https://www.terraform.io/downloads

3. Git

# Linux
sudo apt-
get
install git
# Debian/Ubuntu
sudo yum install git
# RHEL/CentOS
# macOS
brew install git
# Verify
git --version

Article screenshot

4. Zip Utility (for Cloud Function)

# Linux
sudo apt-get install
zip
# macOS (usually pre-installed)
# Windows: Use built-in compression or 7-Zip

GCP Project Setup

Step 1: Create a New GCP Project

Option A: Using gcloud CLI

# Set a unique project ID (must be globally unique)
export
PROJECT_ID=
"cloud-pentest-lab-
$(date +%s)
"
export
REGION=
"us-central1"
export
ZONE=
"us-central1-a"
# Create the project
gcloud projects create
$PROJECT_ID
\
--name=
"Cloud Pentest Lab"
\
--set-as-default
# Link billing account (replace with your billing account ID)
gcloud billing projects
link

$PROJECT_ID
\
--billing-account=$(gcloud billing accounts list --format=
"value(name)"
|
head
-1)

Option B: Using GCP Console

  • Go toGCP Console

  • Click “Select a project” → “New Project”

  • Enter project name: “Cloud Pentest Lab”

  • Click “Create”

  • Note your Project ID

Article screenshot

Step 2: Enable Required APIs

Terraform will automatically enable most APIs, but let’s enable them manually to avoid delays:

# Set your project
gcloud config
set
project
$PROJECT_ID
# Enable all required APIs
gcloud services
enable
\
compute.googleapis.com \
storage.googleapis.com \
iam.googleapis.com \
cloudresourcemanager.googleapis.com \
secretmanager.googleapis.com \
container.googleapis.com \
run.googleapis.com \
cloudfunctions.googleapis.com \
cloudbuild.googleapis.com
# Verify APIs are enabled
gcloud services list --enabled

Article screenshot

**Expected Output:**You should see all 8 APIs listed as “ENABLED”.

Step 3: Authenticate and Set Up Application Default Credentials

⚠️CRITICAL: This step is required for Terraform to work. Without it, you’ll get authentication errors when running terraform plan or terraform apply.

Step 3.1: Authenticate with Google Account

# Authenticate with your Google account
gcloud auth login
# Set up Application Default Credentials (for Terraform)
gcloud auth application-
default
login
# Verify authentication
gcloud auth
list

Expected Output:

Article screenshot

Step 3.2: Set Up Application Default Credentials (Required for Terraform)

Terraform uses Application Default Credentials (ADC) to authenticate with GCP. This is different fromgcloud auth login.

#
Set
up Application
Default
Credentials
gcloud auth application
-
default
login

What happens:

  • A browser window will open

  • You’ll be asked to sign in with your Google account

  • You’ll need to grant permissions

  • Credentials will be saved to~/.config/gcloud/application_default_credentials.json

If gcloud is not in your PATH:

If you getcommand 'gcloud' not found, use the full path:

# Find gcloud location
which
gcloud
# or
find ~ -name gcloud -
type
f 2>/dev/null | grep bin/gcloud
# Use full path (example)
~/google-cloud-sdk/bin/gcloud auth application-default login

To add gcloud to PATH permanently:

# Add to ~/.bashrc or ~/.zshrc
echo

'export PATH="$HOME/google-cloud-sdk/bin:$PATH"'
>> ~/.bashrc
source
~/.bashrc
# Then use normally
gcloud auth application-default login

Step 3.3: Verify Application Default Credentials

# Test that credentials are set up correctly
gcloud auth application-
default

print
-access-token

Expected Output:

  • Should return an access token (long string starting withya29.)

  • If you get an error, credentials are not set up correctly

Alternative verification:

# Check if credentials file exists
ls
-la ~/.config/gcloud/application_default_credentials.json

Troubleshooting Application Default Credentials

Error: “No credentials loaded” or “could not find default credentials”

This means Application Default Credentials are not set up. Solution:

gcloud auth application-
default
login

Error: Browser doesn’t open or authentication fails

Use the--no-browserflag:

gcloud auth application-default login
--no-browser

This will:

  • Display a URL to visit manually

  • Ask you to paste the authorization code

  • Complete the authentication

Error: “gcloud not found”

  • Use the full path to gcloud (see Step 3.2 above)

  • Or install Google Cloud SDK if not installed

Error: “scope is required but not consented”

  • Run the login command again

  • Make sure to grant all requested permissions

  • Check that you’re using the correct Google account

# Create a budget alert (optional but recommended)
# This helps prevent unexpected charges
# First, get your billing account ID
gcloud billing accounts list
# Then set up a budget (requires billing account ID)
# Note: Budget setup is typically done via console
# Go to: https://console.cloud.google.com/billing/budgets

Via Console:

  • Navigate to Billing → Budgets & alerts

  • Click “Create Budget”

  • Set budget amount (e.g., $20)

  • Set alert threshold (e.g., 50%, 90%, 100%)

  • Save

Article screenshot

Local Environment Setup

Step 1: Clone or Navigate to Project Directory

If you already have the project files:

cd
/home/andrey/Cloud_PTcd /home/andrey/Cloud_PT

If you need to set up from scratch:

# Create project directory
mkdir
-p ~/cloud-pentest-lab
cd
~/cloud-pentest-lab
# Copy Terraform files (if needed)
# Or clone from your repository

Step 3: Set Environment Variables

# Set your GCP project ID
export
TF_VAR_project_id=
"your-project-id-here"
export
TF_VAR_region=
"us-central1"
export
TF_VAR_zone=
"us-central1-a"
# Or create a .env file (optional)
cat
> .
env
<<
EOF
export TF_VAR_project_id="your-project-id-here"
export TF_VAR_region="us-central1"
export TF_VAR_zone="us-central1-a"
EOF
source
.
env

Terraform Configuration

Step 1: Create Terraform Variables Files

cd
terraform
# Copy the example file
cp
terraform.tfvars.example terraform.tfvars
# Edit the file with your values
nano terraform.tfvars
# or
vim terraform.tfvars
# or use your preferred editor

Edit**terraform.tfvars**:

project_id
=
"your-gcp-project-id"
region
=
"us-central1"
zone
=
"us-central1-a"

**Important:**Replaceyour-gcp-project-idwith your actual GCP project ID.

Step 2: Create Terraform Configuration Files

Before verifying the project structure, you need to create all the Terraform configuration files. If you’re starting from scratch, create the following files:

Create Directory Structure

# Create main project directory (if not exists)
mkdir
-p ~/cloud-pentest-lab
cd
~/cloud-pentest-lab
# Create Terraform directory
mkdir
-p terraform/function_code
# Create other directories
mkdir
-p scenarios tools
# Create Terraform directory
mkdir
-p terraform/function_code
# Create other directories
mkdir
-p scenarios tools

Create Terraform Files

1. Create**terraform/main.tf**

This is the main Terraform configuration file. Copy the content from the project’sterraform/main.tffile, or create it with the infrastructure definitions.

#
If you have the file from the project, copy it:
#
cp
/path/to/Cloud_PT/terraform/main.tf terraform/
#
Or create it manually (see terraform/main.tf
in
the project)

2. Create**terraform/variables.tf**

cat > terraform/variables.tf <<
'EOF'
variable
"project_id"
{
description =
"GCP Project ID"

type
=
string
}
variable
"region"
{
description =
"GCP Region"

type
=
string

default
=
"us-central1"
}
variable
"zone"
{
description =
"GCP Zone"

type
=
string

default
=
"us-central1-a"
}
EOF

3. Create**terraform/outputs.tf**

output

"project_id"
{
description =
"GCP Project ID"
value = var.project_id
}
output

"region"
{
description =
"GCP Region"
value = var.region
}
output

"zone"
{
description =
"GCP Zone"
value = var.zone
}
output

"web_server_ip"
{
description =
"Public IP of the web server"
value = google_compute_instance.web_server.network_interface[
0
].access_config[
0
].nat_ip
}
output

"web_server_name"
{
description =
"Name of the web server instance"
value = google_compute_instance.web_server.name
}
output

"db_server_name"
{
description =
"Name of the database server instance"
value = google_compute_instance.db_server.name
}
output

"overprivileged_sa_email"
{
description =
"Email of the overprivileged service account"
value = google_service_account.overprivileged_sa.email
}
output

"vulnerable_bucket_name"
{
description =
"Name of the vulnerable storage bucket"
value = google_storage_bucket.vulnerable_bucket.name
}
output

"cloud_run_url"
{
description =
"URL of the vulnerable Cloud Run service"
value = google_cloud_run_service.vulnerable_api.
status
[
0
].url
}
output

"cloud_function_url"
{
description =
"URL of the vulnerable Cloud Function"
value = google_cloudfunctions_function.vulnerable_function.https_trigger_url
}
output

"service_account_key_location"
{
description =
"Location of exposed service account key"
value =
"Metadata: ${google_compute_instance.web_server.name} or Secret Manager: ${google_secret_manager_secret.sa_key.secret_id}"
}
output

"database_internal_ip"
{
description =
"Internal IP of the database server"
value = google_compute_instance.db_server.network_interface[
0
].network_ip
sensitive =
true
}

4. Create**terraform/terraform.tfvars.example**

cat
> terraform/terraform.tfvars.example <<
'EOF'
project_id =
"your-gcp-project-id"
region =
"us-central1"
zone =
"us-central1-a"
EOF

5. Create**terraform/.gitignore**

cat > terraform/.gitignore <<
'EOF
'
*.tfstate
*.tfstate.*
.terraform/
.terraform.lock.hcl
*.tfvars
!*.tfvars.example
crash.log
override
.tf
override
.tf.json
*_
override
.tf
*_
override
.tf.json
function_code.zip
EOF

6. Create**terraform/Makefile**(Optional)

cat > terraform/Makefile <<'EOF'
.PHONY
: init plan apply destroy validate format output
init:
terraform init
validate:
terraform validate
format:
terraform fmt -recursive
plan:
terraform plan -out=tfplan
apply:
terraform apply tfplan
destroy:
terraform destroy
output:
terraform output
prepare-function:
cd function_code && \
pip3 install -r requirements.txt -t . && \
zip -r ../function_code.zip . && \
cd ..
clean:
rm -f *.tfstate* tfplan
rm -rf .terraform
rm -f function_code.zip
deploy: validate prepare-function plan apply output
check: validate format
EOF

7. Create Cloud Function Code

Create**terraform/function_code/main.py**:

cat > terraform/function_code/main.py <<
'EOF'
import
os
import
json
import
urllib.request
from
flask
import
Request
def

vulnerable_handler
(
request: Request
):

"""Intentionally vulnerable Cloud Function with SSRF vulnerability"""


# SSRF vulnerability - no validation

if

'url'

in
request.args:
url = request.args.get(
'url'
)

try
:

# Vulnerable: No validation of URL
response = urllib.request.urlopen(url, timeout=
5
)

return
response.read().decode(
'utf-8'
)

except
Exception
as
e:

return

f"Error:
{
str
(e)}
"


# Command injection vulnerability

if

'cmd'

in
request.args:

import
subprocess
cmd = request.args.get(
'cmd'
)

# Vulnerable: Direct command execution
result = subprocess.run(cmd, shell=
True
, capture_output=
True
, text=
True
)

return
result.stdout


# Exposed environment variables

if

'env'

in
request.args:

return
json.dumps(
dict
(os.environ))


# Exposed secrets

if

'secret'

in
request.args:

return
os.environ.get(
'SECRET_KEY'
,
'Not found'
)


return

"""
<h1>Vulnerable Cloud Function</h1>
<p>Endpoints:</p>
<ul>
<li>?url=http://example.com - SSRF</li>
<li>?cmd=whoami - Command Injection</li>
<li>?env=1 - Environment Variables</li>
<li>?secret=1 - Secret Key</li>
</ul>
"""
EOF

Create**terraform/function_code/requirements.txt**:

cat
> terraform/function_code/requirements.txt <<
'EOF'
flask==2.3.0
EOF

DVWA (Damn Vulnerable Web Application) Deployment

The Terraform configuration automatically deploys**DVWA (Damn Vulnerable Web Application)**on the web server instance. DVWA is a well-known, intentionally vulnerable web application designed for security testing and training.

What is DVWA?

DVWA is a PHP/MySQL web application that is intentionally vulnerable. It provides a legal environment to learn and practice web application security testing techniques. DVWA includes multiple vulnerability categories with different security levels (Low, Medium, High, Impossible).

DVWA Features

DVWA includes the following vulnerability categories:

  • SQL Injection— Various SQL injection challenges

  • Command Injection— OS command execution vulnerabilities

  • File Inclusion— Local and Remote File Inclusion (LFI/RFI)

  • File Upload— Unsafe file upload vulnerabilities

  • XSS (Cross-Site Scripting)— Reflected, Stored, and DOM-based XSS

  • CSRF (Cross-Site Request Forgery)— CSRF token bypass challenges

  • Brute Force— Weak authentication mechanisms

  • Weak Session IDs— Predictable session token vulnerabilities

  • CSP Bypass— Content Security Policy bypass techniques

  • JavaScript— Client-side security vulnerabilities

What Gets Deployed Automatically

The startup script inmain.tfautomatically:

  • **Installs:**nginx, PHP 7.4 with FPM, PHP extensions (mysql, gd, curl, xml, mbstring, zip), MariaDB, Git

  • **Clones DVWA:**Downloads DVWA from GitHub (https://github.com/digininja/DVWA)

  • **Configures Database:**Sets up MariaDB with DVWA database and user

  • **Configures DVWA:**Pre-configures with “low” security level (most vulnerable)

  • **Creates Info Page:**Creates/var/www/html/info.phpthat exposes Cloud Function and Cloud Run URLs

  • **Sets Permissions:**Configures proper file permissions for uploads and logs

  • **Exposes Credentials:**Service account key in/tmp/sa-key.json

**No Manual Steps Required:**DVWA is deployed automatically when the instance is created. The startup script runs during instance boot and sets up everything.

DVWA Access Information

After deployment, you can access DVWA:

  • URL:[http://WEB_SERVER_IP/](http://WEB_SERVER_IP/)

  • Default Credentials:

  • Username:admin

  • Password:password

  • **Security Level:**Pre-configured to “Low” (most vulnerable)

  • **Database:**Automatically created and initialized

Information Disclosure Page

An additionalinfo.phppage is created that exposes Cloud Function and Cloud Run URLs:

  • URL:[http://WEB_SERVER_IP/info.php](http://WEB_SERVER_IP/info.php)

  • **Purpose:**Simulates exposed internal documentation

  • Contains:

  • Cloud Function URL

  • Cloud Run service URL

  • Storage bucket name

  • **Important Note:**Themain.tffile is large and complex (350+ lines). You have three options:

Option A: Copy from Existing Project (Recommended)

# If you have access to the project files
cp
/home/andrey/Cloud_PT/terraform/main.tf terraform/
# Verify the file was copied
ls
-lh terraform/main.tf

Option B: Download/Copy from RepositoryIf the files are in a repository, clone it:

git
clone
<repository-url> ~/cloud-pentest-lab
cd
~/cloud-pentest-lab

Option C: Manual CreationYou’ll need to copy the completemain.tfcontent which includes:

nano
main
.tf
terraform {
required_version =
">= 1.0"
required_providers {
google = {
source =
"hashicorp/google"
version =
"~> 5.0"
}
archive = {
source =
"hashicorp/archive"
version =
"~> 2.0"
}
random = {
source =
"hashicorp/random"
version =
"~> 3.0"
}
}
}
provider
"google"
{
project = var.project_id
region = var.region
zone = var.zone
}
# Enable required APIs
resource
"google_project_service"

"required_apis"
{
for_each = toset([

"compute.googleapis.com"
,

"storage.googleapis.com"
,

"iam.googleapis.com"
,

"cloudresourcemanager.googleapis.com"
,

"secretmanager.googleapis.com"
,

"container.googleapis.com"
,

"run.googleapis.com"
,

"cloudfunctions.googleapis.com"
,

"cloudbuild.googleapis.com"
])
service = each.value
project = var.project_id
disable_on_destroy = false
}
# VPC Network
resource
"google_compute_network"

"vulnerable_vpc"
{
name =
"vulnerable-vpc-
${random_id.suffix.hex}
"
auto_create_subnetworks = false
routing_mode =
"REGIONAL"
}
# Public Subnet (intentionally exposed)
resource
"google_compute_subnetwork"

"public_subnet"
{
name =
"public-subnet-
${random_id.suffix.hex}
"
ip_cidr_range =
"10.0.1.0/24"
region = var.region
network = google_compute_network.vulnerable_vpc.id
}
# Private Subnet (target for lateral movement)
resource
"google_compute_subnetwork"

"private_subnet"
{
name =
"private-subnet-
${random_id.suffix.hex}
"
ip_cidr_range =
"10.0.2.0/24"
region = var.region
network = google_compute_network.vulnerable_vpc.id
private_ip_google_access = true
}
# Firewall Rules - Intentionally permissive (vulnerable)
resource
"google_compute_firewall"

"allow_all_internal"
{
name =
"allow-all-internal-
${random_id.suffix.hex}
"
network = google_compute_network.vulnerable_vpc.name
allow {
protocol =
"tcp"
ports = [
"0-65535"
]
}
allow {
protocol =
"udp"
ports = [
"0-65535"
]
}
allow {
protocol =
"icmp"
}
source_ranges = [
"10.0.0.0/8"
]
target_tags = [
"vulnerable"
]
}
# Firewall Rule - Exposed web services
resource
"google_compute_firewall"

"allow_http_https"
{
name =
"allow-http-https-
${random_id.suffix.hex}
"
network = google_compute_network.vulnerable_vpc.name
allow {
protocol =
"tcp"
ports = [
"80"
,
"443"
,
"8080"
,
"8443"
]
}
source_ranges = [
"0.0.0.0/0"
]
target_tags = [
"web-server"
]
}
# Firewall Rule - Allow SSH access
resource
"google_compute_firewall"

"allow_ssh"
{
name =
"allow-ssh-
${random_id.suffix.hex}
"
network = google_compute_network.vulnerable_vpc.name
allow {
protocol =
"tcp"
ports = [
"22"
]
}
source_ranges = [
"0.0.0.0/0"
]
target_tags = [
"web-server"
,
"vulnerable"
]
}
# Overprivileged Service Account (intentional misconfiguration)
resource
"google_service_account"

"overprivileged_sa"
{
account_id =
"overprivileged-sa-
${random_id.suffix.hex}
"
display_name =
"Overprivileged Service Account"
description =
"Intentionally overprivileged for pentest lab"
}
# Grant excessive permissions
resource
"google_project_iam_member"

"overprivileged_roles"
{
for_each = toset([

"roles/owner"
,

"roles/storage.admin"
,

"roles/secretmanager.admin"
,

"roles/compute.admin"
,

"roles/iam.securityAdmin"
])
project = var.project_id
role = each.value
member =
"serviceAccount:
${google_service_account.overprivileged_sa.email}
"
}
# Service Account Key (intentionally exposed in metadata)
resource
"google_service_account_key"

"exposed_key"
{
service_account_id = google_service_account.overprivileged_sa.name
public_key_type =
"TYPE_X509_PEM_FILE"
}
# Store key in Secret Manager (but also expose it)
resource
"google_secret_manager_secret"

"sa_key"
{
secret_id =
"exposed-sa-key-
${random_id.suffix.hex}
"
replication {
auto {}
}
}
resource
"google_secret_manager_secret_version"

"sa_key_version"
{
secret = google_secret_manager_secret.sa_key.id
secret_data = base64decode(google_service_account_key.exposed_key.private_key)
}
# Storage Bucket - Publicly accessible with sensitive data
resource
"google_storage_bucket"

"vulnerable_bucket"
{
name =
"vulnerable-bucket-
${random_id.suffix.hex}
"
location = var.region
force_destroy = true
uniform_bucket_level_access = false

# Intentionally public
public_access_prevention =
"inherited"
}
# Make bucket publicly readable
resource
"google_storage_bucket_iam_member"

"public_read"
{
bucket = google_storage_bucket.vulnerable_bucket.name
role =
"roles/storage.objectViewer"
member =
"allUsers"
}
# Upload sensitive file to bucket
resource
"google_storage_bucket_object"

"sensitive_data"
{
name =
"secrets/database-credentials.json"
bucket = google_storage_bucket.vulnerable_bucket.name
content = jsonencode({
db_host =
"internal-db-
${random_id.suffix.hex}
"
db_user =
"admin"
db_password =
"EXAMPLE_PASSWORD_123!"
api_key =
"stripe_live_example_
${random_id.suffix.hex}
"
})
}
# Compute Instance - Web Server (vulnerable)
resource
"google_compute_instance"

"web_server"
{
name =
"web-server-
${random_id.suffix.hex}
"
machine_type =
"e2-micro"
zone = var.zone
boot_disk {
initialize_params {
image =
"debian-cloud/debian-11"
size =
20
}
}
network_interface {
network = google_compute_network.vulnerable_vpc.name
subnetwork = google_compute_subnetwork.public_subnet.name
access_config {

# Public IP
}
}
tags = [
"web-server"
,
"vulnerable"
]

# Expose service account key in metadata (vulnerable)
metadata = {
startup-script = <<-EOF

#!/bin/bash
set -e


# Update system
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -
y
\
nginx \
php-fpm \
php-cli \
php-mysql \
php-gd \
php-curl \
php-xml \
php-mbstring \
php-zip \
mariadb-server \
mariadb-client \
git \
unzip


# Start services
systemctl start mariadb
systemctl start nginx
systemctl start php7.
4
-fpm
systemctl enable mariadb
systemctl enable nginx
systemctl enable php7.
4
-fpm


# Intentionally expose service account key in metadata
echo
"
${base64decode(google_service_account_key.exposed_key.private_key)}
"
>
/tmp/sa
-key.json

chmod

644
/tmp/sa-key.json


# Setup MySQL for DVWA
mysql -e
"CREATE DATABASE IF NOT EXISTS dvwa;"
mysql -e
"CREATE USER IF NOT EXISTS 'dvwa'@'localhost' IDENTIFIED BY 'p@ssw0rd';"
mysql -e
"GRANT ALL PRIVILEGES ON dvwa.* TO 'dvwa'@'localhost';"
mysql -e
"FLUSH PRIVILEGES;"


# Install DVWA
cd /var/www/html
rm -rf *
git clone https:
//gi
thub.com/digininja/DVWA.git .


# Configure DVWA
cp config/config.inc.php.dist config/config.inc.php
sed -i
"s/\$_DVWA\[ 'db_user' \] = 'root';/\$_DVWA\[ 'db_user' \] = 'dvwa';/"
config/config.inc.php
sed -i
"s/\$_DVWA\[ 'db_password' \] = 'p@ssw0rd';/\$_DVWA\[ 'db_password' \] = 'p@ssw0rd';/"
config/config.inc.php
sed -i
"s/\$_DVWA\[ 'db_database' \] = 'dvwa';/\$_DVWA\[ 'db_database' \] = 'dvwa';/"
config/config.inc.php
sed -i
"s/\$_DVWA\[ 'default_security_level' \] = 'impossible';/\$_DVWA\[ 'default_security_level' \] = 'low';/"
config/config.inc.php


# Set permissions

chown
-R www-data:www-data /var/www/html

chmod
-R
755
/var/www/html

chmod

777
/var/www/html/hackable/uploads/
2
>
/dev/null
|| true

# Create phpids log directory if it doesn't exist

mkdir
-p /var/www/html/external/phpids/
0
.
6
/lib/IDS/tmp/
2
>
/dev/null
|| true

chmod

777
/var/www/html/external/phpids/
0
.
6
/lib/IDS/tmp/phpids_log.txt
2
>
/dev/null
|| true

chmod

777
/var/www/html/config


# Create info page that exposes Cloud Function and Cloud Run URLs
cat >
/var/
www/html/info.php <<
'INFOPHP'
<?php
// Information disclosure - exposes internal service URLs
?>
<!DOCTYPE html>
<html>
<head>
<title>Internal Services Information<
/title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }
.container { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
h1 { color: #333; }
.service { margin: 20px 0; padding: 15px; background: #e8f4f8; border-left: 4px solid #2196F3; }
.service h3 { margin-top: 0; color: #1976D2; }
.url { font-family: monospace; background: #f0f0f0; padding: 5px 10px; border-radius: 3px; word-break: break-all; }
.warning { color: #d32f2f; font-weight: bold; }
</s
tyle>
<
/head>
<body>
<div class="container">
<h1>Internal Services Information</
h1>
<p>This page contains internal service URLs
for
development purposes.<
/p>

<div class="service">
<h3>Cloud Function API</
h3>
<p>Serverless function
for
processing requests:<
/p>
<div class="url">${google_cloudfunctions_function.vulnerable_function.https_trigger_url}</di
v>
<p><small>Endpoints: ?cmd=, ?url=, ?env=, ?secret=<
/small></p
>
<
/div>

<div class="service">
<h3>Cloud Run API Service</
h3>
<p>Containerized API service:<
/p>
<div class="url">${google_cloud_run_service.vulnerable_api.status[0].url}</di
v>
<
/div>

<div class="service">
<h3>Storage Bucket</
h3>
<p>Public storage bucket:<
/p>
<div class="url">gs:/
/${google_storage_bucket.vulnerable_bucket.name}</di
v>
<
/div>

<hr>
<p><a href="/
">← Back to DVWA</a></p>
<p><small>Last updated: <?php echo date('Y-m-d H:i:s'); ?></small></p>
</div>
</body>
</html>
INFOPHP

# Create redirect from root to DVWA setup if needed
cat > /var/www/html/index.php <<'INDEXPHP'
<?php
// Redirect to DVWA or show info
if (file_exists('/var/www/html/setup.php')) {
header('Location: /setup.php');
exit;
} else {
header('Location: /login.php');
exit;
}
?>
INDEXPHP

# Update nginx config to serve PHP and DVWA
cat > /etc/nginx/sites-available/default <<NGINX
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.php index.html;

location / {
try_files \$uri \$uri/ =404;
}

location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
include fastcgi_params;
}

location ~ /\.ht {
deny all;
}
}
NGINX

# Restart services
systemctl restart nginx
systemctl restart php7.4-fpm

# Wait a bit for services to stabilize
sleep 5

# Create DVWA database (run setup via CLI if possible)
echo "
DVWA installation complete!
"
echo "
Access DVWA at: http:
//
$(curl -s ifconfig.me)/
"
echo "
Default credentials: admin / password
"
echo "
Service info page: http:
//
$(curl -s ifconfig.me)/info.php
"
EOF
# Store key in metadata (intentional vulnerability)
sa-key = base64decode(google_service_account_key.exposed_key.private_key)
}
service_account {
email = google_service_account.overprivileged_sa.email
scopes = ["
cloud-platform
"]
}
}
# Compute Instance - Internal Database Server
resource "
google_compute_instance
" "
db_server
" {
name = "
db-server-${random_id.suffix.hex}
"
machine_type = "
e2-micro
"
zone = var.zone
boot_disk {
initialize_params {
image = "
debian-cloud/debian-
11
"
size = 20
}
}
network_interface {
network = google_compute_network.vulnerable_vpc.name
subnetwork = google_compute_subnetwork.private_subnet.name
# No public IP
}
tags = ["
database
", "
vulnerable
"]
metadata = {
startup-script = <<-EOF
#!/bin/bash
apt-get update
apt-get install -y mysql-server
systemctl start mysql

# Create vulnerable database
mysql -e "
CREATE DATABASE app_db;
"
mysql -e "
CREATE USER
'admin'
@'%' IDENTIFIED BY
'EXAMPLE_PASSWORD_123!'
;
"
mysql -e "
GRANT ALL PRIVILEGES ON app_db.* TO
'admin'
@'%';
"
mysql -e "
FLUSH PRIVILEGES;
"
EOF
}
}
# Cloud Run Service - Vulnerable API
resource "
google_cloud_run_service
" "
vulnerable_api
" {
name = "
vulnerable-api-${random_id.suffix.hex}
"
location = var.region
template {
spec {
containers {
image = "
gcr.io/cloudrun/hello
"
env {
name = "
DATABASE_URL
"
value = "
mysql:
//admin
:EXAMPLE_PASSWORD_123!@${google_compute_instance.db_server.network_interface[
0
].network_ip}:
3306
/app_db
"
}
env {
name = "
API_KEY
"
value = "
stripe_live_example
_
${random_id.suffix.hex}
"
}
}
service_account_name = google_service_account.overprivileged_sa.email
}
}
traffic {
percent = 100
latest_revision = true
}
}
# Make Cloud Run publicly accessible
resource "
google_cloud_run_service_iam_member
" "
public_access
" {
service = google_cloud_run_service.vulnerable_api.name
location = google_cloud_run_service.vulnerable_api.location
role = "
roles/run.invoker
"
member = "
allUsers
"
}
# Cloud Function - Vulnerable function with SSRF
# Note: Function code must be prepared before deployment
# Run: cd function_code && zip -r ../function_code.zip . && cd ..
resource "
google_cloudfunctions_function
" "
vulnerable_function
" {
name = "
vulnerable-function-${random_id.suffix.hex}
"
description = "
Intentionally vulnerable function
for
pentest
"
runtime = "
python39
"
region = var.region
available_memory_mb = 256
source_archive_bucket = google_storage_bucket.vulnerable_bucket.name
source_archive_object = google_storage_bucket_object.function_code.name
# HTTP trigger - required for HTTP-triggered functions
trigger_http = true
entry_point = "
vulnerable_handler
"
environment_variables = {
SECRET_KEY = "
exposed-secret-${random_id.suffix.hex}
"
}
service_account_email = google_service_account.overprivileged_sa.email
depends_on = [google_storage_bucket_object.function_code]
}
# Function code - create zip archive
data "
archive_file
" "
function_zip
" {
type = "
zip
"
output_path = "
${path.module}/function_code.zip
"
source_dir = "
${path.module}/function_code
"
}
resource "
google_storage_bucket_object
" "
function_code
" {
name = "
function-code.zip
"
bucket = google_storage_bucket.vulnerable_bucket.name
source = data.archive_file.function_zip.output_path
}
# Make Cloud Function publicly accessible
resource "
google_cloudfunctions_function_iam_member
" "
public_access
" {
project = var.project_id
region = var.region
cloud_function = google_cloudfunctions_function.vulnerable_function.name
role = "
roles/cloudfunctions.invoker
"
member = "
allUsers
"
}
# Random suffix for uniqueness
resource "
random_id
" "
suffix
" {
byte_length = 4
}
  • Terraform and provider configuration (google, archive, random)

  • API enablement (8 GCP APIs)

  • VPC and network setup (2 subnets)

  • Firewall rules (2 rules)

  • Service accounts and IAM (overprivileged SA with 5 roles)

  • Storage bucket (public with sensitive data)

  • Compute instances (web server and database)

  • Cloud Run service (vulnerable API)

  • Cloud Function (SSRF vulnerability)

  • Archive file data source (for function code)

**⚠️ CRITICAL:**Themain.tffile is essential - without it, Terraform cannot deploy the infrastructure. Make sure you have a complete copy.

**Recommended:**Use Option A or B to ensure you have the complete, tested configuration. Themain.tffile contains all the infrastructure definitions and is too large to manually type.

Step 3: Verify Project Structure

Ensure you have the following structure:

tree terraform/ -L
2

Expected Structure:

Article screenshot

Step 4: Review Terraform Configuration

Before deploying, let’s understand what will be created:

# View main configuration
cat
main.tf |
head
-50
# View variables
cat
variables.tf
# View outputs
cat
outputs.tf

Key Resources That Will Be Created:

  • 1 VPC network

  • 2 subnets (public and private)

  • 2 firewall rules

  • 2 compute instances (web server and database)

  • 1 storage bucket

  • 1 Cloud Run service

  • 1 Cloud Function

  • 1 service account with multiple roles

  • 1 Secret Manager secret

Step 4: Prepare Cloud Function Code

The Cloud Function code needs to be packaged as a ZIP file. Terraform will handle this automatically, but let’s verify:

# Check function code exists
ls
-la function_code/
# Expected files:
# - main.py
# - requirements.txt

Themain.tffile includes adata "archive_file"resource that will automatically create the ZIP file during deployment.

Deployment Process

Step 1: Initialize Terraform

cd
terraform
# Initialize Terraform (downloads providers)
terraform
init

Expected Output:

Article screenshot

If you see errors:

  • Check your internet connection

  • Verify Terraform version:terraform version(should be >= 1.0)

  • Check provider versions inmain.tf

Step 2: Validate Configuration

# Validate Terraform configuration
terraform validate

Expected Output:

Article screenshot

If validation fails:

  • Check for syntax errors in.tffiles

  • Verify all required variables are set

  • Review error messages for specific issues

Step 3: Format Configuration (Optional)

Terraform fmt formats Terraform files to a consistent style.

What it does

  • Rewrites .tf files to canonical formatting

  • Standardizes indentation, spacing, and alignment

  • Makes code easier to read and maintain

Why it’s optional

  • Not required for functionality

  • Terraform works without formatting

  • Recommended for consistency and readability

# Format all Terraform files
terraform
fmt
-recursive

Step 4: Review Deployment Plan

**IMPORTANT:**Always review the plan before applying!

# Generate and review the execution plan
terraform plan

What to Look For:

  • Number of resources to be created (should be ~15–20)

  • Resource types and names

  • Any warnings or errors

  • Estimated costs (if shown)

Expected Output:

Plan:

20

to
add,
0

to
change,
0

to
destroy.
Changes to Outputs:
+ cloud_function_url = (known after apply)
+ cloud_run_url = (known after apply)
+ database_internal_ip = (sensitive value)
+ overprivileged_sa_email = (known after apply)
+ service_account_key_location = (known after apply)
+ vulnerable_bucket_name = (known after apply)
+ web_server_ip = (known after apply)

Save the plan to a file (optional):

terraform plan -
out
=tfplan

Step 5: Apply Configuration

⚠️ This will create real resources and incur costs!

# Apply the configuration
terraform apply
# Or if you saved the plan:
terraform apply tfplan

Terraform will:

  • Show you the plan again

  • Ask for confirmation:Do you want to perform these actions?

  • Typeyesto proceed

Expected Process:

google_project_service
.required_apis
[
"compute.googleapis.com"
]
:
Creating
...
google_project_service
.required_apis
[
"storage.googleapis.com"
]
:
Creating
...
...
google_compute_instance
.web_server
:
Creating
...
google_compute_instance
.db_server
:
Creating
...
...
Apply

complete
!
Resources
:
20

added
,
0

changed
,
0

destroyed
.

**Deployment Time:**5–10 minutes

Common Issues During Apply:

  • **API not enabled:**Wait a few minutes and retry

  • **Quota exceeded:**Check your GCP quotas

  • **Permission denied:**Verify your account has Owner role

  • **Billing not enabled:**Enable billing for the project

Article screenshot

Step 6: Save Outputs

After successful deployment, save the outputs:

terraform
output
project_id
terraform
output
region
terraform
output
zone
terraform
output
web_server_ip
terraform
output
web_server_name
terraform
output
db_server_name
terraform
output
vulnerable_bucket_name
terraform
output
cloud_run_url
terraform
output
cloud_function_url
terraform
output
overprivileged_sa_email
terraform
output
service_account_key_location
terraform
output
database_internal_ip # Sensitive - requires -raw f

Important Outputs to Save:

  • web_server_ip- Public IP for web server

  • vulnerable_bucket_name- Storage bucket name

  • cloud_run_url- Cloud Run service URL

  • cloud_function_url- Cloud Function URL

  • overprivileged_sa_email- Service account email

Verification

Step 1: Verify Compute Instances

# List all instances
gcloud compute instances list

Article screenshot

Step 2: Verify DVWA Installation

The web server includesDVWA (Damn Vulnerable Web Application)— a full-featured vulnerable web application for security testing.

# Get web server IP
WEB_IP=$(terraform output -raw web_server_ip)
# Access DVWA (wait 3-5 minutes after deployment for startup script)
curl -L http:
//
$WEB_IP/ | head -
30
# Or open in browser: http://$WEB_IP/
# Check info page that exposes Cloud Function and Cloud Run URLs
curl http:
//
$WEB_IP/info.php |
grep
-i
"cloud"

Article screenshot

Expected DVWA Content:

  • DVWA login/setup page should be accessible

  • Default credentials:admin/password

  • Database setup may be required on first access

DVWA Features:

  • 10+ vulnerability categories (SQL Injection, XSS, Command Injection, File Inclusion, etc.)

  • Multiple security levels (Low, Medium, High, Impossible)

  • Pre-configured to “Low” security level (most vulnerable)

Information Disclosure Page:

  • URL:[http://WEB_SERVER_IP/info.php](http://WEB_SERVER_IP/info.php)

  • **Purpose:**Exposes Cloud Function and Cloud Run URLs

  • Contains:

  • Cloud Function URL

  • Cloud Run service URL

  • Storage bucket name

If DVWA Shows Default Nginx Page or 404:

  • Wait 3–5 minutes for the startup script to complete

  • Check startup script logs:

gcloud compute instances
get
-serial-port-output WEB_SERVER_NAME \ --zone=ZONE --project=PROJECT_ID | grep -i
"DVWA"
  • If still not working, recreate the instance:
cd
terraform terraform taint google_compute_instance.web_server terraform apply

**Why DVWA Matters:**DVWA provides a comprehensive platform for learning web application security testing. The information disclosure page (info.php) demonstrates how attackers can discover Cloud Function and Cloud Run URLs without having access to Terraform configuration files. In real-world scenarios, these URLs might be exposed through:

  • Internal documentation pages

  • Developer dashboards

  • API documentation

  • Error messages

  • Configuration files

Article screenshot

Step 3: Verify Storage Bucket

# Get bucket name
BUCKET=$(terraform output -raw vulnerable_bucket_name)
# List bucket contents
gsutil
ls
gs://
$BUCKET
/
# Try to read sensitive file (should work - it's public!)
gsutil
cat
gs://
$BUCKET
/secrets/database-credentials.json

Expected Output:

{

"db_host"
:

"internal-db-xxxx"
,

"db_user"
:

"admin"
,

"db_password"
:

"EXAMPLE_PASSWORD_123!"
,

"api_key"
:

"stripe_live_example_xxxx"
}

Article screenshot

Step 4: Verify Cloud Run Service

# Get Cloud Run URL
CLOUD_RUN_URL=
$(terraform output -raw cloud_run_url)
# Test Cloud Run service
curl $CLOUD_RUN_URL

**Expected:**Should return a response (may be default hello world)

Step 5: Verify Service Account

# Get service account email
SA_EMAIL=
$(terraform output -raw overprivileged_sa_email)
# List service account roles
gcloud projects get-iam-policy
$(terraform output -raw project_id)
\
--flatten=
"bindings[].members"
\
--filter=
"bindings.members:serviceAccount:$SA_EMAIL"
\
--format=
"table(bindings.role)"

Step 6: Verify Network Configuration

# List VPC networks
gcloud

compute

networks

list

|

grep

vulnerable
---
# List subnets
gcloud

compute

networks

subnets

list

|

grep

vulnerable
# List firewall rules
gcloud

compute

firewall-rules

list

|

grep

vulnerable
---

Article screenshot

Storage Bucket Access

# Public URL (if bucket is public)
BUCKET=$(terraform output -raw vulnerable_bucket_name)
echo

"Bucket: gs://
$BUCKET
"
echo

"Public URL: https://storage.googleapis.com/
$BUCKET
/secrets/database-credentials.json"

Service Account Key Access

The service account key is exposed in two locations:

1. Instance Metadata:

# Via web server file read vulnerability
curl
"http://
$(terraform output -raw web_server_ip)
/api.php?file=/tmp/sa-key.json"

2. Secret Manager:

# Get secret name
SECRET=$(terraform output -raw service_account_key_location |
cut
-d: -f2 | xargs)
# Access via gcloud (requires authentication)
gcloud secrets versions access latest --secret=
"
$SECRET
"

Accessing the Lab

DVWA (Damn Vulnerable Web Application)

The lab automatically deploys**DVWA (Damn Vulnerable Web Application)**on the web server. DVWA is a well-known, intentionally vulnerable PHP/MySQL web application designed for security testing and training.

What is DVWA?

DVWA is an intentionally vulnerable web application that provides a legal environment to learn and practice web application security testing. It includes multiple vulnerability categories with different security levels, allowing you to practice various attack techniques safely.

DVWA Vulnerability Categories

DVWA includes 10 major vulnerability categories:

  • SQL Injection— Various SQL injection challenges with different difficulty levels

  • Command Injection— OS command execution vulnerabilities

  • File Inclusion— Local and Remote File Inclusion (LFI/RFI)

  • File Upload— Unsafe file upload vulnerabilities

  • XSS (Cross-Site Scripting)— Reflected, Stored, and DOM-based XSS

  • CSRF (Cross-Site Request Forgery)— CSRF token bypass challenges

  • Brute Force— Weak authentication mechanisms

  • Weak Session IDs— Predictable session token vulnerabilities

  • CSP Bypass— Content Security Policy bypass techniques

  • JavaScript— Client-side security vulnerabilities

Accessing DVWA

DVWA URL:

# Get DVWA URL from Terraform outputs
terraform output dvwa_url
# Output: http://WEB_SERVER_IP/
# Or construct manually
WEB_IP=$(terraform output -raw web_server_ip)
echo

"http://
$WEB_IP
/"

Default Credentials:

  • Username:admin

  • Password:password

First Time Setup:

  • Access DVWA URL in your browser

  • Login with default credentials (admin/password)

  • Click “Create / Reset Database” button if prompted

  • Wait for database initialization

  • Go to “DVWA Security” in the left menu

  • Set security level to “Low” (most vulnerable)

Security Levels:

  • **Low:**Most vulnerable — no security measures (recommended for testing)

  • **Medium:**Some security measures in place

  • **High:**Stronger security measures

  • **Impossible:**Secure implementation (for reference)

Information Disclosure Page

An additionalinfo.phppage exposes Cloud Function and Cloud Run URLs:

Access:

# Get info page URL
terraform output info_page_url
# Output: http://WEB_SERVER_IP/info.php
# Or access directly
curl http://$(terraform output -raw web_server_ip)/info.php

What It Shows:

  • Cloud Function URL with endpoints documentation

  • Cloud Run service URL

  • Storage bucket name

  • Service descriptions

**Purpose:**This simulates a real-world scenario where internal service documentation is publicly accessible, allowing attackers to discover Cloud Function and Cloud Run URLs without having access to Terraform configuration files.

What Gets Deployed Automatically

The Terraform startup script automatically:

  • **Installs:**nginx, PHP 7.4 with FPM, PHP extensions (mysql, gd, curl, xml, mbstring, zip), MariaDB, Git

  • **Clones DVWA:**Downloads from GitHub (https://github.com/digininja/DVWA)

  • **Configures Database:**Sets up MariaDB with DVWA database and user

  • **Pre-configures:**Security level set to “Low” (most vulnerable)

  • Creates Info Page:/var/www/html/info.phpwith service URLs

  • **Sets Permissions:**Configures file permissions for uploads and logs

  • **Exposes Credentials:**Service account key in/tmp/sa-key.json

No manual steps required— everything is deployed automatically when the instance is created.

DVWA Testing Examples

SQL Injection:

User
ID:
1
' OR '
1
'='
1

Command Injection:

IP Address: 127.0.0.1;
cat
/etc/passwd

File Inclusion:

File: ../../../../etc/passwd

XSS:

Name
:
<
script
>
alert
(
'XSS'
)
</
script
>

Troubleshooting DVWA

If DVWA Shows “Database Error”:

  • Wait 2–3 minutes after instance creation

  • Access/setup.phpdirectly to reset database

  • Click “Create / Reset Database” button

  • Check MariaDB is running:systemctl status mariadb

If You See Default Nginx Page:

  • Wait 3–5 minutes for startup script to complete

  • Check startup script logs for “DVWA installation complete”

  • If still not working, recreate the instance:

cd
terraform terraform taint google_compute_instance.web_server terraform apply

Why DVWA Matters:

DVWA provides a comprehensive platform for learning web application security:

  • **Legal Environment:**Practice attacks without legal concerns

  • **Multiple Vulnerabilities:**Learn various attack techniques

  • **Difficulty Levels:**Progress from basic to advanced

  • **Real-World Scenarios:**Simulates common web application vulnerabilities

  • **Educational:**Includes hints and documentation

The vulnerabilities in DVWA are based on real-world web application security issues, helping you understand attack vectors and develop secure coding practices.

Web Server Access

# Get web server IP
WEB_IP=$(terraform output -raw web_server_ip)
# Access via browser
echo

"Open in browser: http://
$WEB_IP
"
echo

"Vulnerable endpoint: http://
$WEB_IP
/api.php?cmd=id"
echo

"Homepage with service URLs: http://
$WEB_IP
/"

SSH Access (if needed)

# SSH into web server
# Get instance name and zone from outputs
WEB_SERVER_NAME
=
$(
terraform output -raw web_server_name)
ZONE
=
$(
terraform output -raw zone)
PROJECT_ID
=
$(
terraform output -raw project_id)
# SSH into the instance (direct connection)
gcloud compute ssh
$WEB_SERVER_NAME
--zone=
$ZONE
--project=
$PROJECT_ID
# Alternative: One-liner
gcloud compute ssh
$(
terraform output -raw web_server_name) \
--zone=
$(
terraform output -raw zone) \
--project=
$(
terraform output -raw project_id)
# If SSH connection times out, use IAP tunneling (recommended)
gcloud compute ssh
$WEB_SERVER_NAME
\
--zone=
$ZONE
\
--project=
$PROJECT_ID
\
--tunnel-through-iap
# Troubleshoot SSH connection issues
gcloud compute ssh
$WEB_SERVER_NAME
\
--zone=
$ZONE
\
--project=
$PROJECT_ID
\
--troubleshoot
# If you need to find the instance name manually:
gcloud compute instances list --filter=
"name:web-server*"

Storage Bucket Access

# Public URL (if bucket is public)
BUCKET=$(terraform output -raw vulnerable_bucket_name)
echo

"Bucket: gs://
$BUCKET
"
echo

"Public URL: https://storage.googleapis.com/
$BUCKET
/secrets/database-credentials.json"

Service Account Key Access

The service account key is exposed in two locations:

1. Instance Metadata:

# Via web server file read vulnerability
curl
"http://
$(terraform output -raw web_server_ip)
/api.php?file=/tmp/sa-key.json"

2. Secret Manager:

# Get secret name
SECRET=$(terraform output -raw service_account_key_location |
cut
-d: -f2 | xargs)
# Access via gcloud (requires authentication)
gcloud secrets versions access latest --secret=
"
$SECRET
"

Troubleshooting

Issue: Terraform Init Fails

Error:Failed to query available provider packages

Solution:

# Check internet connection
ping google.com
# Try with explicit provider source
terraform init -upgrade
# Clear Terraform cache and retry
rm
-rf .terraform
terraform init
Issue: API Not Enabled

Error:Error 403: Compute Engine API has not been used

Solution:

# Enable the API manually
gcloud services
enable
compute.googleapis.com
# Wait 1-2 minutes, then retry
terraform apply

Issue: Quota Exceeded

Error:Quota 'INSTANCES' exceeded

Solution:

# Check current quotas
gcloud compute project-info describe --project=
$PROJECT_ID
# Request quota increase via console:
# https://console.cloud.google.com/iam-admin/quotas

Issue: Billing Not Enabled

Error:Billing account not found

Solution:

# Link billing account
gcloud billing projects
link

$PROJECT_ID
\
--billing-account=$(gcloud billing accounts list --format=
"value(name)"
|
head
-1)
# Verify
gcloud billing projects describe
$PROJECT_ID

Issue: Permission Denied

Error:Permission denied on resource

Solution:

# Verify you have Owner role
gcloud projects get-iam-policy
$PROJECT_ID
\
--flatten=
"bindings[].members"
\
--filter=
"bindings.members:user:
$(gcloud config get-value account)
"
# If not, add Owner role (requires project owner)
gcloud projects add-iam-policy-binding
$PROJECT_ID
\
--member=
"user:
$(gcloud config get-value account)
"
\
--role=
"roles/owner"

Issue: SSH Connection Timeout

Error:ssh: connect to host [IP] port 22: Connection timed out

**Symptoms:**Cannot SSH into compute instances

Solution:

Option 1: Use IAP Tunneling (Recommended)

# SSH using IAP tunneling (works even without public SSH access)
gcloud compute ssh
$(terraform output -raw web_server_name)
\
--zone=
$(terraform output -raw zone)
\
--project=
$(terraform output -raw project_id)
\
--tunnel-through-iap

Option 2: Add SSH Firewall Rule

# Check if SSH firewall rule exists
gcloud compute firewall-rules list | grep allow-ssh
# If missing, apply the updated Terraform configuration
cd
terraform
terraform apply

Option 3: Troubleshoot SSH Issues

# Run SSH troubleshooting
gcloud compute ssh
$(terraform output -raw web_server_name)
\
--zone=
$(terraform output -raw zone)
\
--project=
$(terraform output -raw project_id)
\
--troubleshoot
# Check firewall rules
gcloud compute firewall-rules list --filter=
"name:allow-ssh*"
# Verify instance is running
gcloud compute instances describe
$(terraform output -raw web_server_name)
\
--zone=
$(terraform output -raw zone)
\
--format=
"get(status)"

**Note:**The Terraform configuration includes an SSH firewall rule. If you deployed before this was added, runterraform applyto add it.

Issue: Web Server Not Responding

**Symptoms:**HTTP requests timeout or return errors

Solution:

# Check instance status
WEB_NAME=$(terraform output -raw web_server_name)
ZONE=$(terraform output -raw zone)
gcloud compute instances describe
$WEB_NAME
--zone=
$ZONE
# Check if startup script completed
gcloud compute instances get-serial-port-output
$WEB_NAME
\
--zone=
$ZONE
|
tail
-50
# Check firewall rules
gcloud compute firewall-rules list | grep allow-http
# Wait 3-5 minutes for startup script to complete

Issue: Cloud Function Deployment Fails

Error:Error creating function

Solution:

# Verify function code ZIP was created
ls
-la terraform/function_code.zip
# If missing, create manually
cd
terraform/function_code
zip -r ../function_code.zip .
cd
..
# Retry deployment
terraform apply

Issue: Storage Bucket Creation Fails

Error:Bucket name already exists

Solution:

  • Bucket names must be globally unique

  • Terraform uses random suffix, but if it conflicts:

# Destroy and recreate with new random suffix
terraform destroy
terraform apply

Issue: Terraform State Version Mismatch

Error:Failed to load state: unsupported backend state version 4

**Symptoms:**Terraform cannot read the state file

Solution:

Option 1: Use Manual Cleanup

# Use the manual cleanup script
cd
terraform
./manual_cleanup.sh

Option 2: Fix State File

# Backup current state
cp
terraform.tfstate terraform.tfstate.backup2
# Try to upgrade Terraform
# Download latest version from https://www.terraform.io/downloads
# Or use manual cleanup (see Cleanup section)

Option 3: Delete Resources ManuallySee “Option B: Manual Cleanup” in the Cleanup section.

Getting Help

Check Terraform State:

#
View

current
state
terraform
show
# List
all
resources
terraform state list

View Logs:

# Terraform logs
cat
terraform.tfstate
# Current state
# GCP logs
gcloud logging
read

"resource.type=compute_instance"
--
limit
50

Cleanup

Step 1: Destroy All Resources

⚠️ This will permanently delete all lab resources!

cd
terraform
# Review what will be destroyed
terraform plan -destroy
# Destroy all resources
terraform destroy

Expected Process:

google_compute_instance.web_server:

Destroying...
google_compute_instance.db_server:

Destroying...
google_storage_bucket.vulnerable_bucket:

Destroying...
...
Destroy

complete!

Resources:

20

destroyed.

**Note:**Some resources may take a few minutes to delete.

Step 2: Verify Cleanup

# Verify instances are deleted
gcloud compute instances list
# Verify buckets are deleted
gsutil
ls
# Verify networks are deleted
gcloud compute networks list | grep vulnerable

Article screenshot

Step 3: Delete Project (Optional)

⚠️ This will delete the entire GCP project!

# Delete the project (requires project deletion permission)
gcloud projects
delete
$PROJECT_ID
# Note: Project deletion can take time and is irreversible

**Alternative:**Just leave the project empty (no cost if no resources)

Step 4: Clean Local Files

# Remove Terraform state and cache
cd
terraform
rm
-rf .terraform
rm
-f terraform.tfstate terraform.tfstate.backup
rm
-f tfplan
rm
-f function_code.zip

Cost Management Tips

  • Set Budget Alerts(as shown in setup)

  • Destroy When Not in Use

  • terraform destroy # Stops all costs

3. Monitor Usage

  • gcloud billing accounts list gcloud billing projects describe $PROJECT_ID

4. Use Preemptible Instances(modifymain.tfif desired)

5. Schedule Automatic Shutdown(use Cloud Scheduler)

Additional Resources

Summary Checklist

Use this checklist to ensure complete setup:

  • GCP account created and billing enabled

  • New GCP project created

  • All required APIs enabled

  • gcloud authenticated

  • Terraform installed and verified

  • Project files in place

  • terraform.tfvarsconfigured

  • terraform initcompleted successfully

  • terraform planreviewed

  • terraform applycompleted

  • All outputs saved

  • Web server accessible

  • Storage bucket accessible

  • Cloud Run service accessible

  • Cloud Function accessible

  • Service account verified

  • Budget alerts configured

  • Lab outputs documented

**Congratulations!**🎉 You’ve successfully built a vulnerable cloud pentest lab. Remember to use it responsibly and only for authorized testing.

Fast redeploy after destruction

# 1. Login
gcloud auth login
# 2. Create new project
export
PROJECT_ID=
"cloud-pentest-lab-
$(date +%s)
"
gcloud projects create
$PROJECT_ID
--name=
"Cloud Pentest Lab"
# 3. Set project
gcloud config
set
project
$PROJECT_ID
# 4. Link billing account
gcloud billing accounts list
gcloud billing projects
link

$PROJECT_ID
--billing-account=011607-0196AF-A74C3F
# 5. Enable APIs
gcloud services
enable
cloudresourcemanager.googleapis.com compute.googleapis.com cloudfunctions.googleapis.com run.googleapis.com storage.googleapis.com secretmanager.googleapis.com iam.googleapis.com cloudbuild.googleapis.com container.googleapis.com
# 6. Fix ADC quota project
gcloud auth application-default set-quota-project
$PROJECT_ID
# 7. Update terraform.tfvars
cd
terraform
cat
> terraform.tfvars <<
EOFTF
project_id = "$PROJECT_ID"
region = "us-central1"
zone = "us-central1-a"
EOFTF
# 8. Initialize Terraform
rm
-rf .terraform terraform.tfstate* 2>/dev/null ||
true
terraform init
# 9. Deploy
terraform plan
terraform apply