Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

is smbprotocol support anonymous login #168

Open
hackoflpf opened this issue Mar 10, 2022 · 10 comments
Open

is smbprotocol support anonymous login #168

hackoflpf opened this issue Mar 10, 2022 · 10 comments

Comments

@hackoflpf
Copy link

hackoflpf commented Mar 10, 2022

I can't use smbprotocol to login as a guest user,if i set username and password is None,the program behave like belows,
INFO:smbprotocol.connection:Initialising connection, guid: 0f41207e-d29d-4543-a60c-6dcad6339edf, require_signing: False, server_name: 127.0.0.1, port: 445
False
INFO:smbprotocol.connection:Setting up transport connection
INFO:smbprotocol.transport:Connecting to DirectTcp socket
INFO:smbprotocol.connection:Starting negotiation with SMB server
INFO:smbprotocol.connection:Negotiating with SMB2 protocol with highest client dialect of: SMB_3_0_0
INFO:smbprotocol.connection:Sending SMB2 Negotiate message
INFO:smbprotocol.connection:Receiving SMB2 Negotiate response
INFO:smbprotocol.connection:Negotiated dialect: (768) SMB_3_0_0
INFO:smbprotocol.connection:Connection require signing: False
INFO:smbprotocol.session:Initialising session with username: admins
INFO:smbprotocol.connection:Disconnecting transport connection
INFO:smbprotocol.transport:Disconnecting DirectTcp socket
Traceback (most recent call last):
File "/home/admins/.local/lib/python3.8/site-packages/smbprotocol/session.py", line 266, in connect
context = spnego.client(self.username, self.password, service='cifs', hostname=self.connection.server_name,
File "/home/admins/.local/lib/python3.8/site-packages/spnego/auth.py", line 202, in client
return _new_context(
File "/home/admins/.local/lib/python3.8/site-packages/spnego/auth.py", line 117, in _new_context
return proxy(
File "/home/admins/.local/lib/python3.8/site-packages/spnego/_ntlm.py", line 287, in init
self._credential = _NTLMCredential(next(c for c in credentials if "ntlm" in c.supported_protocols))
File "/home/admins/.local/lib/python3.8/site-packages/spnego/_ntlm.py", line 227, in init
self.domain, self.username, self.lm_hash, self.nt_hash = _get_credential(self._store, domain, username)
File "/home/admins/.local/lib/python3.8/site-packages/spnego/_ntlm.py", line 137, in _get_credential
raise OperationNotAvailableError(context_msg="Retrieving NTLM store without NTLM_USER_FILE set to a filepath")
spnego.exceptions.OperationNotAvailableError: SpnegoError (16): Operation not supported or available, Context: Retrieving NTLM store without NTLM_USER_FILE set to a filepath

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "file-management.py", line 30, in
session.connect()
File "/home/admins/.local/lib/python3.8/site-packages/smbprotocol/session.py", line 269, in connect
raise SMBAuthenticationError("Failed to authenticate with server: %s" % str(err.message))
smbprotocol.exceptions.SMBAuthenticationError: Failed to authenticate with server: SpnegoError (16): Operation not supported or available, Context: Retrieving NTLM store without NTLM_USER_FILE set to a filepath

my code is

import uuid

from smbprotocol.connection import Connection
from smbprotocol.create_contexts import CreateContextName, \
    SMB2CreateContextRequest, SMB2CreateQueryMaximalAccessRequest
from smbprotocol.security_descriptor import AccessAllowedAce, AccessMask, \
    AclPacket, SDControl, SIDPacket, SMB2CreateSDBuffer
from smbprotocol.session import Session
from smbprotocol.structure import FlagField
from smbprotocol.open import CreateDisposition, CreateOptions, \
    FileAttributes, FilePipePrinterAccessMask, ImpersonationLevel, Open, \
    ShareAccess
from smbprotocol.tree import TreeConnect
import logging
server = "127.0.0.1"
port = 445
username = None
password = None
share = r"\\%s\tm" % server
file_name = "file-test.txt"
logging.basicConfig(level=logging.INFO)
connection = Connection(uuid.uuid4(), server, port,require_signing=False)
connection.connect(0x300)

try:
    session = Session(connection, username, password,require_encryption=False,auth_protocol="ntlm")
    session.connect()
    tree = TreeConnect(session, share)
    tree.connect()
    exit(0)
    # ensure file is created, get maximal access, and set everybody read access
    max_req = SMB2CreateContextRequest()
    max_req['buffer_name'] = \
        CreateContextName.SMB2_CREATE_QUERY_MAXIMAL_ACCESS_REQUEST
    max_req['buffer_data'] = SMB2CreateQueryMaximalAccessRequest()

    # create security buffer that sets the ACL for everyone to have read access
    everyone_sid = SIDPacket()
    everyone_sid.from_string("S-1-1-0")

    ace = AccessAllowedAce()
    ace['mask'] = AccessMask.GENERIC_ALL
    ace['sid'] = everyone_sid

    acl = AclPacket()
    acl['aces'] = [ace]

    sec_desc = SMB2CreateSDBuffer()
    sec_desc['control'].set_flag(SDControl.SELF_RELATIVE)
    sec_desc.set_dacl(acl)
    sd_buffer = SMB2CreateContextRequest()
    sd_buffer['buffer_name'] = CreateContextName.SMB2_CREATE_SD_BUFFER
    sd_buffer['buffer_data'] = sec_desc

    create_contexts = [
        max_req,
        sd_buffer
    ]

    file_open = Open(tree, file_name)
    open_info = file_open.create(
        ImpersonationLevel.Impersonation,
        FilePipePrinterAccessMask.GENERIC_READ |
        FilePipePrinterAccessMask.GENERIC_WRITE,
        FileAttributes.FILE_ATTRIBUTE_NORMAL,
        ShareAccess.FILE_SHARE_READ | ShareAccess.FILE_SHARE_WRITE,
        CreateDisposition.FILE_OVERWRITE_IF,
        CreateOptions.FILE_NON_DIRECTORY_FILE,
        create_contexts
    )

    # as the raw structure 'maximal_access' is an IntField, we create our own
    # flag field, set the value and get the human readble string
    max_access = FlagField(
        size=4,
        flag_type=FilePipePrinterAccessMask,
        flag_strict=False
    )
    max_access.set_value(open_info[0]['maximal_access'].get_value())
    print("Maximum access mask for file %s\\%s: %s"
          % (share, file_name, str(max_access)))

    # write to a file
    text = "Hello World, what a nice day to play with SMB"
    file_open.write(text.encode('utf-8'), 0)

    # read from a file
    file_text = file_open.read(0, 1024)
    print("Text of file %s\\%s: %s"
          % (share, file_name, file_text.decode('utf-8')))
    file_open.close(False)

    # read and delete a file in a single SMB packet instead of 3
    file_open = Open(tree, file_name)
    delete_msgs = [
        file_open.create(
            ImpersonationLevel.Impersonation,
            FilePipePrinterAccessMask.GENERIC_READ |
            FilePipePrinterAccessMask.DELETE,
            FileAttributes.FILE_ATTRIBUTE_NORMAL,
            0,
            CreateDisposition.FILE_OPEN,
            CreateOptions.FILE_NON_DIRECTORY_FILE |
            CreateOptions.FILE_DELETE_ON_CLOSE,
            send=False
        ),
        file_open.read(0, len(text), send=False),
        file_open.close(False, send=False)
    ]
    requests = connection.send_compound([x[0] for x in delete_msgs],
                                        session.session_id,
                                        tree.tree_connect_id, related=True)
    responses = []
    for i, request in enumerate(requests):
        response = delete_msgs[i][1](request)
        responses.append(response)
    print("Text of file when reading/deleting in 1 request: %s"
          % responses[1].decode('utf-8'))
finally:
    connection.disconnect(True)
@jborean93
Copy link
Owner

Technically it should be possible but I remember trying to get it working when I first wrote the code and it was difficult. You can try setting the username to Guest with an empty string as the password but I doubt that’s going to work. Using a Guest or Anonymous logon loses a lot of the security benefits that SMB had introduced like message signatures and encryption. You are better off adding an account that can you and authenticate with rather than an anonymous user in general.

@adiroiban
Copy link
Contributor

adiroiban commented Mar 11, 2022

Does your server support anonymous/guest session?

I remember that guest accounts are supported by Windows Desktop, but guest accounts are disabled by default with Windows Server.

See this docs - https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-R2-and-2012/jj852219%28v=ws.11%29

As commented by Jordan, you will need to use Guest as username.
You will need to set password to something ... and that value is ignored.

From my code

    # Some custom logicc
    anonymous = True
    username = 'Guest'
    # Anonymous needs to have a password that is later ignored.
    # Otherwise pyspnego will try to load it from a file.
    password = 'ignored'

Setting to None should be avoided as username and password API should require these values to have a text type.

And you can see from the error that it fails as it tries to load the password from a file.

@jborean93
Copy link
Owner

Thanks @adiroiban that about sums up what I came across before, the Guest could be used with any value for the password as it was ignored on the server. I believe Anonymous accounts are slightly different to Guest and needs extra work in pyspnego to use.

@hackoflpf
Copy link
Author

hackoflpf commented Mar 14, 2022

Of course,i test this on ubuntu with samba 4.15.0,i can use the default samba tool smbclient to login without password,but if i set the password with None value,smbprotocol will raise exceptions.This is my smb.conf
[check]
path = /home/test/
browseable = yes
public = yes
available = yes
oplocks = yes
follow symlinks = yes
map archive = no
guest ok = yes
writable = yes
Maybe the Spnego library doesn't support this operation,i haven't check the code,but i can use samba python library to login without password,only to set the username is "" and password is None,it works well.

@jborean93
Copy link
Owner

Pyspnego certainly doesn’t support anonymous users as you’ve seen which leans smbprotocol also does not support anonymous logons. It should work with Guest logins which has a username of Guest and the password is set to any string value. They are slightly different but could still work in your scenario if your samba server is configured to allow it.

@mxmlnkn
Copy link

mxmlnkn commented Oct 4, 2024

I have the same problem. Set up a server with:

mkdir /tmp/smbshare
echo bar > /tmp/smbshare/foo
sudo apt install samba
cat <<EOF | sudo tee /etc/samba/smb.conf
[test]
path = /tmp/smbshare
browsable = yes
guest ok = yes
read only = yes
create mask = 0755
EOF
sudo systemctl restart smbd nmbd

Connect to it anonymously works with smbclient by not specifying any user and specifying any password:

sudo apt install smbclient
smbclient --password=ignored --port 445 -c ls //127.0.0.1/test

I fail to achieve the same goal with smbprotocol. I tried:

import smbclient

smbclient.register_session(server="127.0.0.1")'
# spnego.exceptions.BadMechanismError: SpnegoError (1): SpnegoError (16): Operation not supported or available, 
# Context: No username or password was specified and the credential cache did not exist or contained no credentials, 
# Context: Unable to negotiate common mechanism

smbclient.register_session(server="127.0.0.1", username="", password="")
# Same as above

smbclient.register_session(server="127.0.0.1", username="Guest", password="ignored")
# smbprotocol.exceptions.LogonFailure: Received unexpected status from the server:
# The attempted logon is invalid. This is either due to a bad username or authentication information.
# (3221225581) STATUS_LOGON_FAILURE: 0xc000006d

@adiroiban
Copy link
Contributor

Hi Max,

Thanks for the report.

As far as I know, the SMB protocol has no "native" support for anonymous accounts.

In Windows, we have "guest" account

Windows documentation is here:

https://learn.microsoft.com/en-us/windows-server/storage/file-server/enable-insecure-guest-logons-smb2-and-smb3?tabs=group-policy


smbclient.register_session(server="127.0.0.1", username="Guest", password="ignored")
# smbprotocol.exceptions.LogonFailure: Received unexpected status from the server:
# The attempted logon is invalid. This is either due to a bad username or authentication information.
# (3221225581) STATUS_LOGON_FAILURE: 0xc000006d

Inside Samba documentation, we also have references to the "guest" account

See for example https://www.samba.org/samba/docs/current/man-html/smb.conf.5.html#MAPTOGUEST

Not sure if you have setup the guest accout inside Samba..

smbclient.register_session(server="127.0.0.1", username="", password="")
# Same as above

smbclient.register_session(server="127.0.0.1", username="", password="")

I think that the issue here is with pyspnego which handles an empty string, the same was as None

I don't know how this can be fixed without breaking backward compatibility

So, to fix the "anonymous" login, I think that we need an explicit guest/anonymous request.

It can look something like this:

smbclient.register_session(server="127.0.0.1", username=pyspnego.GUEST)

Now, the pyspnego.GUEST constant can be aliased in the smbprotocol API so that you don't need to explicity import pyspnego ... but we need to tell pyspnego that we want to go via the "guest credentials" use case , and not the normal "userame + password" credentials

@jborean93
Copy link
Owner

Anonymous user support is not present at the pyspnego library does not support anonymous authentication. It's been a while since I last looked at the guest support but AFAIK guest logons are used when you attempt to log on with a user that is not valid. For Samba the times when it will map to a guest logon are configured by https://www.samba.org/samba/docs/current/man-html/smb.conf.5.html#MAPTOGUEST with the default being never map to a guest user. You would have to explicitly configure that value to something else like Bad User and then specify a user that doesn't exist with any random password string. You cannot use an empty string for either as that means use the implicit user identity which may or may not exist depending on your environment.

@jborean93
Copy link
Owner

I can confirm the following Samba config works

[global]
map to guest = Bad User

[test]
path = /tmp/smbshare
browsable = yes
guest ok = yes
read only = yes
create mask = 0755

I was able to test this out with the following Python code

import smbclient

smbclient.ClientConfig(require_secure_negotiate=False)

smbclient.register_session(
    'localhost',
    username='Guest',
    password='ignored',
    port=8445,
    require_signing=False,
)

res = smbclient.listdir(
    r'\\localhost\test',
    port=8445,
)
print(res)

The smbclient.ClientConfig(require_secure_negotiate=False) is needed to disable the post tree connect verification that is done to verify the protocol hasn't been downgraded. The require_signing=False is then needed when registering the session to also allow that session to not require signing. The signing bits all need to be disabled for guest and anonymous accounts as there is no secret session key required by signing.

All of this is unfortunately sub-optimal but considering it's simpler to just create a dummy account and still achieve the same result of guest/anon accounts there is not much of a pressing need to do this. I am hoping to revamp the whole config/kwargs setup to be more user friendly as the current implementation can be confusing to use.

@mxmlnkn
Copy link

mxmlnkn commented Oct 8, 2024

I can confirm the following Samba config works

Works for me, too.

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

No branches or pull requests

4 participants