Closed zeroSteiner closed 3 years ago
Did a first pass - awesome stuff, thanks folks
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.
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
andfull_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 beingWORKGROUP\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:or the method can be redefined as seen in the
example/auth_capture.rb
server.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
WORKGROUP\RubySMB
andpassword
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:
example/authenticate.rb
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.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 atexample/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:
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:
TreeConnect
requestFor at least one version:
If you use the
example/authenticate.rb
script, there will not be aTreeConnect
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.