Web Server Compromise — From a Public-Facing RCE to a Cobalt Strike Beacon
A three-hour intrusion: a years-old web-application vulnerability, a Cobalt Strike beacon loaded by DLL side-loading, escalation to SYSTEM, dual persistence, and a command-and-control channel dressed up as Microsoft Azure.
This is a sanitized account of a real Managed Detection and Response (MDR) investigation. Account names and hostnames are placeholders; network indicators are replaced with RFC 2606 (example) domains and RFC 5737 (documentation) IP ranges, and file hashes are withheld. The malware behavior, tooling, and analysis are real and unchanged.
Executive Summary
On 18 June 2026, the client's endpoint detection and response (EDR) platform raised an alert for a suspicious .NET module loaded into an IIS web-server process. The client engaged the MDR team, which initiated incident response to determine the scope and impact of the activity. The investigation confirmed that a single host and a single account were in scope.
A threat actor exploited a public-facing IIS web application running a component vulnerable to a known 2019 remote-code-execution flaw (CVE-2019-6714). From that foothold the actor wrote a legitimate, signed Java binary and a malicious companion DLL to disk and used DLL side-loading to run a Cobalt Strike beacon in memory. The actor escalated to SYSTEM, established two independent persistence mechanisms (a Windows service and a WMI event subscription), and beaconed to a command-and-control (C2) server disguised as Microsoft Azure infrastructure, sending roughly 2.27 MB of data outbound. Approximately three hours after initial access, the EDR platform's managed threat-hunting team contained the incident — killing the IIS process tree and quarantining the malicious files — before the loader's full capabilities could be re-established. No further activity was seen afterwards.
Timeline
All times UTC on 18 June 2026, drawn from IIS, EDR process, Windows event, and network telemetry.
| Time | Event |
|---|---|
| 05:36:33–05:36:54 | Repeated successful POST requests to the site root (/) exploit a public-facing IIS application vulnerable to CVE-2019-6714. The load balancer was not logging incoming HTTP requests, so the external source IP and exact exploit URI could not be recovered. |
| 05:36:51 | The IIS worker process (w3wp.exe) writes a legitimate Java binary (javaw.exe) and a malicious companion DLL (jli.dll) to C:\Temp\. jli.dll is a Cobalt Strike beacon loader. |
| 05:37:02 | javaw.exe is executed from C:\Temp\ under the IIS app-pool account prod-web-pool; via DLL side-loading it loads the malicious jli.dll into memory. |
| 05:37:09 | The compromised Java process opens the first of five C2 connections to a domain masquerading as Microsoft Azure. |
| 08:32:25 | A second javaw.exe is launched through WMI (wmiprvse.exe -Embedding) running as NT AUTHORITY\SYSTEM — the actor already holds SYSTEM-level access. |
| 08:34:31 | nssm.exe installs a Windows service named OracleSch pointing at a staged payload, C:\ProgramData\Microsoft\WDF\javaw.exe. |
| 08:53:25–08:53:26 | jdk.mof is compiled via mofcomp.exe; a malicious WMI Event Consumer is registered for reboot persistence (Event ID 5861). |
| 08:55:16 | The actor runs attrib +s +h to hide the payload. Simultaneously, the EDR platform's managed threat-hunting team kills the parent IIS processes and quarantines the malicious files. |
| ~08:55:46 | The final C2 / SSL sessions close; approximately 2.27 MB of data had been sent outbound over the intrusion window. |
Investigation & Analysis
Initial access — a public-facing RCE
Between 05:36:33 and 05:36:54 UTC, IIS logs recorded several successful POST requests to the site root. The affected application pool (prod-web-pool) ran on .NET v4 and used the BlogEngine.NET component, which is vulnerable to remote code execution under CVE-2019-6714 — a flaw disclosed in 2019. Because the load balancer in front of the site was not configured to log incoming HTTP requests, the exact exploit URI and the true external source IP could not be confirmed; the evidence of exploitation came from the IIS request logs and the actions that immediately followed on the host.
Execution & DLL side-loading
At 05:36:51 UTC the IIS worker process, w3wp.exe, wrote two files into C:\Temp\: a legitimate, signed Java binary (javaw.exe) and a DLL named jli.dll. A genuine jli.dll ships with Java, but the copy dropped here had an unrecognized hash. At 05:37:02 UTC javaw.exe was executed from C:\Temp\ under the prod-web-pool app-pool identity.
This is a classic DLL side-loading abuse (T1574.002): when the legitimate Java binary started, it searched its own directory for jli.dll, found the attacker's file with the expected name, and — without verifying its authenticity — loaded the malicious code into the Java process's memory. The DLL was a Cobalt Strike beacon loader, so the beacon ran inside a trusted, signed process rather than as an obviously malicious executable.
Privilege escalation to SYSTEM
By 08:32:25 UTC the actor was operating with NT AUTHORITY\SYSTEM privileges: a second javaw.exe was spawned directly by the WMI Provider Host (wmiprvse.exe -Embedding) as SYSTEM. The timing indicates this was not fired by a persistence trigger but a direct WMI process-creation call (for example Win32_Process.Create) issued by an actor who had already obtained SYSTEM. The specific escalation vector could not be confirmed, again because the missing load-balancer logs left a gap in the earliest stage of the intrusion.
Persistence — a Windows service and a WMI event subscription
The actor built two independent persistence mechanisms so that losing one would not cost them access:
- Windows service (T1543.003). At 08:34:31 UTC the Non-Sucking Service Manager (nssm.exe) was used to install a service named OracleSch — a benign, database-sounding name — configured to run a staged payload at C:\ProgramData\Microsoft\WDF\javaw.exe.
- WMI event subscription (T1546.003). At 08:53:25 UTC a MOF file, C:\Temp\jdk.mof, was compiled with mofcomp.exe; a second later a WMI Event Consumer was registered (visible as Event ID 5861 in the Microsoft-Windows-WMI-Activity/Operational log). The event filter JavaW_Startup_Filter used a WQL query against the Win32_PerfFormattedData_PerfOS_System class, set to fire when system uptime was between 120 and 180 seconds after boot:
SELECT * FROM __InstanceModificationEvent WITHIN 5
WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System'
AND TargetInstance.SystemUpTime >= 120
AND TargetInstance.SystemUpTime < 180
Figure 1 — The WQL event-filter condition (reconstructed) used to trigger the payload shortly after each reboot.
That filter was bound to a CommandLineEventConsumer named JavaW_Startup_Consumer configured to execute C:\Temp\javaw.exe — re-launching the payload on every reboot. Finally, at 08:55:16 UTC the actor ran attrib +s +h C:\Temp\javaw.exe to mark the payload as a hidden system file so it would not appear in a normal File Explorer view.
Command and control
Between 05:37:09 and 08:55:46 UTC the two compromised Java processes opened five TCP connections to a C2 server, alongside additional firewall-permitted SSL sessions. The C2 domain was built to impersonate Microsoft Azure: it was a convincing typosquat of a legitimate Azure cloud domain, it presented a valid, publicly-trusted TLS certificate, and it conditionally redirected ordinary browser traffic to genuine Azure so that casual inspection looked benign. The domain resolved to a Cloudflare-fronted IP address at the time of the incident. Roughly 2.27 MB of data was sent outbound before containment.
Malware Analysis: the jli.dll loader
A copy of jli.dll was analyzed in a controlled environment. It never executed on the host — the EDR platform blocked and quarantined it — so the following describes the loader's intended capabilities, recovered by static analysis. It was a purpose-built loader meant to be side-loaded through Java components, with a notably sophisticated evasion toolkit:
- Anti-analysis & sandbox evasion. It checked the BeingDebugged and ProcessDebugPort flags to detect user-mode debuggers, and timed CPU tick counts across Sleep() calls to detect accelerated execution inside an automated sandbox.
- Direct syscalls (EDR unhooking). To bypass EDR hooks placed in ntdll.dll, it used the “Halo's Gate” technique to resolve System Service Numbers (SSNs) dynamically and invoke system calls directly.
- Privilege escalation. It called RtlAdjustPrivilege to raise the privileges of its execution context.
- In-memory obfuscation. It registered a Vectored Exception Handler paired with hardware breakpoints on Sleep(); on each sleep it would spoof the thread's call stack with legitimate Windows frames and re-encrypt its shellcode in memory — specifically to defeat routine EDR memory scans.
- Staged payload. A Stage-2 position-independent (PIC) payload was decrypted with a 16-byte XOR key (JlI_V2LoAdErKeY!); it resolved the APIs it needed via ROR13 hashing and handed execution to Stage 3 — the full Cobalt Strike beacon (command execution, host fingerprinting, and data exfiltration).
None of this ran on the host: the beacon was quarantined at the DLL-loading stage, which is why the intrusion stalled at persistence and reconnaissance rather than progressing to hands-on-keyboard objectives.
Findings & Root Cause
- Root cause — an unpatched, internet-facing application. The entry point was a public-facing web app running a component with a remote-code-execution vulnerability that had been public since 2019 (CVE-2019-6714). A single missing patch on an exposed service was the intrusion's entire foundation.
- A logging blind spot cost attribution. The load balancer in front of the site was not logging incoming HTTP requests, so the exact exploit URI, the true source IP, and the privilege-escalation vector to SYSTEM could not be confirmed — edge logging is exactly what is needed to reconstruct the start of an intrusion.
- Stealthy, well-resourced tradecraft. The actor ran a Cobalt Strike beacon in memory via DLL side-loading of a signed Java binary, masqueraded throughout (a benign-looking service name, a Microsoft\WDF file path, and a C2 domain impersonating Azure with a valid TLS certificate), and used a loader built with debugger/sandbox checks, direct syscalls, and call-stack spoofing.
- Redundant persistence. Two independent mechanisms — a Windows service and a WMI event subscription — were installed so that removing one would not evict the actor.
- Service-account foothold. The initial execution context was the IIS application-pool identity; escalation to SYSTEM followed. Over-privileged, interactively-capable service accounts widen the blast radius.
- Detection and response worked. Behavioral EDR detection — not signature AV — caught the in-memory beacon, and the managed threat-hunting team contained it (~3 hours after initial access) before the loader's full capability set could be re-established. Scope stayed at one host and one account.
MITRE ATT&CK techniques observed
| Tactic | ID | Technique |
|---|---|---|
| Initial Access | T1190 | Exploit Public-Facing Application (CVE-2019-6714) |
| Execution | T1059 | Command and Scripting Interpreter |
| Execution | T1047 | Windows Management Instrumentation |
| Persistence | T1543.003 | Create or Modify System Process: Windows Service |
| Persistence | T1546.003 | Event Triggered Execution: WMI Event Subscription |
| Defense Evasion | T1574.002 | Hijack Execution Flow: DLL Side-Loading |
| Defense Evasion | T1036 | Masquerading (service name, file path, C2 domain) |
| Defense Evasion | T1564.001 | Hide Artifacts: Hidden Files and Directories |
| Command & Control | T1071.001 | Application Layer Protocol: Web Protocols |
| Command & Control | T1105 | Ingress Tool Transfer |
Containment & Remediation
The following actions were recommended to contain the incident and return the host and account to a trusted state:
- Isolate the affected endpoint from the network immediately to cut the C2 channel and stop further action.
- Rebuild the host from a known-good image. The malware ran in memory and installed multiple persistence mechanisms; manual clean-up or an AV scan cannot be relied on to fully restore integrity.
- Reset credentials for the affected account(s) promptly. Users should pick genuinely new passwords rather than incrementing one character (e.g. Example1! → Example2!), in line with NIST memorized-secret guidance.
- Block the malicious domain and IP address at all appropriate network controls — firewalls, web proxies, DNS, routers, and switches.
Corrective actions
- Patch public-facing web servers and applications. The whole intrusion turned on a single unpatched, internet-facing component; timely patching of exposed services would have closed the entry point.
- Deploy a web application firewall (WAF) in front of public web servers to filter malicious requests and reduce the exposure of vulnerable endpoints.
- Remove interactive and local login rights from service accounts via Active Directory GPO. Service accounts should start services, not log on interactively — which limits how far a compromised app-pool identity can be taken.
- Forward all valuable log sources to the SIEM — especially load-balancer / WAF HTTP access logs, which were the missing piece here. Centralized logs also survive on-host anti-forensics.
- Enforce multi-factor authentication for critical systems, privileged access, and cloud / business services.
Lessons Learned
- Old, unpatched, internet-facing bugs are still how intrusions start. A 2019 CVE on an exposed web app was the entire foothold. Attack-surface management and patching of public services are foundational.
- Log at the edge. The one gap that repeatedly blocked the investigation — source IP, exploit URI, escalation vector — was the load balancer not logging HTTP requests. Turn on access logging at the edge and forward it to the SIEM.
- Side-loading turns trusted binaries into malware carriers. A signed Java binary spawned by w3wp.exe from C:\Temp\, plus nssm.exe service installs, mofcomp.exe compiling a MOF, and WMI event-consumer creation (Event ID 5861), are all high-signal behaviors worth alerting on.
- In-memory C2 needs behavioral and network detection. The beacon never existed as an obvious malicious executable and its C2 was convincingly disguised as Azure; behavioral EDR — not signatures — is what caught it.
- Fast containment limits damage. Managed threat-hunting killed the process tree and quarantined the payload before the loader's full capabilities were realized, holding scope to a single host.
Indicators of Compromise
Indicators are sanitized for publication: the domain uses the RFC 2606 example namespace, the IP uses an RFC 5737 documentation range, and file hashes are withheld. Notation is defanged ([.]). The structure and relationships between indicators reflect the real activity.
File indicators
| File | Path / SHA-256 | Notes |
|---|---|---|
| javaw.exe | C:\Temp\ · C:\ProgramData\Microsoft\WDF\ SHA-256: [withheld] | Legitimate signed Java binary, abused for DLL side-loading |
| jli.dll | C:\Temp\ · C:\ProgramData\Microsoft\WDF\ SHA-256: [withheld] | Malicious companion DLL — Cobalt Strike beacon loader |
| nssm.exe | C:\Windows\System32\ SHA-256: [withheld] | Non-Sucking Service Manager, dropped and used for service persistence |
| jdk.mof | C:\Temp\ SHA-256: N/A | Malicious MOF file used to configure the WMI persistence |
Network indicators
| Indicator | Type | Notes |
|---|---|---|
| events[.]azure-cdn[.]example | Domain | C2 domain; a typosquat of a legitimate Azure cloud domain, backed by a valid TLS certificate and conditional redirection to genuine Azure |
| 198.51.100.53 | IPv4 | Cloudflare-fronted IP address facilitating the C2 communication |