76
76
FileStreamInformation ,
77
77
NETWORK_INTERFACE_INFO ,
78
78
SECURITY_DESCRIPTOR ,
79
+ SMB2_CREATE_DURABLE_HANDLE_RESPONSE_V2 ,
80
+ SMB2_CREATE_QUERY_MAXIMAL_ACCESS_RESPONSE ,
81
+ SMB2_CREATE_QUERY_ON_DISK_ID ,
79
82
SMB2_Cancel_Request ,
80
83
SMB2_Change_Notify_Request ,
81
84
SMB2_Change_Notify_Response ,
82
85
SMB2_Close_Request ,
83
86
SMB2_Close_Response ,
84
87
SMB2_Create_Context ,
85
- SMB2_CREATE_DURABLE_HANDLE_RESPONSE_V2 ,
86
- SMB2_CREATE_QUERY_MAXIMAL_ACCESS_RESPONSE ,
87
- SMB2_CREATE_QUERY_ON_DISK_ID ,
88
88
SMB2_Create_Request ,
89
89
SMB2_Create_Response ,
90
+ SMB2_ENCRYPTION_CIPHERS ,
90
91
SMB2_Echo_Request ,
91
92
SMB2_Echo_Response ,
92
93
SMB2_Encryption_Capabilities ,
93
94
SMB2_Error_Response ,
94
95
SMB2_FILEID ,
95
96
SMB2_Header ,
96
97
SMB2_IOCTL_Network_Interface_Info ,
97
- SMB2_IOCTL_Request ,
98
98
SMB2_IOCTL_RESP_GET_DFS_Referral ,
99
+ SMB2_IOCTL_Request ,
99
100
SMB2_IOCTL_Response ,
100
101
SMB2_IOCTL_Validate_Negotiate_Info_Response ,
101
102
SMB2_Negotiate_Context ,
108
109
SMB2_Query_Info_Response ,
109
110
SMB2_Read_Request ,
110
111
SMB2_Read_Response ,
112
+ SMB2_SIGNING_ALGORITHMS ,
111
113
SMB2_Session_Logoff_Request ,
112
114
SMB2_Session_Logoff_Response ,
113
115
SMB2_Session_Setup_Request ,
@@ -155,9 +157,11 @@ class SMBShare:
155
157
:param path: the path the the folder hosted by the share
156
158
:param type: (optional) share type per [MS-SRVS] sect 2.2.2.4
157
159
:param remark: (optional) a description of the share
160
+ :param encryptdata: (optional) whether encryption should be used for this
161
+ share. This only applies to SMB 3.1.1.
158
162
"""
159
163
160
- def __init__ (self , name , path = "." , type = None , remark = "" ):
164
+ def __init__ (self , name , path = "." , type = None , remark = "" , encryptdata = False ):
161
165
# Set the default type
162
166
if type is None :
163
167
type = 0 # DISKTREE
@@ -171,6 +175,7 @@ def __init__(self, name, path=".", type=None, remark=""):
171
175
self .name = name
172
176
self .type = type
173
177
self .remark = remark
178
+ self .encryptdata = encryptdata
174
179
175
180
def __repr__ (self ):
176
181
type = SRVSVC_SHARE_TYPES [self .type & 0x0FFFFFFF ]
@@ -202,6 +207,8 @@ class SMB_Server(Automaton):
202
207
:param ANONYMOUS_LOGIN: mark the clients as anonymous
203
208
:param GUEST_LOGIN: mark the clients as guest
204
209
:param REQUIRE_SIGNATURE: set 'Require Signature'
210
+ :param REQUIRE_ENCRYPTION: globally require encryption.
211
+ You could also make it share-specific on 3.1.1.
205
212
:param MAX_DIALECT: maximum SMB dialect. Defaults to 0x0311 (3.1.1)
206
213
:param TREE_SHARE_FLAGS: flags to announce on Tree_Connect_Response
207
214
:param TREE_CAPABILITIES: capabilities to announce on Tree_Connect_Response
@@ -223,7 +230,8 @@ def __init__(self, shares=[], ssp=None, verb=True, readonly=True, *args, **kwarg
223
230
self .GUEST_LOGIN = kwargs .pop ("GUEST_LOGIN" , None )
224
231
self .EXTENDED_SECURITY = kwargs .pop ("EXTENDED_SECURITY" , True )
225
232
self .USE_SMB1 = kwargs .pop ("USE_SMB1" , False )
226
- self .REQUIRE_SIGNATURE = kwargs .pop ("REQUIRE_SIGNATURE" , False )
233
+ self .REQUIRE_SIGNATURE = kwargs .pop ("REQUIRE_SIGNATURE" , None )
234
+ self .REQUIRE_ENCRYPTION = kwargs .pop ("REQUIRE_ENCRYPTION" , False )
227
235
self .MAX_DIALECT = kwargs .pop ("MAX_DIALECT" , 0x0311 )
228
236
self .TREE_SHARE_FLAGS = kwargs .pop (
229
237
"TREE_SHARE_FLAGS" , "FORCE_LEVELII_OPLOCK+RESTRICT_EXCLUSIVE_OPENS"
@@ -294,6 +302,8 @@ def __init__(self, shares=[], ssp=None, verb=True, readonly=True, *args, **kwarg
294
302
self .SMB2 = False
295
303
self .NegotiateCapabilities = None
296
304
self .GUID = RandUUID ()._fix ()
305
+ self .NextForceSign = False
306
+ self .NextForceEncrypt = False
297
307
# Compounds are handled on receiving by the StreamSocket,
298
308
# and on aggregated in a CompoundQueue to be sent in one go
299
309
self .NextCompound = False
@@ -315,9 +325,8 @@ def __init__(self, shares=[], ssp=None, verb=True, readonly=True, *args, **kwarg
315
325
Automaton .__init__ (self , * args , ** kwargs )
316
326
# Set session options
317
327
self .session .ssp = ssp
318
- self .session .SecurityMode = kwargs .pop (
319
- "SECURITY_MODE" ,
320
- 3 if self .REQUIRE_SIGNATURE else bool (ssp ),
328
+ self .session .SigningRequired = (
329
+ self .REQUIRE_SIGNATURE if self .REQUIRE_SIGNATURE is not None else bool (ssp )
321
330
)
322
331
323
332
@property
@@ -336,7 +345,14 @@ def vprint(self, s=""):
336
345
print ("> %s" % s )
337
346
338
347
def send (self , pkt ):
339
- return super (SMB_Server , self ).send (pkt , Compounded = self .NextCompound )
348
+ ForceSign , ForceEncrypt = self .NextForceSign , self .NextForceEncrypt
349
+ self .NextForceSign = self .NextForceEncrypt = False
350
+ return super (SMB_Server , self ).send (
351
+ pkt ,
352
+ Compounded = self .NextCompound ,
353
+ ForceSign = ForceSign ,
354
+ ForceEncrypt = ForceEncrypt ,
355
+ )
340
356
341
357
@ATMT .state (initial = 1 )
342
358
def BEGIN (self ):
@@ -433,6 +449,9 @@ def on_negotiate(self, pkt):
433
449
self .send (resp )
434
450
return
435
451
if self .SMB2 : # SMB2
452
+ # SecurityMode
453
+ if SMB2_Header in pkt and pkt .SecurityMode .SIGNING_REQUIRED :
454
+ self .session .SigningRequired = True
436
455
# Capabilities: [MS-SMB2] 3.3.5.4
437
456
self .NegotiateCapabilities = "+" .join (
438
457
[
@@ -449,16 +468,17 @@ def on_negotiate(self, pkt):
449
468
"MULTI_CHANNEL" ,
450
469
"PERSISTENT_HANDLES" ,
451
470
"DIRECTORY_LEASING" ,
471
+ "ENCRYPTION" ,
452
472
]
453
473
)
454
- if DialectRevision in [0x0300 , 0x0302 ]:
455
- # "if Connection.Dialect is "3.0" or "3.0.2""...
456
- # Note: 3.1.1 uses the ENCRYPT_DATA flag in Tree Connect Response
457
- self .NegotiateCapabilities += "+ENCRYPTION"
458
474
# Build response
459
475
resp = self .smb_header .copy () / cls (
460
476
DialectRevision = DialectRevision ,
461
- SecurityMode = self .session .SecurityMode ,
477
+ SecurityMode = (
478
+ "SIGNING_ENABLED+SIGNING_REQUIRED"
479
+ if self .session .SigningRequired
480
+ else "SIGNING_ENABLED"
481
+ ),
462
482
ServerTime = (time .time () + 11644473600 ) * 1e7 ,
463
483
ServerStartTime = 0 ,
464
484
MaxTransactionSize = 65536 ,
@@ -473,7 +493,27 @@ def on_negotiate(self, pkt):
473
493
resp .MaxReadSize = 0x800000
474
494
resp .MaxWriteSize = 0x800000
475
495
# SMB 3.1.1
476
- if DialectRevision >= 0x0311 :
496
+ if DialectRevision >= 0x0311 and pkt .NegotiateContextsCount :
497
+ # Negotiate context-capabilities
498
+ for ngctx in pkt .NegotiateContexts :
499
+ if ngctx .ContextType == 0x0002 :
500
+ # SMB2_ENCRYPTION_CAPABILITIES
501
+ for ciph in ngctx .Ciphers :
502
+ tciph = SMB2_ENCRYPTION_CIPHERS .get (ciph , None )
503
+ if tciph in self .session .SupportedCipherIds :
504
+ # Common !
505
+ self .session .CipherId = tciph
506
+ self .session .SupportsEncryption = True
507
+ break
508
+ elif ngctx .ContextType == 0x0008 :
509
+ # SMB2_SIGNING_CAPABILITIES
510
+ for signalg in ngctx .SigningAlgorithms :
511
+ tsignalg = SMB2_SIGNING_ALGORITHMS .get (signalg , None )
512
+ if tsignalg in self .session .SupportedSigningAlgorithmIds :
513
+ # Common !
514
+ self .session .SigningAlgorithmId = tsignalg
515
+ break
516
+ # Send back the negotiated algorithms
477
517
resp .NegotiateContexts = [
478
518
# Preauth capabilities
479
519
SMB2_Negotiate_Context ()
@@ -504,7 +544,11 @@ def on_negotiate(self, pkt):
504
544
"LEVEL_II_OPLOCKS+LOCK_AND_READ+NT_FIND+"
505
545
"LWIO+INFOLEVEL_PASSTHRU+LARGE_READX+LARGE_WRITEX"
506
546
),
507
- SecurityMode = self .session .SecurityMode ,
547
+ SecurityMode = (
548
+ "SIGNING_ENABLED+SIGNING_REQUIRED"
549
+ if self .session .SigningRequired
550
+ else "SIGNING_ENABLED"
551
+ ),
508
552
ServerTime = (time .time () + 11644473600 ) * 1e7 ,
509
553
ServerTimeZone = 0x3C ,
510
554
)
@@ -534,6 +578,11 @@ def on_negotiate(self, pkt):
534
578
)
535
579
self .send (resp )
536
580
581
+ @ATMT .state (final = 1 )
582
+ def NEGO_FAILED (self ):
583
+ self .vprint ("SMB Negotiate failed: encryption was not negotiated." )
584
+ self .end ()
585
+
537
586
@ATMT .state ()
538
587
def NEGOTIATED (self ):
539
588
pass
@@ -550,6 +599,17 @@ def update_smbheader(self, pkt):
550
599
self .smb_header .CreditCharge = pkt .CreditCharge
551
600
# If the packet has a NextCommand, set NextCompound to True
552
601
self .NextCompound = bool (pkt .NextCommand )
602
+ # [MS-SMB2] sect 3.3.4.1.1 - "If the request was signed by the client..."
603
+ # If the packet was signed, note we must answer with a signed packet.
604
+ if (
605
+ not self .session .SigningRequired
606
+ and pkt .SecuritySignature != b"\0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 "
607
+ ):
608
+ self .NextForceSign = True
609
+ # [MS-SMB2] sect 3.3.4.1.4 - "If the message being sent is any response to a
610
+ # client request for which Request.IsEncrypted is TRUE"
611
+ if pkt [SMB2_Header ]._decrypted :
612
+ self .NextForceEncrypt = True
553
613
# [MS-SMB2] sect 3.3.5.2.7.2
554
614
# Add SMB2_FLAGS_RELATED_OPERATIONS to the response if present
555
615
if pkt .Flags .SMB2_FLAGS_RELATED_OPERATIONS :
@@ -632,12 +692,19 @@ def on_setup_andx_request(self, pkt, ssp_blob):
632
692
):
633
693
# SMB1 extended / SMB2
634
694
if SMB2_Session_Setup_Request in pkt :
635
- # SMB2
636
695
resp = self .smb_header .copy () / SMB2_Session_Setup_Response ()
637
696
if self .GUEST_LOGIN :
697
+ # "If the security subsystem indicates that the session
698
+ # was established by a guest user, Session.SigningRequired
699
+ # MUST be set to FALSE and Session.IsGuest MUST be set to TRUE."
638
700
resp .SessionFlags = "IS_GUEST"
701
+ self .session .IsGuest = True
702
+ self .session .SigningRequired = False
639
703
if self .ANONYMOUS_LOGIN :
640
704
resp .SessionFlags = "IS_NULL"
705
+ # [MS-SMB2] sect 3.3.5.5.3
706
+ if self .session .Dialect >= 0x0300 and self .REQUIRE_ENCRYPTION :
707
+ resp .SessionFlags += "ENCRYPT_DATA"
641
708
else :
642
709
# SMB1 extended
643
710
resp = (
@@ -672,10 +739,23 @@ def on_setup_andx_request(self, pkt, ssp_blob):
672
739
)
673
740
if status == GSS_S_COMPLETE :
674
741
# Authentication was successful
675
- self .session .computeSMBSessionKey ( )
742
+ self .session .computeSMBSessionKeys ( IsClient = False )
676
743
self .authenticated = True
677
- # and send
744
+ # [MS-SMB2] Note: "Windows-based servers always sign the final session setup
745
+ # response when the user is neither anonymous nor guest."
746
+ # If not available, it will still be ignored.
747
+ self .NextForceSign = True
678
748
self .send (resp )
749
+ # Check whether we must enable encryption from now on
750
+ if (
751
+ self .authenticated
752
+ and not self .session .IsGuest
753
+ and self .session .Dialect >= 0x0300
754
+ and self .REQUIRE_ENCRYPTION
755
+ ):
756
+ # [MS-SMB2] sect 3.3.5.5.3: from now on, turn encryption on !
757
+ self .session .EncryptData = True
758
+ self .session .SigningRequired = False
679
759
680
760
@ATMT .condition (RECEIVED_SETUP_ANDX_REQUEST )
681
761
def wait_for_next_request (self ):
@@ -771,7 +851,9 @@ def receive_tree_connect(self, pkt):
771
851
def send_tree_connect_response (self , pkt , tree_name ):
772
852
self .update_smbheader (pkt )
773
853
# Check the tree name against the shares we're serving
774
- if not any (x ._name == tree_name .lower () for x in self .shares ):
854
+ try :
855
+ share = next (x for x in self .shares if x ._name == tree_name .lower ())
856
+ except StopIteration :
775
857
# Unknown tree
776
858
resp = self .smb_header .copy () / SMB2_Error_Response ()
777
859
resp .Command = "SMB2_TREE_CONNECT"
@@ -783,17 +865,32 @@ def send_tree_connect_response(self, pkt, tree_name):
783
865
self .tree_id += 1
784
866
self .smb_header .TID = self .tree_id
785
867
self .current_trees [self .smb_header .TID ] = tree_name
868
+
869
+ # Construct ShareFlags
870
+ ShareFlags = (
871
+ "AUTO_CACHING+NO_CACHING"
872
+ if self .current_tree () == "IPC$"
873
+ else self .TREE_SHARE_FLAGS
874
+ )
875
+ # [MS-SMB2] sect 3.3.5.7
876
+ if (
877
+ self .session .Dialect >= 0x0311
878
+ and not self .session .EncryptData
879
+ and share .encryptdata
880
+ ):
881
+ if not self .session .SupportsEncryption :
882
+ raise Exception ("Peer asked for encryption but doesn't support it !" )
883
+ ShareFlags += "+ENCRYPT_DATA"
884
+
786
885
self .vprint ("Tree Connect on: %s" % tree_name )
787
886
self .send (
788
- self .smb_header
887
+ self .smb_header . copy ()
789
888
/ SMB2_Tree_Connect_Response (
790
889
ShareType = "PIPE" if self .current_tree () == "IPC$" else "DISK" ,
791
- ShareFlags = "AUTO_CACHING+NO_CACHING"
792
- if self .current_tree () == "IPC$"
793
- else self .TREE_SHARE_FLAGS ,
794
- Capabilities = 0
795
- if self .current_tree () == "IPC$"
796
- else self .TREE_CAPABILITIES ,
890
+ ShareFlags = ShareFlags ,
891
+ Capabilities = (
892
+ 0 if self .current_tree () == "IPC$" else self .TREE_CAPABILITIES
893
+ ),
797
894
MaximalAccess = self .TREE_MAXIMAL_ACCESS ,
798
895
)
799
896
)
@@ -848,7 +945,11 @@ def send_ioctl_response(self, pkt):
848
945
SMB2_IOCTL_Validate_Negotiate_Info_Response (
849
946
GUID = self .GUID ,
850
947
DialectRevision = self .session .Dialect ,
851
- SecurityMode = self .session .SecurityMode ,
948
+ SecurityMode = (
949
+ "SIGNING_ENABLED+SIGNING_REQUIRED"
950
+ if self .session .SigningRequired
951
+ else "SIGNING_ENABLED"
952
+ ),
852
953
Capabilities = self .NegotiateCapabilities ,
853
954
),
854
955
)
0 commit comments