17 min read

Enumeration

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

Every attempt lands in one of three states: a null session accepted, a session that authenticates but returns 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

null-session.sh
# 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

authenticated-share-enum.sh
# 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

smbclient-session.sh
# 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

smbmap-deep.sh
# 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

nxc-spider.sh
# 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

enum4linux-ng.sh
# 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

rpcclient.sh
# 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

impacket-samrdump.sh
# 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

target-file-names.txt
# 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 databases

GPP Password Extraction

gpp-password.sh
# 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

smb-signing.sh
# 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)

ms17-010.sh
# 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

printnightmare.sh
# 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

A subnet-wide authenticated sweep is a different authorisation from a single-host check. It writes a 4624 to every host in the range, and the spray at the bottom of the block below can lock out real staff: with a threshold of 5 and a 30-minute window, four attempts per account per window is the ceiling, and a --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.
mass-smb-enum.sh
# 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$, NETLOGON and SYSVOL exist 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_plus with EXCLUDE_DIR set 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 -R or nxc --shares shows READ and WRITE per share - a WRITE share is a delivery path, not just a finding.
  • ☐ nmap --script smb2-security-mode reports signing "not required" on the host you plan to relay to, with its build number next to it.

Confirm what it means

  • ☐ IPC$, NETLOGON and SYSVOL are 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 cpassword is 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