Skip to content

Fix anonymous share enumeration + JSON Lines output (shares, files, content matches) + Py3.14 console fix#96

Open
ocervell wants to merge 7 commits into
blacklanternsecurity:masterfrom
freelabz:feature/anon-share-enum-and-json-output
Open

Fix anonymous share enumeration + JSON Lines output (shares, files, content matches) + Py3.14 console fix#96
ocervell wants to merge 7 commits into
blacklanternsecurity:masterfrom
freelabz:feature/anon-share-enum-and-json-output

Conversation

@ocervell

@ocervell ocervell commented Jun 16, 2026

Copy link
Copy Markdown

Summary

A set of related fixes and features discovered while running manspider against a Samba host where smbclient -L //host -N listed shares fine but manspider returned nothing.

1. fix(smb): use anonymous null session before Guest for share enumeration

When no credentials are supplied, login() switched to the Guest account and only fell back to a true null session if Guest failed. On Samba with map to guest = Bad User, the Guest login succeeds, but the SRVSVC NetrShareEnumAll RPC (listShares) is denied with STATUS_ACCESS_DENIED — so share enumeration silently returned nothing even though smbclient -L -N works. Now a true anonymous null session is attempted first, matching smbclient -N.

Verified directly with impacket against the same host:

NULL session ("")  : OK   -> ['myshare', 'IPC$']
Guest ("Guest","") : FAIL -> STATUS_ACCESS_DENIED

2. fix(logging): force the fork multiprocessing start method

Python 3.14 changed the default start method on Linux to forkserver, which re-imports modules in the worker process and creates a new, unlistened log queue — so no console output appeared at all (only the file log). Forcing fork restores the shared queue between the parent's QueueListener and the worker's QueueHandler. Guarded with try/except so it's a no-op on older Pythons and degrades safely where fork is unavailable.

3. feat(cli): list shares when no filter is given

When none of --filenames/--content/--extensions/--exclude-extensions are specified, manspider now enumerates and prints the shares for each remote target (like smbclient -L) instead of erroring out.

4. feat: always report enumerated shares while spidering

Previously the share list was only shown in no-filter mode. The enumerated shares are now reported once per target at INFO level (and emitted to JSON) regardless of whether filters are active.

5. feat(output): --json [FILE] JSON Lines output

Adds machine-readable output. --json FILE appends JSON Lines to a file; --json with no argument writes JSONL to stdout and redirects human-readable logs to stderr (so stdout stays clean for piping).

Three record types, each carrying a consistent target + port:

{"type": "share", "target": "localhost", "port": 445, "share": "myshare"}
{"type": "file", "target": "localhost", "port": 445, "share": "myshare", "path": "sample.cfg", "size": 10, "downloaded": false}
{"type": "content_match", "target": "localhost", "port": 445, "share": "myshare", "path": "creds.cfg", "pattern": "password=\\S+", "count": 1, "matches": [{"match": "password=Sup3rS3cret!", "line": 2, "column": 1, "end_column": 22}]}

content_match records include a matches array with the actual matched substrings and where they were found — each entry is {"match": ..., "line": N, "column": N, "end_column": N} (1-based). Samples are deduped and capped at 20 distinct matches per pattern per file (each truncated to 256 chars). The substrings and locations are recovered from the regex span against the already-extracted text, so no extra scanning is performed.

Testing

All behaviours verified end-to-end against a local Samba container (myshare, anonymous read-write): null-session enumeration, console output on Python 3.14.4, no-filter share listing, share reporting while spidering, and --json in both stdout and file modes including captured match strings with line/column locations.

🤖 Generated with Claude Code

ocervell and others added 2 commits June 15, 2026 18:53
When no credentials are supplied, login() switched to the "Guest" account
and only fell back to a true null session if Guest *failed*. On Samba with
`map to guest = Bad User`, the Guest login succeeds but the SRVSVC
NetrShareEnumAll RPC (listShares) is denied (STATUS_ACCESS_DENIED), so share
enumeration silently returned nothing even though `smbclient -L -N` works.

Prefer a true anonymous null session first, matching smbclient -N behaviour.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- feat(output): add `--json [FILE]` to emit results as JSON Lines (share,
  file, content_match records). With no FILE the JSONL goes to stdout and
  human-readable logs are redirected to stderr; with a FILE it is appended.
- feat(cli): when no content/filename/extension filter is given, enumerate and
  print shares for each remote target (like `smbclient -L`) instead of erroring.
- fix(logging): force the "fork" multiprocessing start method. Python 3.14
  changed the Linux default to "forkserver", which re-imports modules in the
  worker and creates a new, unlistened log queue, so no console output appeared
  (only the log file). Forcing fork restores the shared queue; guarded so it is
  a no-op on older Pythons and degrades safely where fork is unavailable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@ocervell

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

bls-cla-bot Bot added a commit to blacklanternsecurity/CLA that referenced this pull request Jun 16, 2026
@ocervell

Copy link
Copy Markdown
Author

recheck

ocervell and others added 3 commits June 16, 2026 10:16
Previously the share list was only printed in no-filter mode. When a filter
was supplied, shares were enumerated but only logged at debug level. Now the
enumerated shares are reported once per target at INFO and emitted as JSON
share records regardless of whether filters are active.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make all JSON Lines records consistent: share, file, and content_match now
each carry explicit "target" and "port" fields (plus share/path). The
content_match record is emitted from the spiderling instead of the parser so
the target/port/share context is available; the file record now uses the
host/port directly rather than str(Target).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
content_match records now carry a "matches" array with the exact substrings
that matched each pattern (deduped, capped at 20 distinct samples per pattern
per file, each truncated to 256 chars). The matched substrings are recovered
from the regex span against the extracted text, so no extra scanning is needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ocervell ocervell changed the title Fix anonymous share enumeration + add JSON Lines output, no-filter share listing, and Py3.14 console fix Fix anonymous share enumeration + JSON Lines output (shares, files, content matches) + Py3.14 console fix Jun 16, 2026
@ocervell

ocervell commented Jun 16, 2026

Copy link
Copy Markdown
Author

Here is an example of the manspider output with --json flag (JSON lines output) after this PR:

manspider localhost --loot-dir . -v --json --extensions cfg --content password               
[+] MANSPIDER command executed: /home/jahmyst/.local/bin/manspider localhost --loot-dir . -v --json --extensions cfg --content password
[+] Skipping files larger than 10.00MB
[+] Using 5 threads
[+] Searching by file extension: ".cfg"
[+] Searching by file content: "password"
[+] Matching files will be downloaded to .
[*] localhost: Trying null session
[*] localhost (localhost): Authenticating as ""
[+] localhost: Successful login as ""
[*] localhost: Response length: 2
[*] localhost: Share 0: name='myshare', type=0, comment=''
[*] localhost: Share 1: name='IPC$', type=2147483651, comment='IPC Service (Samba Server Version 4.6.3)'
[+] localhost: 2 shares: myshare, IPC$
{"type": "share", "target": "localhost", "port": 445, "share": "myshare"}
{"type": "share", "target": "localhost", "port": 445, "share": "IPC$"}
[*] localhost: myshare: contains 3 items
[*] localhost: Skipping file mstest.txt, does not match extension filters
[*] localhost: Skipping myshare\mstest.txt: filename/extensions do not match
[*] localhost: sample.cfg matches extension filters
[*] localhost: Trying null session
[*] localhost (localhost): Authenticating as ""
[+] localhost: Successful login as ""
[*] localhost: Downloading myshare\sample.cfg
[*] localhost: creds.cfg matches extension filters
[*] localhost: Downloading myshare\creds.cfg
[*] Parsing file: localhost\myshare\sample.cfg
[*] Extracted text from localhost\myshare\sample.cfg using charset-normalizer
[*] localhost: Skipping blacklisted share: IPC$
[+] Finished spidering localhost
[*] Parsing file: localhost\myshare\creds.cfg
[*] Extracted text from localhost\myshare\creds.cfg using charset-normalizer
[+] localhost\myshare\creds.cfg: matched "password" 1 times
[+] password=Sup3rS3cret!
{"type": "content_match", "target": "localhost", "port": 445, "share": "myshare", "path": "creds.cfg", "pattern": "password", "count": 1, "matches": [{"match": "password", "line": 2, "column": 1, "end_column": 9}]}

JSON lines are emitted to stdout (so we can parse them with jq pipe them to other tools like notify) and the rest to sderr only when the --json flag is set.

ocervell and others added 2 commits June 16, 2026 11:18
Each entry in a content_match record's "matches" array is now an object with
the matched string plus its 1-based location in the extracted text:
{"match": ..., "line": N, "column": N, "end_column": N}. Locations are
derived from the regex span, so no extra scanning is required.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`--extensions "ini cfg"` (a single quoted argument) now behaves the same as
`--extensions ini cfg` (two arguments). Previously the quoted form became the
single extension ".ini cfg", causing the search to match nothing. Each value
is now split on whitespace before normalization.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant