rapid7 / ruby_smb

A native Ruby implementation of the SMB Protocol Family
Other
79 stars 82 forks source link

Add Anonymous Authentication Support for SMB2 and SMB3 #190

Closed zeroSteiner closed 2 years ago

zeroSteiner commented 2 years ago

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 the examples/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.

Demo

In this example 192.168.159.96 is a Server 2019 domain controller.

[smcintyre@localhost ruby_smb]$ ruby examples/tree_connect.rb --no-smbv1 --no-smbv2 192.168.159.96 IPC$
SMB3 : (0x00000000) STATUS_SUCCESS: The operation completed successfully.
Connected to \\192.168.159.96\IPC$ successfully!
[smcintyre@localhost ruby_smb]$ ruby examples/tree_connect.rb --no-smbv1 --no-smbv3 192.168.159.96 IPC$
SMB2 : (0x00000000) STATUS_SUCCESS: The operation completed successfully.
Connected to \\192.168.159.96\IPC$ successfully!
[smcintyre@localhost ruby_smb]$ 
cdelafuente-r7 commented 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

Test non existing user, no password - Windows: `net use \\192.168.1.32\share /user:MYLAB\foo` - RubySMB: `ruby examples/tree_connect.rb --username MYLAB\\foo 192.168.1.32 share` | 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 | yes | | Win 2019 | yes | STATUS_SUCCESS | Guest=True, Null=False, Encrypt=True | no (client refuses encryption and exits) | | RubySMB | yes | STATUS_SUCCESS | Guest=True, Null=False, Encrypt=True | yes |
Test non existing user, with password - Windows: `net use \\192.168.1.32\share /user:MYLAB\foo 123` - RubySMB: `ruby examples/tree_connect.rb --username MYLAB\\foo --password 123 192.168.1.32 share` | 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 | yes (only the first TreeConnectRequest, server reset the connection immediately after) | | Win 2019 | yes | STATUS_SUCCESS | Guest=True, Null=False, Encrypt=True | no (client refuses encryption and exits) | | RubySMB | yes | STATUS_SUCCESS | Guest=True, Null=False, Encrypt=True | yes (only the first TreeConnectRequest, server reset the connection immediately after) |
Test an existing user, no password - Windows: `net use \\192.168.1.32\share /user:DESKTOP-UUQE0B4\smbtest` - RubySMB: `ruby examples/tree_connect.rb --username DESKTOP-UUQE0B4\\smbtest 192.168.1.32 share` | Client | Server encryption enforced | Session Setup Status Code | Session Setup Response > Session Flags | Encryption after Session Setup | | --- | --- | --- | --- | --- | | Win 2019 | no | STATUS_SUCCESS | Guest=False, Null=False, Encrypt=False | no | | RubySMB | no | STATUS_LOGON_FAILURE | Guest=False, Null=False, Encrypt=False | n/a | | Win 2019 | yes | STATUS_SUCCESS | Guest=False, Null=False, Encrypt=True | yes | | RubySMB | yes | STATUS_LOGON_FAILURE | Guest=False, Null=False, Encrypt=False | n/a |
Test an existing user, wrong password - Windows: `net use \\192.168.1.32\share /user:DESKTOP-UUQE0B4\smbtest 123` - RubySMB: `ruby examples/tree_connect.rb --username DESKTOP-UUQE0B4\\smbtest --password 123 192.168.1.32 share` | Client | Server encryption enforced | Session Setup Status Code | Session Setup Response > Session Flags | Encryption after Session Setup | | --- | --- | --- | --- | --- | | Win 2019 | no | STATUS_LOGON_FAILURE | Guest=False, Null=False, Encrypt=False | n/a | | RubySMB | no | STATUS_LOGON_FAILURE | Guest=False, Null=False, Encrypt=False | n/a | | Win 2019 | yes | STATUS_LOGON_FAILURE | Guest=False, Null=False, Encrypt=False | n/a | | RubySMB | yes | STATUS_LOGON_FAILURE | Guest=False, Null=False, Encrypt=False | n/a |
Test an existing user, good password - Windows: `net use \\192.168.1.32\share /user:DESKTOP-UUQE0B4\smbtest 123456` - RubySMB: `ruby examples/tree_connect.rb --username DESKTOP-UUQE0B4\\smbtest --password 123456 192.168.1.32 share` | Client | Server encryption enforced | Session Setup Status Code | Session Setup Response > Session Flags | Encryption after Session Setup | | --- | --- | --- | --- | --- | | Win 2019 | no | STATUS_SUCCESS | Guest=False, Null=False, Encrypt=False | no | | RubySMB | no | STATUS_SUCCESS | Guest=False, Null=False, Encrypt=False | yes | | Win 2019 | yes | STATUS_SUCCESS | Guest=False, Null=False, Encrypt=True | yes | | RubySMB | yes | STATUS_SUCCESS | Guest=False, Null=False, Encrypt=True | yes |

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.

zeroSteiner commented 2 years ago

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)

Source

Also these settings to enable true anonymous access.

zeroSteiner commented 2 years ago

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.

cdelafuente-r7 commented 2 years ago

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 yes no
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 yes (only the first TreeConnectRequest, server reset the connection immediately after) no (but STATUS_ACCESS_DENIED on the TreeConnect)

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:

Screenshot 2022-02-02 at 20 42 24

I still have the same STATUS_LOGON_FAILURE in case 3.

zeroSteiner commented 2 years ago

Case 1

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.

Case 2

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.

cdelafuente-r7 commented 2 years ago

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 the always_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.

cdelafuente-r7 commented 2 years ago

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:

  1. Once the rubyntlm's update is landed, no code change will be necessary in RubySMB to benefit from this improvement.
  2. Case 3 issue seems to be related to my own environment and could not be reproduced else where. Also, since the main goal is to achieve anonymous connection to the $IPC share for specific exploit needs, I believe this issue is irrelevant for now.

I've retested with and without the rubyntlm changes and I got the expected results:

I'll go ahead and land it.