Impacket port of Snaffler.
snaffler-ng is a post-exploitation tool that discovers readable SMB shares, walks directory trees, and identifies credentials and sensitive data on Windows networks. Also scans FTP servers, local filesystems, and works as a Python library for C2 integration.
pip install snaffler-ng
# or
pipx install snaffler-ngPre-built binaries (no Python required) are available on the Releases page for Linux x86_64, Linux aarch64, and Windows x86_64. Debian/Kali: sudo dpkg -i snaffler-ng_*.deb.
Optional extras:
pip install snaffler-ng[socks] # SOCKS proxy support
pip install snaffler-ng[web] # live web dashboard
pip install snaffler-ng[7z,rar] # 7z/RAR archive peeking# Full domain discovery — finds computers, resolves DNS, enumerates shares, scans everything
snaffler -u USER -p PASS -d DOMAIN.LOCAL
# Kerberos with ccache
snaffler -k --use-kcache -d DOMAIN.LOCAL --dc-host CORP-DC02
# Scan specific UNC paths
snaffler -u USER -p PASS --unc //10.0.0.5/Share --unc //10.0.0.6/Data
# Scan specific computers (share discovery enabled)
snaffler -u USER -p PASS --computer 10.0.0.5 --computer 10.0.0.6
# Local filesystem (no auth needed)
snaffler --local-fs /mnt/share
# FTP server (anonymous)
snaffler --ftp ftp://10.0.0.5
# Fast mode — skip time-waster directories, interleave share walking
snaffler -u USER -p PASS -d DOMAIN.LOCAL --fastQueries AD for computers + DFS namespaces, resolves DNS, probes port 445, enumerates shares, then scans:
snaffler -u USER -p PASS -d DOMAIN.LOCAL
snaffler -u USER -p PASS -d DOMAIN.LOCAL --max-hosts 50 # cap at 50 hosts
snaffler -u USER -p PASS -d DOMAIN.LOCAL --shares-only # enumerate shares without scanning
snaffler -u USER -p PASS -d DOMAIN.LOCAL --include-disabled # include disabled/stale accountsSkip LDAP discovery, target specific hosts. Supports hostnames, IPs, CIDR ranges, and IP ranges:
snaffler -u USER -p PASS --computer 10.0.0.5 --computer 10.0.0.6
snaffler -u USER -p PASS --computer 10.0.0.0/24
snaffler -u USER -p PASS --computer-file targets.txtSkip share discovery, scan specific paths directly:
snaffler -u USER -p PASS --unc //10.0.0.5/Share --unc //10.0.0.6/ITnxc smb 10.0.0.0/24 -u user -p pass --shares | snaffler -u user -p pass --stdinSame classification engine, all 106 rules, content scanning, resume, and download:
snaffler --ftp ftp://10.0.0.5 # anonymous
snaffler --ftp ftp://10.0.0.5/Data -u ftpuser -p ftppass # with creds + subpath
snaffler --ftp ftp://10.0.0.5:2121 --ftp-tls # custom port + TLS
snaffler --ftp-file ftp_targets.txt -u ftpuser -p ftppass # load from fileBare hostnames accepted: --ftp 10.0.0.5 becomes ftp://10.0.0.5. Without -u/-p, anonymous login is attempted.
No network, no auth -- useful for mounted shares, extracted filesystems, or testing rules:
snaffler --local-fs /mnt/share
snaffler --local-fs /tmp/extracted --local-fs /home/user/DocumentsRe-test previously access-denied shares with new credentials -- useful after password spraying:
# Initial scan with low-privilege creds
snaffler -u lowpriv -p 'Password1' -d CORP.LOCAL --state scan.db
# Later, with higher-privilege creds
snaffler --rescan-unreadable -u highpriv -p 'NewPass!' --state scan.dbThe initial scan stores all discovered shares (readable and unreadable) in the state DB. --rescan-unreadable loads only the previously denied shares, re-tests them with current credentials, and scans any that are now accessible. Respects --share, --exclude-share, and --exclusions filters.
Download specific files without scanning. Pipe file paths from snaffler results --files or provide them manually:
# List finding paths, then download them
snaffler results --files | snaffler -u USER -p PASS --grab -m ./loot
# Download from a file list
cat paths.txt | snaffler -u USER -p PASS --grab -m ./loot# Only scan specific shares
snaffler ... --share "SYSVOL" --share "IT*"
# Exclude shares
snaffler ... --exclude-share "IPC$" --exclude-share "print$"
# Exclude paths (glob, works with all modes)
snaffler ... --exclude-path "*/Windows/*" --exclude-path "*/.snapshot/*"
# Limit directory recursion depth
snaffler ... --max-depth 5
# Regex post-filter on findings (matches path, rule name, content)
snaffler ... --match "password|connectionstring"
# Skip specific hosts
snaffler ... --exclusions hosts_to_skip.txt
# Stop after N hosts
snaffler ... --max-hosts 50
# Minimum severity (0=all, 1=Yellow+, 2=Red+, 3=Black only)
snaffler ... -b 2Three output formats: plain (default), JSON, TSV. Auto-detected from -o file extension:
snaffler ... -o findings.json # JSON
snaffler ... -o findings.tsv # TSV
snaffler ... -o findings.txt # plain
snaffler ... -o out.log -t json # explicit overrideScan state is tracked in SQLite (snaffler.db). Scans auto-resume when the DB exists:
snaffler -u USER -p PASS -d DOMAIN.LOCAL # creates snaffler.db
# interrupted? re-run the same command — picks up where it left off
snaffler -u USER -p PASS -d DOMAIN.LOCAL # resumes
snaffler ... --state /tmp/scan1.db # custom DB path
snaffler ... --fresh # ignore existing stateProgressive deepening works across resumes: directories beyond --max-depth are stored but not walked. Re-running with a higher depth walks them automatically, skipping already-scanned files.
snaffler results # plain text summary
snaffler results -f json # JSON
snaffler results -f html > report.html # self-contained HTML report
snaffler results -b 2 # Red+ severity only
snaffler results -r RuleName # filter by rule name
snaffler results -s /path/to/snaffler.db # custom DB path
snaffler results --files # one file path per line (pipe into --grab)The HTML report includes resizable columns, host filtering, inline severity/rule dropdowns, and a connect command copy button.
See which rules matched and how many findings each produced:
snaffler results rules # plain text
snaffler results rules -f json # JSONShare results with teammates or merge findings from parallel scans:
# Export — portable DB or JSON
snaffler results export scan-results.db
snaffler results export findings.json
# Import — merge into your local state DB
snaffler results import teammate-scan.db
snaffler results import findings.json
# Export from a specific state DB
snaffler results export -s /path/to/scan.db report.json
# Import into a specific state DB
snaffler results import -s /path/to/combined.db other-scan.dbFormat is auto-detected from the file extension (.db or .json), or override with -f.
Live browser dashboard for monitoring scan progress and findings:
snaffler ... --web --web-port 8080Requires pip install snaffler-ng[web].
Scans filenames inside ZIP, 7z, and RAR archives without extraction:
pip install snaffler-ng[7z,rar] # ZIP works out of the box| Flag | Description |
|---|---|
-u / -p |
NTLM username/password |
--hash |
NTLM pass-the-hash |
-k |
Kerberos authentication |
--use-kcache |
Kerberos via existing ccache (KRB5CCNAME) |
--socks |
SOCKS proxy pivoting (socks5://127.0.0.1:1080) |
--nameserver / --ns |
Custom DNS server (uses TCP, works through SOCKS) |
--dc-host |
Domain controller hostname or IP |
--stealth |
OPSEC mode: pad LDAP queries to break IDS signatures |
# SOCKS + custom DNS through tunnel
snaffler -u USER -p PASS -d DOMAIN.LOCAL \
--socks socks5://127.0.0.1:1080 --ns 192.168.201.11 --dc-host 192.168.201.11During a scan, press d for DEBUG output, i to switch back to INFO.
Skips 30 known time-waster directories (Windows internals, package caches, VCS metadata, build artifacts) and enables fair-share thread scheduling so one deep share cannot monopolize all workers:
snaffler -u USER -p PASS -d DOMAIN.LOCAL --fastSensitive paths like Windows\Panther (contains unattend.xml with credentials) are deliberately not excluded.
snaffler ... --max-threads 90 # total worker threads (default: 60)
snaffler ... --dns-threads 200 # DNS + port probe threads (default: 100)
snaffler ... --max-threads-per-share 5 # cap tree-walk threads per share (--fast auto-sets)Threads are split equally across share discovery, tree walking, and file scanning. After share discovery completes, idle threads are rebalanced to file scanning.
from snaffler import Snaffler
for finding in Snaffler().walk("/mnt/share"):
print(f"[{finding.triage.label}] {finding.file_path}")
if finding.match:
print(f" matched: {finding.match}")Minimize beacon traffic -- most files are skipped at phase 1 (metadata-only, zero I/O):
from snaffler import Snaffler, FileCheckStatus
s = Snaffler()
# Phase 1: metadata only — instant, no file read
check = s.check_file(path, size=4096, mtime_epoch=1700000000.0)
if check.status == FileCheckStatus.NEEDS_CONTENT:
# Phase 2: only download + classify when needed
result = s.scan_content(file_bytes, prior=check)
elif check.status == FileCheckStatus.MATCHED:
result = check.result # matched on filename alone (e.g. ntds.dit)Plug in any transport -- no ABC required, just implement walk_directory and read:
class BeaconWalker:
def walk_directory(self, path, on_file=None, on_dir=None, cancel=None):
for entry in beacon.ls(path):
if entry.is_dir:
if on_dir: on_dir(entry.path)
elif on_file:
on_file(entry.path, entry.size, entry.mtime)
return [e.path for e in beacon.ls(path) if e.is_dir]
class BeaconReader:
def read(self, path, max_bytes=None):
return beacon.download(path, max_bytes)
s = Snaffler(walker=BeaconWalker(), reader=BeaconReader())
for finding in s.walk("C:\\Users"):
beacon.report(finding.file_path, finding.triage.label)| Parameter | Default | Description |
|---|---|---|
walker |
LocalTreeWalker() |
Directory listing provider |
reader |
LocalFileAccessor() |
File content reader |
rule_dir |
None |
Custom TOML rules directory |
min_interest |
0 |
Minimum severity (0=all, 3=Black only) |
max_read_bytes |
2MB |
Content scan byte limit |
match_context_bytes |
200 |
Context bytes around regex matches |
cert_passwords |
built-in list | Passwords to try on PKCS12 certs |
exclude_unc |
None |
Glob patterns to skip directories |
match_filter |
None |
Regex post-filter on findings |
max_depth |
None |
Maximum directory recursion depth |
Write TOML rules to extend or replace the built-in 106-rule set:
snaffler ... --rule-dir /path/to/rules/See snaffler/rules/example_custom_rule.toml for the format.
