Closed zeroSteiner closed 2 years ago
Thanks for updating this @zeroSteiner. I've done some extended tests, comparing behaviours between Windows and RubySMB clients. Here are my findings (hopefully these make sense):
Server: Windows 10 20H2
I believe RubySMB should behaves as close as possible to Windows and enforce encryption, when possible. The third test (Test an existing user, no password
) is the most different. Authentication fails with RubySMB whereas it succeeds on Win 2019. The second test (Test non existing user, with password
) only fails due to encryption requested by the client. Maybe the client should decide to disable encryption when the Guest
's Session Flags
(in Session Setup
response) is true.
Just a note: I wasn't able to reproduce these results on Windows 10 until I added a share, gave everyone access and made the following change:
Network and Sharing Center > Advanced sharing settings > All Networks > Turn off password protected sharing (this enables the guest account)
Also these settings to enable true anonymous access.
Alright I read through the specs again (MS-SMB2 section 3.2.5.3.1) and changed it in ab13bec. This seems to fix test case 2 which is aligned with my understanding that when the encrypt flag is set in the session response that session-encryption must be enabled.
If Connection.Dialect belongs to the SMB 3.x dialect family and if the SMB2_SESSION_FLAG_ENCRYPT_DATA bit is set in the SessionFlags field of the SMB2 SESSION_SETUP Response, Session.EncryptData MUST be set to TRUE, and Session.SigningRequired MUST be set to FALSE.
Following that, it looks like the behavior is undefined when the guest and null flags are set.
For the third test case, I'm getting success with my existing user and no password, however the SESSION_SETUP response has the guest flag set. I'm assuming this is likely due to my server's configuration.
I run through all the cases again and found two differences in the results:
Client | Server encryption enforced | Session Setup Status Code | Session Setup Response > Session Flags | Encryption after Session Setup |
---|---|---|---|---|
Win 2019 | no | STATUS_SUCCESS | Guest=True, Null=False, Encrypt=False | no |
RubySMB | no | STATUS_SUCCESS | Guest=True, Null=False, Encrypt=False |
Client | Server encryption enforced | Session Setup Status Code | Session Setup Response > Session Flags | Encryption after Session Setup |
---|---|---|---|---|
Win 2019 | no | STATUS_SUCCESS | Guest=True, Null=False, Encrypt=False | no |
RubySMB | no | STATUS_SUCCESS | Guest=True, Null=False, Encrypt=False |
I'm not sure if the change in case 1 is wanted, since, in my understanding, we want to encrypt as much as possible, even if the server does not explicitly require it.
Case 2 is still failing but differently; since it is not encrypted anymore, the server doesn't RST the connection. However, I got a STATUS_ACCESS_DENIED
in the TreeConnectResponse
, I'm not sure why:
I still have the same STATUS_LOGON_FAILURE
in case 3.
I'm not sure if the change in case 1 is wanted, since, in my understanding, we want to encrypt as much as possible, even if the server does not explicitly require it.
Well we also wanted to match the Windows client as closely as possible and you suggested we disable encryption when the Guest flag is set.
I believe RubySMB should behaves as close as possible to Windows and enforce encryption, when possible. ... Maybe the client should decide to disable encryption when the Guest's Session Flags (in Session Setup response) is true.
Just let me know if you want to prioritize acting like Windows or encrypting things when we can and I'll make the change. In this particular case though, we can't do both.
I think the problem we were running into here is that the TreeConnectRequest
was being signed when it shouldn't be. Because we're authenticated as a guest and signing is not required, it shouldn't be signed. I tweaked this signing logic in commit 76978c11a215ddddf33701201bbd4682d6d5f9e9 to more closely match the docs:
If Connection.Dialect is "3.1.1" and Session.IsAnonymous and Session.IsGuest are set to FALSE and the request is not signed or not encrypted, then the server MUST disconnect the connection.
This change involved tracking if the session is a guest based on the SESSION_SETUP response flags, and taking that into account when signing packets for 3.1.1. This seems to have fixed the problem for test case 2.
Greta! This last change fixed case 2. Thanks for fixing this.
I think I was not very clear, sorry about that.
I believe RubySMB should behaves as close as possible to Windows and enforce encryption, when possible. Let me rephrase this.
I believe RubySMB should behaves as close as possible to Windows
: I meant having the same behavior during authentication (e.g. a valid user with no password will authenticate on Windows).enforce encryption, when possible
: I meant that we should encrypt as much as possible, if we have the required elements and the server supports it. I was under the impression this was the default behavior we wanted for RubySMB (e.g. we updated thealways_encrypt
client flag to true recently). That said, since there is no password in case 1, it is trivial to get the session key and decrypt the message (wireshark does this automatically BTW). There is no much value in having encryption in this case. So, it is okay to have the traffic in clear text.
Still the STATUS_LOGON_FAILURE
problem in case 3 remains. I'll try to look into it today, since it might be due to my server configuration.
Thanks for updating this again @zeroSteiner. After reviewing this again, it looks like the PR should be ready to land, even if two things are still pending:
rubyntlm
's update is landed, no code change will be necessary in RubySMB to benefit from this improvement.I've retested with and without the rubyntlm
changes and I got the expected results:
without rubyntlm
changes:
❯ bundle exec ruby examples/tree_connect.rb --no-smbv2 --no-smbv3 192.168.144.202 'IPC$'
SMB1 : (0x00000000) STATUS_WAIT_0: The caller specified WaitAny for WaitType and one of the dispatcher objects in the
Object array has been set to the signaled state.
Connected to \\192.168.144.202\IPC$ successfully!
❯ bundle exec ruby examples/tree_connect.rb --no-smbv1 --no-smbv3 192.168.144.202 'IPC$'
SMB2 : (0xc000006d) STATUS_LOGON_FAILURE: The attempted logon is invalid. This is either due to a bad username or
authentication information.
Authentication failed!
❯ bundle exec ruby examples/tree_connect.rb --no-smbv1 --no-smbv2 192.168.144.202 'IPC$'
SMB3 : (0xc000006d) STATUS_LOGON_FAILURE: The attempted logon is invalid. This is either due to a bad username or
authentication information.
Authentication failed!
with rubyntlm
changes:
❯ bundle exec ruby examples/tree_connect.rb --no-smbv2 --no-smbv3 192.168.144.202 'IPC$'
SMB1 : (0x00000000) STATUS_WAIT_0: The caller specified WaitAny for WaitType and one of the dispatcher objects in the
Object array has been set to the signaled state.
Connected to \\192.168.144.202\IPC$ successfully!
❯ bundle exec ruby examples/tree_connect.rb --no-smbv1 --no-smbv3 192.168.144.202 'IPC$'
SMB2 : (0x00000000) STATUS_WAIT_0: The caller specified WaitAny for WaitType and one of the dispatcher objects in the
Object array has been set to the signaled state.
Connected to \\192.168.144.202\IPC$ successfully!
❯ bundle exec ruby examples/tree_connect.rb --no-smbv1 --no-smbv2 192.168.144.202 'IPC$'
SMB3 : (0x00000000) STATUS_WAIT_0: The caller specified WaitAny for WaitType and one of the dispatcher objects in the
Object array has been set to the signaled state.
Connected to \\192.168.144.202\IPC$ successfully!
I'll go ahead and land it.
This adds anonymous authentication support for SMB versions 2 and 3. It requires the changes proposed in WinRB/rubyntlm#45 to be in place. The tiny block in
lib/ruby_smb/client/authentication.rb
is really the only bit necessary for the fix, it disables encryption when the session has been authenticated anonymously.I also updated the two example files that I used while testing this. While the
examples/anonymous_auth.rb
example shows that the session was properly authenticated, it does not prove that the session key was correctly calculated. This makes it a bit misleading and is why I also updated and tested more thoroughly with theexamples/tree_connect.rb
script. This example makes a TreeConnect request, that is always signed. When the session has been authenticated anonymously, this proves that the session key is correct.Testing
Use the updated tree_connect script, which now has some nice options so the code doesn't need to be changed for various configurations. By default, it will perform anonymous authentication. A username and password can optionally be specified.
ruby examples/tree_connect.rb --no-smbv1 --no-smbv2 target_ip IPC$
ruby examples/tree_connect.rb --no-smbv1 --no-smbv3 target_ip IPC$
Demo
In this example 192.168.159.96 is a Server 2019 domain controller.