jborean93 / smbprotocol

Python SMBv2 and v3 Client
MIT License
321 stars 74 forks source link

is smbprotocol support anonymous login #168

Open hackoflpf opened 2 years ago

hackoflpf commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

I can confirm the following Samba config works

Works for me, too.