Skip to content

SMB 3.1.1: guest sessions still get signed, TREE_CONNECT then fails with STATUS_ACCESS_DENIED #2277

Description

@hilarex

I ran into something I can't quite explain and I'd rather ask before assuming it's a bug, since the code looks deliberate and I may well be missing context.

Against a server that allows guest access, a guest session works fine up to dialect 3.0 but fails at 3.1.1: the session setup succeeds and the server marks the session as guest, then the very first TREE_CONNECT (to IPC$) comes back STATUS_ACCESS_DENIED.
Since 3.1.1 is part of the default dialect list now, callers that don't pass preferredDialect land on that path, so listShares() and friends fail for guest where they used to work.

The same server serves the same share to smbclient -N as guest without complaint, and its log says the problem is a signature mismatch rather than a permissions decision, which is what makes me think the client is signing something it shouldn't, rather than the server being unhappy about access.

Configuration

  • impacket v0.14.0.dev0+20260828.120813.032dfb1b (current master)
  • Python 3.14, Linux
  • Target: stock Samba 4.17.12 from debian:bookworm-slim, no patches, config below

Reproducer

Fully self-contained, a stock Debian Samba container with one guest-readable share.

Dockerfile:

FROM debian:bookworm-slim
RUN apt-get update \
 && apt-get install -y --no-install-recommends samba smbclient \
 && rm -rf /var/lib/apt/lists/*
RUN mkdir -p /srv/public /run/samba /var/lib/samba/private \
 && echo "hello" > /srv/public/readme.txt
COPY smb.conf /etc/samba/smb.conf
CMD ["smbd", "--foreground", "--no-process-group", "--debug-stdout"]

smb.conf, the only notable settings are map to guest and guest ok:

[global]
    workgroup = WORKGROUP
    netbios name = REPRO
    server string = samba guest repro
    security = user
    map to guest = Bad User
    guest account = nobody
    log level = 1
    load printers = no
    printing = bsd
    printcap name = /dev/null
    disable spoolss = yes

[public]
    path = /srv/public
    browseable = yes
    read only = yes
    guest ok = yes

repro.py:

import sys
from impacket import version
from impacket.smbconnection import SMBConnection
from impacket.smb3structs import (
    SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30, SMB2_DIALECT_311,
)

TARGET = sys.argv[1]

CASES = [
    ("guest", "", "2.0.2", SMB2_DIALECT_002),
    ("guest", "", "2.1", SMB2_DIALECT_21),
    ("guest", "", "3.0", SMB2_DIALECT_30),
    ("guest", "", "3.1.1", SMB2_DIALECT_311),
    ("guest", "", "default", None),
    ("nosuchuser", "wrongpw", "default", None),   # also mapped to guest
    ("", "", "default", None),                    # null session, for contrast
]

print(version.BANNER)
print(f"{'user':12} {'asked':8} {'got':8} {'guest':5} {'signing':7} result")

for user, password, label, dialect in CASES:
    conn = SMBConnection(TARGET, TARGET, preferredDialect=dialect, timeout=10)
    conn.login(user, password)
    got = f"0x{conn.getDialect():04x}"
    guest = bool(conn.isGuestSession())
    signing = bool(conn._SMBConnection._Session["SigningActivated"])
    try:
        tid = conn.connectTree("IPC$")
        conn.disconnectTree(tid)
        shares = ",".join(s["shi1_netname"].rstrip("\x00") for s in conn.listShares())
        result = f"OK -> {shares}"
    except Exception as exc:
        result = f"FAIL -> {exc}"
    print(f"{user or '<null>':12} {label:8} {got:8} {str(guest):5} {str(signing):7} {result}")
    conn.close()

Output

First, the same server with a stock client, to establish that guest access itself is fine:

$ docker exec repro smbclient -N -L localhost
    Sharename       Type      Comment
    ---------       ----      -------
    public          Disk
    IPC$            IPC       IPC Service (samba guest repro)

$ docker exec repro smbclient -N //localhost/public -c 'ls'
  .                                   D        0  Tue Sep  1 12:30:08 2026
  ..                                  D        0  Tue Sep  1 12:30:08 2026
  readme.txt                          N       31  Tue Sep  1 12:30:08 2026

Then impacket against that same container:

Impacket v0.14.0.dev0+20260828.120813.032dfb1b - Copyright Fortra, LLC and its affiliated companies

user         asked    got      guest signing result
guest        2.0.2    0x0202   True  False   OK -> public,IPC$
guest        2.1      0x0210   True  False   OK -> public,IPC$
guest        3.0      0x0300   True  False   OK -> public,IPC$
guest        3.1.1    0x0311   True  True    FAIL -> STATUS_ACCESS_DENIED
guest        default  0x0311   True  True    FAIL -> STATUS_ACCESS_DENIED
nosuchuser   default  0x0311   True  True    FAIL -> STATUS_ACCESS_DENIED
<null>       default  0x0311   False True    OK -> public,IPC$

(STATUS_ACCESS_DENIED above is the full SMB SessionError: code: 0xc0000022 ..., trimmed for width.)

Two things stand out: signing flips on exactly where it breaks, and the null session, which comes back guest=False, is unaffected even though it also has signing active. So it's specifically the guest case.

What the server says

With log level = 1, smbd logs one of these per failed request:

Bad SMB2 (sign_algo_id=1) signature for message

That's smb2_signing_check_pdu() in libcli/smb/smb2_signing.c reaching its mem_equal_const_time(res, sig, 16) comparison and failing it, i.e. the server did receive a signed request and the signature didn't match, it isn't refusing access to IPC$ for any other reason.

The reason the two ends can't agree is that a guest session's session key is all zeros server-side. Samba is explicit about it in source3/auth/auth_util.c:

/* annoying, but the Guest really does have a session key, and it is
   all zeros! */
session_info->session_key = data_blob_talloc_zero(session_info, 16);

The client derives its signing key from the password it actually sent, so the two signing keys differ. The session setup itself is unsigned, which is why login appears to succeed and only the next request fails.

What I think is happening client-side (please correct me)

As far as I can follow it, Connection.RequireSigning is set unconditionally for 3.1.1 in negotiateSession()smb3.py L687-692 on master:

if (negResp['SecurityMode'] & SMB2_NEGOTIATE_SIGNING_REQUIRED) == SMB2_NEGOTIATE_SIGNING_REQUIRED or \
        self._Connection['Dialect'] == SMB2_DIALECT_311:
    self._Connection['RequireSigning'] = True
if self._Connection['Dialect'] == SMB2_DIALECT_311:
    # Always Sign
    self._Connection['RequireSigning'] = True

That flows into Session['SigningRequired'] (L1053), and then in sessionSetup() (L1135-1146) the server's session flags arrive and signing is activated:

sessionSetupResponse = SMB2SessionSetup_Response(packet['Data'])
self._Session['SessionFlags'] = sessionSetupResponse['SessionFlags']
self._Session['SessionID']    = packet['SessionID']

# Do not encrypt anonymous connections
if user == '' or self.isGuestSession():
    self._Connection['SupportsEncryption'] = False

# Calculate the key derivations for dialect 3.0
if self._Session['SigningRequired'] is True:
    self._Session['SigningActivated'] = True

So SMB2_SESSION_FLAG_IS_GUEST is available right there and already used to turn encryption off, but signing stays on. That's the part that makes me unsure this is an oversight, the guest case was clearly considered three lines earlier, and # Always Sign reads like a conscious decision.

Why it seems to be surfacing now

I don't think the signing code changed. git log -S"# Always Sign" puts that block in 8afe4fe (2020-03-12, "Adding SMB 3.1.1 support for Client SMB Connections"), and 8f81720 ("fix connection issue when Smb2DialectMin is SMB311 on target SMB server") doesn't touch RequireSigning at all — it added 3.0.2 and 3.1.1 to the default dialect list. So my read is that the guest behaviour has been there since 3.1.1 support landed, and callers only started reaching it by default after the dialect change. That commit looks correct in itself; guest connections just seem to be collateral. Consistent with that, the explicit 3.1.1 row above fails on any version, not just current master.

Downstream this shows up as e.g. nxc smb <ip> --shares -u guest -p '' reporting Error enumerating shares: STATUS_ACCESS_DENIED, which is what sent me digging.

For reference

MS-SMB2 3.2.5.3.1 does seem to carve guest/anonymous out client-side:

  • If the security subsystem indicates that the session was established by an anonymous user, Session.SigningRequired MUST be set to FALSE and Session.IsAnonymous MUST be set to TRUE.
  • If the security subsystem indicates that the session was established by a guest user, Session.SigningRequired MUST be set to FALSE and Session.IsGuest MUST be set to TRUE.
  • If the SMB2_SESSION_FLAG_IS_GUEST bit is set in the SessionFlags field of the SMB2 SESSION_SETUP Response and if RequireMessageSigning is FALSE, Session.SigningRequired MUST be set to FALSE.

and Microsoft's Open Specifications write-up on SMB signing states the 3.1.1 rule with an explicit exception:

If 3.1.1 dialect is negotiated, the protocol requires signing or encrypting all TREE_CONNECT sent in non-guest, non-anonymous sessions.

NOTE: Signing cannot be required for guest or anonymous sessions as they do not have proper security context.

I'm reading those cold, though, and it's entirely possible there's a practical reason not to follow them here.
I should also be upfront that I've only tested against Samba.
Happy to put together a PR and test it more widely if you can point me at the shape you'd want — and equally happy to be told I'm holding it wrong. Thanks for taking a look.

Disclaimer: I used an AI assistant to help draft this write-up and to track down the exact line numbers and commits referenced above. The reproduction is my own: I hit the bug in the first place, built the container myself, and every line of output pasted here is from a run I did.

Metadata

Metadata

Labels

in reviewThis issue or pull request is being analyzedquestionMeant for discussion threads

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions