Skip to content

ForwardMachine: UnboundLocalError in cb_sni when crtfile/keyfile is provided - TLS interception completely broken with user certificates #5047

Description

@SunandM

Title: ForwardMachine: UnboundLocalError in cb_sni when crtfile/keyfile is provided - TLS interception completely broken with user certificates

## Summary

ForwardMachine with tls=True and user-provided crtfile/keyfile parameters is completely non-functional in Scapy 2.7.1. Every TLS connection attempt crashes silently with UnboundLocalError in the cb_sni callback, causing [SSL: CALLBACK_FAILED] on the client side.

## Affected version

Scapy 2.7.1 (released July 10, 2026) - scapy/fwdmachine.py line 398

## Root cause

In 'ForwardMachine.handler().cb_sni(), the variable passwordis only assigned inside theif self.crtfile is Nonebranch (self-signed mode). Whencrtfileis provided, theelsebranch runs but never assignspassword. Line 398 then references password` unconditionally in both branches:

if self.crtfile is None:
    # SELF-SIGNED mode
    ...
    password = os.urandom(32)   # <-- only assigned here
    ...
else:
    certfile = self.crtfile
    keyfile = self.keyfile
    # password is NEVER assigned here

sslcontext.load_cert_chain(certfile, keyfile, password=password)  # line 398
#                                                        ^^^^^^^^
#                                            UnboundLocalError when crtfile provided

## Reproduce

# Generate a test certificate
openssl req -x509 -newkey rsa:2048 -keyout /tmp/k.key -out /tmp/c.crt -days 1 -nodes -subj "/CN=test"
import threading, socket, ssl, time
from scapy.fwdmachine import ForwardMachine
from scapy.layers.http import HTTP

# Start a dummy TLS remote for cb_sni to connect to
def remote():
    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    ctx.load_cert_chain("/tmp/c.crt", "/tmp/k.key")
    srv = socket.socket()
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind(("127.0.0.1", 19444))
    srv.listen(5)
    while True:
        try:
            conn, _ = srv.accept()
            ctx.wrap_socket(conn, server_side=True).close()
        except: pass

threading.Thread(target=remote, daemon=True).start()

**# Patch**

 _getpeersock to use local dummy remote
def patched(self, dest, server_hostname=None):
    s = socket.socket()
    s.settimeout(3)
    s.connect(("127.0.0.1", 19444))
    return s
ForwardMachine._getpeersock = patched

def run():
    fm = ForwardMachine(
        mode=ForwardMachine.MODE.SERVER,
        port=19443, cls=HTTP, tls=True,
        crtfile="/tmp/c.crt",
        keyfile="/tmp/k.key",
        remote_address="127.0.0.1", timeout=3,
    )
    fm.run()

threading.Thread(target=run, daemon=True).start()
time.sleep(0.5)

ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
s = socket.create_connection(("127.0.0.1", 19443), timeout=5)
ctx.wrap_socket(s, server_hostname="example.com")

## Actual output

Exception ignored in: <function ForwardMachine.handler..cb_sni at 0x...>
Traceback (most recent call last):
File ".../scapy/fwdmachine.py", line 398, in cb_sni
sslcontext.load_cert_chain(certfile, keyfile, password=password)
UnboundLocalError: cannot access local variable 'password' where it is not associated with a value
[SSL: CALLBACK_FAILED] callback failed (_ssl.c:1033)

## Fix

Add password = None in the else branch:

else:
    certfile = self.crtfile
    keyfile = self.keyfile
    password = None  # <-- add this line

## Additional notes

The self-signed mode works fine, but the custom cert path is broken. Happy to open a PR if that helps.

Scapy 2.7.1, Python 3.13, Kali Linux.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions