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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions