What happens
A network attacker can make Scapy's SMB client send a file transfer in the clear even though the
caller explicitly asked for encryption.
The attacker sits between the client and the server and passes the connection through. Real
authentication still succeeds, and the attacker never learns a password or a key. What the
attacker
changes is one flag in the final "you are logged in" message, which claims the login was a guest
login. Scapy reacts by switching off both signing and encryption for the rest of the session.
On commit 1f870205baae8baf1718bc20700d0d9ffc0e4324, a file read then crossed the attacker in 10
plaintext frames. Changing only that one flag back made the same transfer use 10 encrypted frames.
Related to GHSA-56fg-jjc2-xv49, but a different defect. That report covers a server sending the
wrong response during an already-encrypted download, and its fix is on the download path. This one
removes the protection earlier, at login, before any download starts, and needs its own guard in
receive_session_setup_response(). Filing it separately so the two fixes stay separately
reviewable; close it as a duplicate if you would rather fold it in.
Background — what this code does
SMB signing authenticates messages, and SMB encryption hides their commands and contents from the
network. SMB_Client exposes REQUIRE_SIGNATURE and REQUIRE_ENCRYPTION options so a caller can
refuse a session that lacks those properties
(scapy/layers/smbclient.py:123,
scapy/layers/smbclient.py:136).
The final SessionSetup response states whether authentication produced a guest session. Scapy
receives that response before it has derived the SMB signing and encryption keys, then computes
those keys after accepting the response flags
(scapy/layers/smbclient.py:565,
scapy/layers/smbclient.py:637).
How the code is reached
The affected path is the ordinary high-level smbclient() workflow with an authenticated SSP and
REQUIRE_SIGNATURE=True or REQUIRE_ENCRYPTION=True. The demonstrated relay also changes the
negotiate request sent to the server from required to optional signing. The server must permit
optional signing; a server that independently requires signing rejects the later unsigned request.
Why it matters
The caller selected required encryption specifically to keep file names, operations, and contents
from a network observer. After the guest mutation, Scapy still holds valid signing and encryption
keys but uses neither one. The demonstrated relay read guest-flag-policy-secret from the
server's file response without knowing either key.
Reproduce it
Save the attached reproducer as reproduce.py and run:
Expected result on the affected commit:
{
"trigger": {
"boundary_held": false,
"guest_flag_mutated": true,
"download_succeeded": true,
"proxy_saw_secret": true,
"post_auth_plaintext_frames": 10,
"post_auth_encrypted_frames": 0,
"is_guest": true,
"encrypt_data": false,
"has_encryption_key": true
},
"control": {
"boundary_held": true,
"guest_flag_mutated": false,
"download_succeeded": true,
"proxy_saw_secret": false,
"post_auth_plaintext_frames": 0,
"post_auth_encrypted_frames": 10,
"is_guest": false,
"encrypt_data": true,
"has_encryption_key": true
}
}
Confirmed with Scapy 2.7.1rc1.post100, Python 3.13.14, default Scapy configuration, and
conf.debug_dissector = 0.
Where it goes wrong
SMBStreamSocket.recv() at
scapy/layers/smb2.py:3843
verifies incoming messages only when self.session.SigningKey is already present. The key is not
installed while the final SessionSetup response is being received.
receive_session_setup_response() at
scapy/layers/smbclient.py:599
then trusts the guest flag first:
if pkt.SessionFlags.IS_GUEST:
self.session.IsGuest = True
self.session.SigningRequired = False
elif self.session.Dialect >= 0x0300:
if pkt.SessionFlags.ENCRYPT_DATA or self.REQUIRE_ENCRYPTION:
self.session.EncryptData = True
self.session.SigningRequired = False
The elif means a guest flag does more than disable signing: it also skips the branch that honors
REQUIRE_ENCRYPTION. The client derives its valid session keys only afterward at
scapy/layers/smbclient.py:648,
after the protection decision has already been made.
Suggested fix
This follows the shape already accepted for the server-side equivalent: consult the transport
protection the caller asked for and refuse early, rather than introducing a new mechanism.
@@ -597,6 +597,9 @@ class SMB_Client(Automaton):
if SMB2_Session_Setup_Response in pkt:
# [MS-SMB2] sect 3.2.5.3.1
if pkt.SessionFlags.IS_GUEST:
+ if self.REQUIRE_SIGNATURE or self.REQUIRE_ENCRYPTION:
+ self.ErrorStatus = "Guest session lacks required protection"
+ raise self.AUTH_FAILED()
A guest session cannot satisfy an explicit signing or encryption requirement, so the client should
reject that incompatible result before sending application requests. Guest sessions remain valid
when the caller did not request either property.
The regression is in the existing test/scapy/layers/smbclientserver.uts suite and defines its
helper inside the test. With only the regression added, the new test fails; with the three-line
fix, it passes and also confirms that an ordinary guest session still succeeds. The full BSD test
profile was identical before and after: 291 passed and the same three unrelated
interactive/startup tests failed. Scapy's own flake8, mypy and codespell checks report the
same result with this patch applied as without it.
Performance impact of the fix, measured on one computer by running the same test before and after:
an ordinary non-guest login took 5,770 ns before and 5,813 ns after, 0.7% slower — a difference of
42 ns. Repeat runs of that test moved by about 2%, so that difference is smaller than the test can
distinguish.
Affected
- Package: Scapy · Branch:
master
- Confirmed on: commit
1f870205baae8baf1718bc20700d0d9ffc0e4324,
version 2.7.1rc1.post100
- Severity: High —
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N (5.9). Scapy grades
disclosure of data the caller asked to have encrypted as high. The attacker needs an active
relay and has to make two coordinated changes to the handshake, which is what holds the numeric
score below the grade.
- CWE: CWE-345 — Insufficient Verification of Data Authenticity
Credit
Reported by: Clinton Thomas (@KernelClint) of Trail of Bits, in collaboration with OpenAI.
Found with GPT-5.6-Cyber as part of the Patch the Planet security initiative.
What happens
A network attacker can make Scapy's SMB client send a file transfer in the clear even though the
caller explicitly asked for encryption.
The attacker sits between the client and the server and passes the connection through. Real
authentication still succeeds, and the attacker never learns a password or a key. What the
attacker
changes is one flag in the final "you are logged in" message, which claims the login was a guest
login. Scapy reacts by switching off both signing and encryption for the rest of the session.
On commit
1f870205baae8baf1718bc20700d0d9ffc0e4324, a file read then crossed the attacker in 10plaintext frames. Changing only that one flag back made the same transfer use 10 encrypted frames.
Related to GHSA-56fg-jjc2-xv49, but a different defect. That report covers a server sending the
wrong response during an already-encrypted download, and its fix is on the download path. This one
removes the protection earlier, at login, before any download starts, and needs its own guard in
receive_session_setup_response(). Filing it separately so the two fixes stay separatelyreviewable; close it as a duplicate if you would rather fold it in.
Background — what this code does
SMB signing authenticates messages, and SMB encryption hides their commands and contents from the
network.
SMB_ClientexposesREQUIRE_SIGNATUREandREQUIRE_ENCRYPTIONoptions so a caller canrefuse a session that lacks those properties
(
scapy/layers/smbclient.py:123,scapy/layers/smbclient.py:136).The final SessionSetup response states whether authentication produced a guest session. Scapy
receives that response before it has derived the SMB signing and encryption keys, then computes
those keys after accepting the response flags
(
scapy/layers/smbclient.py:565,scapy/layers/smbclient.py:637).How the code is reached
The affected path is the ordinary high-level
smbclient()workflow with an authenticated SSP andREQUIRE_SIGNATURE=TrueorREQUIRE_ENCRYPTION=True. The demonstrated relay also changes thenegotiate request sent to the server from required to optional signing. The server must permit
optional signing; a server that independently requires signing rejects the later unsigned request.
Why it matters
The caller selected required encryption specifically to keep file names, operations, and contents
from a network observer. After the guest mutation, Scapy still holds valid signing and encryption
keys but uses neither one. The demonstrated relay read
guest-flag-policy-secretfrom theserver's file response without knowing either key.
Reproduce it
Save the attached reproducer as
reproduce.pyand run:Expected result on the affected commit:
{ "trigger": { "boundary_held": false, "guest_flag_mutated": true, "download_succeeded": true, "proxy_saw_secret": true, "post_auth_plaintext_frames": 10, "post_auth_encrypted_frames": 0, "is_guest": true, "encrypt_data": false, "has_encryption_key": true }, "control": { "boundary_held": true, "guest_flag_mutated": false, "download_succeeded": true, "proxy_saw_secret": false, "post_auth_plaintext_frames": 0, "post_auth_encrypted_frames": 10, "is_guest": false, "encrypt_data": true, "has_encryption_key": true } }Confirmed with Scapy
2.7.1rc1.post100, Python3.13.14, default Scapy configuration, andconf.debug_dissector = 0.Where it goes wrong
SMBStreamSocket.recv()atscapy/layers/smb2.py:3843verifies incoming messages only when
self.session.SigningKeyis already present. The key is notinstalled while the final SessionSetup response is being received.
receive_session_setup_response()atscapy/layers/smbclient.py:599then trusts the guest flag first:
The
elifmeans a guest flag does more than disable signing: it also skips the branch that honorsREQUIRE_ENCRYPTION. The client derives its valid session keys only afterward atscapy/layers/smbclient.py:648,after the protection decision has already been made.
Suggested fix
This follows the shape already accepted for the server-side equivalent: consult the transport
protection the caller asked for and refuse early, rather than introducing a new mechanism.
A guest session cannot satisfy an explicit signing or encryption requirement, so the client should
reject that incompatible result before sending application requests. Guest sessions remain valid
when the caller did not request either property.
The regression is in the existing
test/scapy/layers/smbclientserver.utssuite and defines itshelper inside the test. With only the regression added, the new test fails; with the three-line
fix, it passes and also confirms that an ordinary guest session still succeeds. The full BSD test
profile was identical before and after: 291 passed and the same three unrelated
interactive/startup tests failed. Scapy's own
flake8,mypyandcodespellchecks report thesame result with this patch applied as without it.
Performance impact of the fix, measured on one computer by running the same test before and after:
an ordinary non-guest login took 5,770 ns before and 5,813 ns after, 0.7% slower — a difference of
42 ns. Repeat runs of that test moved by about 2%, so that difference is smaller than the test can
distinguish.
Affected
master1f870205baae8baf1718bc20700d0d9ffc0e4324,version
2.7.1rc1.post100CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N(5.9). Scapy gradesdisclosure of data the caller asked to have encrypted as high. The attacker needs an active
relay and has to make two coordinated changes to the handshake, which is what holds the numeric
score below the grade.
Credit
Reported by: Clinton Thomas (@KernelClint) of Trail of Bits, in collaboration with OpenAI.
Found with GPT-5.6-Cyber as part of the Patch the Planet security initiative.