SMB Enumeration
SMB on TCP 139 and 445 is the highest-yield protocol on an internal network: it exposes shares, user lists, password policy, and often the files that end the engagement early. This page is about reading those replies — which lines are findings and which are noise.
Information
NO ACCESS on every share, or a working credentialed session. Read the session
line before the share list — nxc smb prints [+] when the logon succeeded and
adds (Pwn3d!) only when that account is local admin, while
STATUS_LOGON_FAILURE means the credential is wrong and STATUS_ACCESS_DENIED
on the IPC$ connect means the credential is valid but not permitted to enumerate.
Share Enumeration
Share enumeration answers three questions in order: which shares exist, which of them your current
context can read, and which of those contain something that should not be there. The first two are
reconnaissance; only the third is a finding. Null sessions used to answer the first question on any
Windows host, but anonymous SAMR enumeration has been restricted by default since Server 2003 and
RestrictAnonymous closes the rest — a null IPC$ connect may still list the
default shares, but it will not hand you users. So on a 2016+ estate expect the null pass to fail at
the interesting step and plan for a low-privileged account instead.
Null Session Enumeration
# smbclient - list shares (null session)
smbclient -L //TARGET -N
# smbmap - null session
smbmap -H TARGET
# nxc - null session
nxc smb TARGET -u '' -p '' --shares
# enum4linux-ng - comprehensive null session
enum4linux-ng -A TARGET
A share list of IPC$, NETLOGON and SYSVOL is the default and
proves nothing — those exist on every domain member. The null session is a finding only when names
come back instead of blanks: real user accounts out of enum4linux-ng -A, a share you can
list that is not a default, or a readable SYSVOL from an unauthenticated context. If the
run prints Could not initialise session, or every RPC call returns
NT_STATUS_ACCESS_DENIED, the host is hardened: record the state and stop retrying it with
louder flags, because the retries only add noise to the client's SIEM.
Authenticated Enumeration
# smbclient with credentials
smbclient -L //TARGET -U 'domain/user%password'
# smbmap authenticated
smbmap -H TARGET -u user -p password
smbmap -H TARGET -u user -p password -d domain
# nxc authenticated
nxc smb TARGET -u user -p password --shares
# enum4linux-ng authenticated
enum4linux-ng -u user -p password -A TARGET
One valid credential changes everything on this page: RIDs resolve to names, --shares
returns per-share permissions instead of NO ACCESS, and SYSVOL becomes
readable. Use the least-privileged account you already have — a domain user is enough for the share
map, and the difference between what a domain user can read and what an admin can read is itself a
useful measurement. If the only credential you hold is local admin on one host, run the authenticated
pass against that host first to confirm the syntax works before sweeping the subnet.
Connecting to Shares
# Interactive connection
smbclient //TARGET/share -U 'user%password'
# Useful smbclient commands
smb: \> dir
smb: \> cd directory
smb: \> get filename
smb: \> mget *
smb: \> recurse ON
smb: \> prompt OFF
smb: \> mget *
# Mount share locally
sudo mount -t cifs //TARGET/share /mnt/share -o user=user,password=pass
sudo mount -t cifs //TARGET/share /mnt/share -o user=user,password=pass,domain=DOMAIN
An interactive session is for confirming the share is real and navigable, not for bulk collection.
recurse ON followed by mget * will pull gigabytes and leave an unmistakable
access pattern in the file server's logs — enumerate paths first, then download only the specific
files you need to prove the finding. Mounting the share is worth it when you need to search content
with local tools, but it also creates a durable connection that outlives your shell.
Deep Share Enumeration
smbmap Deep Dive
# Recursive listing (depth)
smbmap -H TARGET -u user -p pass -r share --depth 5
# Download files matching pattern
smbmap -H TARGET -u user -p pass -R -A '\.(config|xml|txt|ini|bak)$'
# Search for specific filename patterns
smbmap -H TARGET -u user -p pass -R -A 'password|credential|secret'
# List all readable files recursively
smbmap -H TARGET -u user -p pass -R
Deep enumeration is where a share map becomes a narrative. smbmap -r with a depth limit
gives you directory structure; -R -A filters for names that matter. Rank what comes back
by directory, not by file size: a Finance or HR tree readable by an
unprivileged account is a reportable finding even if every file in it is small, while an
IT$\Software share full of installers is noise. Keep the recursive listing output as
evidence — it is the proof that the permission, not a guess, exposed the data.
nxc Spider
# Spider shares for files
nxc smb TARGET -u user -p pass -M spider_plus
# Spider with specific patterns
nxc smb TARGET -u user -p pass -M spider_plus -o EXCLUDE_DIR=Windows,ProgramData
# Output to JSON for analysis
nxc smb TARGET -u user -p pass -M spider_plus -o OUTPUT=/tmp/spider.json
spider_plus writes a per-host JSON file containing every path, size and timestamp it could
read, which makes it the fastest way to diff shares across the estate: the same
spider_plus run against a hundred hosts shows which one has a share nobody else has. Exclude
Windows and ProgramData or the output is mostly OS files. Treat the JSON as an
index and query it offline; re-running the spider to answer a new question multiplies your log footprint
for no new information.
User & Group Enumeration
enum4linux-ng
# Full enumeration
enum4linux-ng -A TARGET
# Users only
enum4linux-ng -U TARGET
# Groups only
enum4linux-ng -G TARGET
# Shares only
enum4linux-ng -S TARGET
# Password policy
enum4linux-ng -P TARGET
# RID cycling (enumerate users via RID brute force)
enum4linux-ng -R TARGET
This section is where SMB enumeration feeds the rest of the engagement. enum4linux-ng -U -G
gives you the real account list and the membership of every privileged group; getdompwinfo
gives you the password policy. Read the policy before you read the user list, because the lockout
threshold is a safety limit rather than a target: a threshold of 5 with a 30-minute observation window
caps you at four guesses per account per window, and exceeding it locks out real staff. A policy of
"never expires, minimum length 8, no lockout" is the finding that makes credential attacks viable, and
it belongs in the report whether or not you crack anything.
rpcclient
# Connect with credentials
rpcclient -U 'user%password' TARGET
# Null session
rpcclient -U '' -N TARGET
# Interactive commands
rpcclient $> enumdomusers
rpcclient $> enumdomgroups
rpcclient $> queryuser 500
rpcclient $> queryuser administrator
rpcclient $> querygroupmem "Domain Admins"
rpcclient $> getdompwinfo
rpcclient $> lsaenumsid
rpcclient $> lookupnames administrator
rpcclient is the surgical option when enum4linux-ng is too loud or its output
too coarse. queryuser 500 always returns the built-in Administrator; the useful line is
logon count on a named account — zero means it has never been used and is a safe spray
candidate, while a high count with an old pwdLastSet means the password is likely weak.
If enumdomusers returns NT_STATUS_ACCESS_DENIED but
lookupnames works, SAMR is restricted and you should fall back to SID lookups rather than
concluding the host has no accounts.
Impacket Tools
# samrdump - dump SAM info
samrdump.py domain/user:password@TARGET
# lookupsid - enumerate users via SID
lookupsid.py domain/user:password@TARGET
# Brute force RIDs
lookupsid.py domain/user:password@TARGET 20000
RID cycling exists for the case where SAMR enumeration is blocked but SID resolution is not:
lookupsid.py walks the RID range and prints a name for every SID the DC will resolve.
Gaps of roughly 500 usually indicate a deleted or recreated account, and low RIDs in the 1000-range
are the original domain accounts — those are the ones worth looking at first. Every name that resolves
is also a username confirmed valid without a failed logon, which makes this the quietest way to build
a spray list.
Sensitive File Hunting
Hunt for files by what they yield, and stop climbing the tree once you have one of each class:
Groups.xml / cpassword gives a credential immediately, web.config
or appsettings.json gives connection strings and sometimes service credentials,
unattend.xml / sysprep.xml gives a local administrator password,
*.kdbx, *.pem, id_rsa and .git/ give a second
credential store plus history that may contain deleted secrets. The filename list below is a list of
what to search for, not a command — feed those patterns to the spider or smbmap -A from
the previous section.
High-Value Targets
# Files to look for:
web.config # ASP.NET config with connection strings
*.config # Various app configs
unattend.xml # Windows deployment answers
sysprep.xml # Sysprep answers (may contain creds)
Groups.xml # GPP passwords (SYSVOL)
*.kdbx # KeePass databases
*.key, *.pem # SSH/SSL keys
id_rsa # SSH private keys
.git/ # Git repositories
.svn/ # SVN repositories
*.bak, *.old # Backup files
passwords.txt # Self-explanatory
credentials.xml # Jenkins credentials
*.sql # Database dumps
*.mdb, *.accdb # Access databasesGPP Password Extraction
# Check SYSVOL for Groups.xml (GPP passwords)
smbclient //DC/SYSVOL -U 'user%pass' -c 'recurse;ls' | grep -i groups.xml
# Download and decrypt
smbmap -H DC -u user -p pass -R SYSVOL -A Groups.xml
# Decrypt GPP password
gpp-decrypt "encrypted_password_here"
# Automated with nxc
nxc smb DC -u user -p pass -M gpp_password
GPP passwords are still the single most common quick win on an internal test. The cpassword
value in a GPP preferences file is AES-encrypted with a key Microsoft published in 2012, so recovery is
instant rather than a crack — gpp-decrypt prints the cleartext. Crucially, the file lives
in SYSVOL, which every authenticated domain user can read, so the finding is domain-wide:
the password is usable by anyone with any account, and it is often a local administrator password
reused across the estate. Check whether the GPO is still linked and the password still current before
writing it up, and record the file's full path as evidence.
SMB Vulnerabilities
These three checks answer different questions and only one of them is about exploitation. Signing is a configuration fact that matters because of what it enables elsewhere. MS17-010 is an unpatched-host question, and the probe is safe while the exploit is not. PrintNightmare here is only a spooler-presence check. Treat none of them as a vulnerability in the report until you can state the consequence for this estate.
SMB Signing Check
# Check SMB signing (required for relay attacks)
nxc smb TARGET --gen-relay-list relay.txt
# Nmap script
nmap -p 445 --script smb-security-mode TARGET
# Output shows:
# message_signing: disabled (vulnerable to relay)
# message_signing: required (not vulnerable)
message_signing: disabled is not a finding on its own — it is an ingredient. It becomes
one when you can pair it with a coercion primitive or a broadcast name-resolution weakness, which is
the relay work on the Relay Attacks page. Collect it per host with
--gen-relay-list and keep the file, because the list of unsigned hosts is exactly the
target list that page needs. Note the version boundary: from Windows 11 24H2 and Server 2025, SMB
signing is required by default for both inbound and outbound connections, so treat an unsigned host
as evidence of an older build rather than of a deliberate policy.
EternalBlue (MS17-010)
# Check for MS17-010
nmap -p 445 --script smb-vuln-ms17-010 TARGET
# nxc check
nxc smb TARGET -u '' -p '' -M ms17-010
MS17-010 is worth checking only because an unpatched host is a guaranteed SYSTEM shell; the script is
a safe probe, the public exploit is not, and it bluescreens vulnerable machines often enough that it
needs a named, time-boxed approval in the rules of engagement. Read the script's categories rather than
a single word: VULNERABLE means the target answered the crafted request in the way only an
unpatched build does, while LIKELY VULNERABLE from the older smb-vuln-ms17-010
heuristics is not proof. Confirm the cumulative update level from the host inventory first — on any
estate patched after 2017 this check returns clean and the finding is documentation only.
PrintNightmare Check
# Check if Print Spooler is running
rpcdump.py TARGET | grep -i spooler
# nxc module
nxc smb TARGET -u user -p pass -M printnightmare
A reachable spooler is only half of PrintNightmare — the other half is the patch state of the
Point and Print policy and whether the driver signing requirement is enforced. The
rpcdump.py hit tells you the interface is exposed; the printnightmare module
tells you whether the host still accepts a remote driver path. Record both, and do not run the exploit
against a print server that production depends on without a rollback plan: a failed attempt can leave
the spooler stopped.
Mass SMB Enumeration
Danger
--continue-on-success run does not respect it. Agree the account, the range
and the attempt budget with the client first, and keep bulk file collection out of the run — a recursive
mget leaves an access pattern that outlives the engagement.
# Enumerate entire subnet
nxc smb 10.10.10.0/24 -u user -p pass --shares
# Find writable shares
nxc smb 10.10.10.0/24 -u user -p pass --shares | grep -i write
# Password spray across SMB
nxc smb targets.txt -u users.txt -p 'Password123!' --continue-on-success
# Export results
nxc smb 10.10.10.0/24 -u user -p pass --shares --export shares.json
Sweep with one credential at a time and read the result column, not the share table. A successful
nxc smb row shows the host, the domain, the OS build, the signing state and — the part
that matters — (Pwn3d!) when that credential is local admin. That output is your lateral
movement map: hosts where the credential is admin are the ones worth touching, everything else is
inventory. Do not make SMB the first protocol you spray across a large subnet. Failed NTLM logons
trip account lockout, and on 24H2 / Server 2025 the SMB authentication rate limiter (2 seconds per
failed attempt, enabled by default) turns a wide spray into a crawl that also looks exactly like an
attack in the logs.
What the Defender Sees
Everything on this page is visible from the domain controller and the file server, which is why the share map is a good place to agree detection coverage with the blue team before you go further:
- 4624 (logon type 3): one event per successful SMB logon, with the account and source IP. A sweep appears as the same account authenticating to hundreds of hosts in minutes.
- 4625 and 4771: failed network logons and Kerberos pre-auth failures — the signature of a spray, and the reason the lockout threshold matters.
- 5140 / 5145: share access and per-file access on the file server, including the share name and the object opened. Bulk spidering is visible here even when the logon succeeded.
- 4768 / 4769: a TGT or service ticket issued to the account you authenticated with — the DC-side record of the same session.
- Anonymity: null-session attempts appear as anonymous logons or as nothing at all if anonymous auditing is off, which is why a failed null pass is not proof the host is safe.
Quick Reference
| Task | Tool | Command |
|---|---|---|
| List shares | smbmap | smbmap -H TARGET -u user -p pass |
| Full enum | enum4linux-ng | enum4linux-ng -A TARGET |
| Spider files | nxc | nxc smb TARGET -M spider_plus |
| GPP passwords | nxc | nxc smb DC -M gpp_password |
| Signing check | nxc | nxc smb TARGET --gen-relay-list |
Evidence Collection
SMB findings are permission findings, so the evidence has to show the permission rather than the file. Capture the listing that proves what an account could read.
Share map:
Per host: share name, comment, read/write flag and the account used. smbmap or nxc --shares output, saved per run rather than summarised in a screenshot.
Permission proof: The recursive listing of the directory that should not be readable from an unprivileged context, plus one file opened as proof. Path, file count and account, not the file contents.
Session states:
The logon result per attempt: null accepted, credential valid but NO ACCESS, or authenticated with rights. That distinction is what separates "hardened" from "misconfigured".
Password policy:
The getdompwinfo output — lockout threshold, observation window, minimum length — because it bounds every credential attack in the report.
GPP password:
The Groups.xml path in SYSVOL, the GPO it belongs to, the decrypted value and whether the GPO is still linked and applied.
False Positive Identification
-
Default shares:
IPC$,NETLOGONandSYSVOLexist on every domain member. Their presence is not a finding; a readable Finance tree is. - A null session that only lists: Anonymous access that returns share names but refuses to open them is a hardening note, not an exposure. Record the read result for at least one share before calling it.
-
An empty spider:
spider_pluswithEXCLUDE_DIRset too wide returns a clean JSON that means nothing. Check the file count against the share size before reporting "no sensitive files". - Signing disabled on its own: An unsigned host is an ingredient, not a vulnerability. It becomes a finding when paired with a coercion primitive or a broadcast name-resolution weakness — say which, or downgrade it.
Testing Checklist
Every SMB claim on this page is a permission claim, so the test is always the same question: could this account actually open it?
Confirm the access
- ☐ The null session either lists shares or is refused; record which, per host, with the exact command.
- ☐ A share is called readable only after a file in it was opened as the low-privilege account.
- ☐
smbmap -Rornxc --sharesshows READ and WRITE per share - a WRITE share is a delivery path, not just a finding. - ☐
nmap --script smb2-security-modereports signing "not required" on the host you plan to relay to, with its build number next to it.
Confirm what it means
- ☐
IPC$,NETLOGONandSYSVOLare recorded but not reported as exposure; a non-default readable share is the finding. - ☐ The spider's file count is compared against the share size before "no sensitive files" is written.
- ☐ A
cpasswordis decrypted, tested as a credential, and tied to a GPO that is still linked. - ☐ EternalBlue and PrintNightmare checks are run read-only first; only a confirmed result escalates to exploitation.
Remediation & Defense
SMB exposure is fixed with permissions and protocol settings, in that order of impact.
Shares and permissions
- • Remove <code>Everyone</code> and <code>Authenticated Users</code> from non-default shares; grant by group
- • Review share and NTFS permissions together — the effective right is the stricter of the two
- • Delete <code>cpassword</code> entries from GPP and rotate every password they contained
Protocol and policy
- • Require SMB signing (default on 24H2 and Server 2025) and disable SMBv1 and guest access
- • Restrict anonymous SAMR and null-session enumeration through <code>RestrictAnonymous</code>
- • Set a lockout threshold and a short observation window so a spray is both slow and visible
Detection
- • Alert on one account authenticating to many hosts in a short window
- • Monitor 5140/5145 on file servers for bulk recursive reads outside business hours
- • Watch for new local Administrators membership and for GPP file access by non-admin accounts