rapid7 / ruby_smb

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

Add an initial SMB server implementation #177

Closed zeroSteiner closed 3 years ago

zeroSteiner commented 3 years ago

ImTheSMBServerNow

SMB Server

This PR adds an initial implementation of an SMB server powered by Ruby SMB. This partial implementation supports SMB version 1 (dialect NT LM 0.12) through SMB version 3.1.1 (the latest as of this writing) and every dialect in the 2.x and 3.x families. Everything needed to fully authenticate a user is included (but nothing more). This includes the ability to perform protocol negotiation and session setup.

At this point the server should provide everything necessary to run authentication based attacks like capturing and relaying. Things like hosting files will be done in another round of work.

Dialect Module

Folks familiar with SMB know that the SMB versions 2 and 3 are very closely related. If you browse through the code base you'll notice that SMB3 related packets are defined under the RubySMB::SMB2 module and alot of code is reused from the SMB2 logic to provide SMB3 functionality. I added a new dialect module to will translate a dialect from any of the versions into an object containing useful metadata that can be used by us developers to group the dialect using varying degrees of specificity from the exact dialect, to the version, to the family. This also allows code that needs to differentiate between version 1 and version 2 or 3 to simply check the #order attribute. In this context "order" is used because it's above the family in the Taxonomic Rank. This allows us to be consistent about calling "SMB 2.x" and "SMB 3.x" a family which is aligned with the Microsoft SMB specs.

These metadata objects also have a few name attributes that will be helpful to print in diagnostic output in the future to describe the dialect to users. See the version_name and full_name attributes, which again have varying degrees of specificity for use in different contexts.

Authentication

Authentication is abstracted into a provider module which is server specific and generates an Authenticator object instance which performs the actual authentication process and is specific to a particular connection. This is important because the provider object is shared across connections and could become confused if it were to handle multiple simultaneous authentication processes.

There are base classes for both the provider and authenticator that do nothing but define the interface and can be used in the future.

Authentication providers can also specify that they permit anonymous authentication. As the name implies, this configuration allows clients to connect and "authenticate" to the server without specifying any credentials. Windows 10 does not permit this by default (probably because crypto keys can't be negotiated in this case which eliminates some security protections).

NTLM Authentication

There is a full NTLM-backed authentication provider included in this implementation. This provider uses the standard NTLMSSP protocol and goes through the full 4-way handshake involving the transfer of Net-NTLMv1 and Net-NTLMv2 challenge and responses. The provider allows accounts to be defined and stored in memory, though a database or file backend could easily be added by a new class and overriding the get and put account methods.

Account usernames and domains are case-preserving but insensitive for comparisons. This means a user logging in as workgroup\rubysmb can authenticate just fine even if the account was defined as being WORKGROUP\RubySMB so long as the password is correct of course.

The NTLM messages are provided by the Net::NTLM module which RubySMB is already using for client-side authentication. One thing that could be problematic is that the NTLM messages are able to be in one of two encodings based on a flag within the message. Every client implementation I tested (see the Testing section below) uses UTF-16LE. A client implementation that does not use UTF-16LE may be unable to authenticate and is something that should be tested if such a client can be identified.

The provided NTLM provider class makes it very easy to manipulate the server challenge for nefarious purposes but defaults to using SecureRandom to properly generate a challenge. To override this behavior, a generator block can be provided:

provider.generate_server_challenge do
  "\x11\x22\x33\x44\x55\x66\x77\x88"
end

or the method can be redefined as seen in the example/auth_capture.rb server.

class HaxorNTLMProvider < RubySMB::Gss::Provider::NTLM
  def generate_server_challenge(&block)
    "\x11\x22\x33\x44\x55\x66\x77\x88"
  end
end

Since the Net::NTLM module is capable of treating NTLM hashes as passwords, an account can actually be provided using a valid NTLM hash as its password and this will work correctly.

Prior Testing

image This is an example of a Windows 10 client connection to a test harness, negotiating SMB version 3.1.1. The original authentication as the smcintyre user fails, and then Windows prompts for the username and password. At that point, I enter the credentials that are valid for the harness of WORKGROUP\RubySMB and password which allows it to authenticate and then attempt the TreeConnect request proving that the SMB 3 crypto keys were calculated correctly.

This implementation has been tested over the course of development using a number of clients including:

In each case the client should be able to connect to, negotiate with and authenticate to the SMB server. Once authenticated, using Wireshark, it should be visible that a TreeConnect request is sent by the client showing that the authentication process was successful. Of particular note, SMB 3.1.1 needs to send a TreeConnect request because the Windows client will reject the server's response that authentication was successfully established if the response is not signed.

If Connection.Dialect is "3.1.1", and if SMB2_FLAGS_SIGNED is not set in the Flags field of the SMB2 packet header of the response, the client MUST return an error to the calling application.

Source: 3.2.5.3.1 Handling a New Authentication

Receiving the TreeConnect request shows that everything the server currently supports is working correctly, particularly the correct crypto keys for SMB 2 and SMB 3. After the TreeConnect request is received, it's expected that the server will crash and not handle that request at this time.

Test Servers

This implementation includes a demonstration server that will capture authentication similar to Metasploit's auxiliary/server/capture/smb module. This example server is located at example/auth_capture.rb and simply logs the Net-NTLMv1 and Net-NTLMv2 challenge and response messages necessary for an attacker to perform an offline attack against the credentials of the user that initiated the connection.

Example Capture Output The following is an example of the captured output where the client was the `example/authenticate.rb` script. The different versions of SMB that are being negotiated can be seen in the left hand column. ``` ruby auth_capture.rb server is running received connection [SMB v3] NTLMv2-SSP Client : 127.0.0.1 [SMB v3] NTLMv2-SSP Username : .\RubySMB [SMB v3] NTLMv2-SSP Hash : RubySMB::.:1122334455667788:9c538a23be908b316aac01868216c2a9:010100000000000080a54102fb8ad701351f9b4af704b8a600000000020012004c004f00430041004c0048004f0053005400010012004c004f00430041004c0048004f0053005400040002000000030012004c004f00430041004c0048004f00530054000700080080a54102fb8ad7010000000000000000 received connection [SMB v2] NTLMv2-SSP Client : 127.0.0.1 [SMB v2] NTLMv2-SSP Username : .\RubySMB [SMB v2] NTLMv2-SSP Hash : RubySMB::.:1122334455667788:5c12aec8b72e86c49a3bd5b395392fd5:010100000000000080a54102fb8ad70105feff0fcf247f7100000000020012004c004f00430041004c0048004f0053005400010012004c004f00430041004c0048004f0053005400040002000000030012004c004f00430041004c0048004f00530054000700080080a54102fb8ad7010000000000000000 received connection [SMB v1] NTLMv2-SSP Client : 127.0.0.1 [SMB v1] NTLMv2-SSP Username : .\RubySMB [SMB v1] NTLMv2-SSP Hash : RubySMB::.:1122334455667788:52d3fb5ea7d4d18fde57bc369dfebc94:010100000000000080a54102fb8ad701248b266e8b38ad1700000000020012004c004f00430041004c0048004f0053005400010012004c004f00430041004c0048004f0053005400040002000000030012004c004f00430041004c0048004f00530054000700080080a54102fb8ad7010000000000000000 received connection [SMB v2] NTLMv2-SSP Client : 127.0.0.1 [SMB v2] NTLMv2-SSP Username : .\RubySMB [SMB v2] NTLMv2-SSP Hash : RubySMB::.:1122334455667788:d1902819dd714910efd22ca0322edd9f:010100000000000080a54102fb8ad701362172730ac6bc0700000000020012004c004f00430041004c0048004f0053005400010012004c004f00430041004c0048004f0053005400040002000000030012004c004f00430041004c0048004f00530054000700080080a54102fb8ad7010000000000000000 received connection [SMB v3] NTLMv2-SSP Client : 127.0.0.1 [SMB v3] NTLMv2-SSP Username : .\RubySMB [SMB v3] NTLMv2-SSP Hash : RubySMB::.:1122334455667788:b92d74028ea93b670cae15177270ef30:010100000000000080a54102fb8ad7019240da74578df96e00000000020012004c004f00430041004c0048004f0053005400010012004c004f00430041004c0048004f0053005400040002000000030012004c004f00430041004c0048004f00530054000700080080a54102fb8ad7010000000000000000 ```

This server is included but doesn't demonstrate a fully functional authenticator. To test the full NTLM authentication provider use a simple harness:

#!/usr/bin/ruby

require 'bundler/setup'
require 'ruby_smb'
require 'ruby_smb/gss/provider/ntlm'

ntlm_provider = RubySMB::Gss::Provider::NTLM.new(allow_anonymous: true)
ntlm_provider.put_account('RubySMB', 'password')  # password can also be an NTLM hash

server = RubySMB::Server.new(
  gss_provider: ntlm_provider
)
puts "server is running"
server.run do
  puts "received connection"
  true
end

Administrivia

All of the important bits should be documented. Most of the code has some kind of basic unit test. What's notably missing from the unit tests are:

Testing

Using Windows is one of the most comprehensive ways to test the authentication process. Just tap Win + R and then enter the UNC path into the server where the test harness is running, e.g. \\1.2.3.4\share\somefile.txt. It can be a bit slow at times while Windows is negotiating the connection. Delays of up to a minute were observed.

For each of the major SMB versions 1, 2 and 3:

For at least one version:

If you use the example/authenticate.rb script, there will not be a TreeConnect request after the authentication is complete. That only occurs if the client is attempting to access a file.

If any issues are encountered, please collect a PCAP for debugging purposes and note the expected dialect from the testing environment.

sempervictus commented 3 years ago

Did a first pass - awesome stuff, thanks folks

cdelafuente-r7 commented 3 years ago

Release Notes

This adds an initial SMB server implementation. It supports SMBv1 (only NT LM 0.12 dialect) through SMBv3 and every dialect in the 2.x and 3.x families. This server can perform authentication, including protocol negotiation and session setup.