#7752·nuclei

smb-anonymous-access can never match, and the obvious fix makes it a false positive on any guest-mapping server

Author: stan-threatmateCreated Sep 17, 2026Updated Sep 17, 2026

Summary

Two linked problems in the JS SMB stack, found while investigating a customer-reported false positive. They are filed together because fixing the first without the second makes things worse for users, and the second needs a library primitive that does not exist yet.

  1. smb-anonymous-access can never match, on any server, on current builds.
  2. The obvious one-line fix turns it into a false positive on every guest-mapping SMB server — which is most NAS appliances.

Tested on nuclei v3.11.1, template javascript/misconfiguration/smb/smb-anonymous-access.yaml, against real Samba servers.


Part 1 — the template can never fire

The template asks for an anonymous session the only way a template can:

yaml
args:
  Host: "{{Host}}"
  Port: "445"
  User: " "
  Pass: " "

That single space is trimmed to an empty username before it reaches the wire. In pkg/js/libs/smbsession/path.go, ParseIdentity opens with user = strings.TrimSpace(user) and returns "", "" for a blank string; session.go then falls back to strings.TrimSpace(creds.User), which is empty again:

go
domain, user := ParseIdentity(creds.User)
if creds.Domain != "" { domain = creds.Domain }
if user == "" { user = strings.TrimSpace(creds.User) }

goimpacket then refuses client-side, before anything is sent:

SMB login failed: internal error: Anonymous account is not supported yet. Use guest account instead

Measured against two Samba servers by calling smbsession.Dial directly:

User value result
" " (as shipped) dial fails — "Anonymous account is not supported yet"
"" dial fails — same
"guest" succeeds, enumerates shares

Deterministic and server-independent: it is a client-side refusal, so the template yields nothing anywhere. A scan of five differently-configured Samba hosts produced zero matches for this template.


Part 2 — why User: "guest" is not the fix

Switching the username to guest makes the template run, and then it reports almost everything. Four Samba servers, all enumerated as guest:

server shares guest can enumerate shipped matcher contains(response,"IPC$") matcher ignoring admin shares should report?
map to guest = Bad User, shares restricted to a real user [Backups IPC$ Media] fires fires no
world-readable Public [IPC$ Public] fires fires yes
printer, only spooler shares [IPC$ direct hold lp1 print] fires fires no
no shares published [IPC$] fires no no

Two things are going on:

  • contains(response, "IPC$") cannot discriminate at all. IPC$ is the pipe the enumeration itself travelled over — every successful srvsvc.NetShareEnumAll returns it. Deleting the last real share on a server does not clear the finding.
  • Tightening the matcher to require a non-administrative share only rescues the last row. On the first and third servers the guest session can read the share names but cannot open any of themTREE_CONNECT returns STATUS_ACCESS_DENIED. map to guest = Bad User is the default on a lot of NAS and backup appliances, so this is the common case, not a corner one.

The underlying point: share-name enumeration is not access, and no matcher over a name list can tell the two apart. A template-only fix trades a dead check for a noisy one.

Related: the same gap in smb-default-login

javascript/default-logins/smb-default-login.yaml matches on response != "[]" and success == true. Because that template does not call Export, response reaches the dsl as the value from results.Export() — a Go []string, compared against a string literal, so the clause is true for every slice and never discriminates. The effective gate is success == true alone, which any guest-mapping server satisfies for all nine credential pairs. Same root cause: the check rests on "the enumeration succeeded" rather than "something was reachable".


Suggested direction (your design call)

A correct check needs to demonstrate reachability, not enumeration. That is a library capability rather than a matcher one, so it would be a two-part change:

  • pkg/js/libs/smb — expose something like ListReachableShares(host, port, user, pass) that enumerates, drops administrative shares (IPC$, ADMIN$, C$..Z$, print$) and, for the remainder, performs a TREE_CONNECT and a root listing, returning only the shares that actually opened. smbsession already has everything needed — Session.ListDir does mount-then-list today.
  • the template — use a real username (guest) and match on that reachable list instead of on contains(response, "IPC$").

Optionally, a session that authenticates a random 20-character username is a reliable tell that the server validates nothing, which is what separates "these credentials work" from "this server accepts anything" for smb-default-login.

We have implemented and measured this approach downstream and would be glad to prepare PRs for both repos if you think the direction is right — or to just fix the username if you would rather take the smaller change and accept the false positives. Happy to follow whatever you prefer.

Reproducing

Two Samba configs are enough to see both halves:

ini
# fires, and should: Public is genuinely world-readable
[global]
   map to guest = Bad User
[Public]
   path = /srv/Public
   guest ok = yes
   read only = yes
ini
# fires once the username is fixed, and should NOT: guest can read the
# share names and open neither
[global]
   map to guest = Bad User
[Backups]
   path = /srv/Backups
   valid users = realuser
   guest ok = no

smbclient -L //<host> -U 'RandomUser20Chars%whatever' lists the shares on both; smbclient //<host>/Backups -U 'RandomUser20Chars%whatever' -c ls returns NT_STATUS_ACCESS_DENIED on the second.