Skip to content

Patch the Planet: A guest flag on the final SMB login message switches off required encryption

Moderate
gpotter2 published GHSA-38xg-rhhx-hjfm Sep 8, 2026

Package

pip scapy (pip)

Affected versions

<2.8.0

Patched versions

2.8.0

Description

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:

python reproduce.py

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.

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
High
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N

CVE ID

No known CVE

Weaknesses

Insufficient Verification of Data Authenticity

The product does not sufficiently verify the origin or authenticity of data, in a way that causes it to accept invalid data. Learn more on MITRE.

Credits