CyberSecurity Project: Transparent Filtering Bridge (+ Extras) 10.0

After getting the Transparent Filtering Bridge mostly functional in OPNSense 25.1.8 and 25.1.12, I had the pleasure of dealing with a major version update to 25.7.
Major software version changes always bring new challenges, but they also bring opportunities for learning... and pivoting in new directions.
Table of Contents
The WiFi Update

WiFi connectivity issues have been a staple of this project, so it was no real surprise that the update completely undid my custom configuration. Annoyingly, OPNSense had retained network connectivity after the update and for about an hour later. Then I had zero network connectivity to the miniPC after that point, forcing me to use the OPNSense shell to correct this.
However, running my setup_wpa
script resulted in a new error:

Inputting the error into ChatGPT to revise the initial revision of the script produced the following result:
#!/bin/sh
#
# PROVIDE: wpa_supplicant
# REQUIRE: DAEMON NETWORKING if_iwlwifi
# KEYWORD: shutdown
#
# Add the following to /etc/rc.conf to enable this service:
# wpa_supplicant_enable="YES"
# wpa_supplicant_iface="iwlwifi0_wlan0"
# wpa_supplicant_driver="bsd"
# wpa_supplicant_conf="/var/etc/wpa_supplicant_iwlwifi0_wlan0.conf"
#
. /etc/rc.subr # import rc_* functions
. /etc/network.subr # import network helper functions
name="wpa_supplicant"
rcvar="wpa_supplicant_enable"
# load overrides from rc.conf, set defaults
load_rc_config $name
: ${wpa_supplicant_enable:="YES"} # YES to start at boot
: ${wpa_supplicant_iface:="iwlwifi0_wlan0"} # logical WLAN interface
: ${wpa_supplicant_driver:="bsd"} # driver backend
: ${wpa_supplicant_conf:="/var/etc/wpa_supplicant_iwlwifi0_wlan0.conf"}
# path to the wpa_supplicant binary
command="/usr/sbin/wpa_supplicant"
# pidfile lives under /var/run/<name>.<iface>.pid
pidfile="/var/run/${name}.${wpa_supplicant_iface}.pid"
logfile="/var/log/wpa_supplicant.log"
# arguments to wpa_supplicant, add debug flags (-dd) for verbose logging
command_args="-dd -B \
-i ${wpa_supplicant_iface} \
-D ${wpa_supplicant_driver} \
-c ${wpa_supplicant_conf} \
-P ${pidfile} \
2>&1 | tee -a ${logfile}"
# override default actions
start_cmd="wpa_supplicant_start"
stop_cmd="wpa_supplicant_stop"
status_cmd="wpa_supplicant_status"
log() {
echo "$(date '+%F %T') [${name}] $*"
}
#
# start: load driver, create interface, bring it up, then start daemon
#
wpa_supplicant_start()
{
log "Starting wpa_supplicant service"
# 1) Load the iwlwifi kernel module if not already present
if ! kldstat -n if_iwlwifi > /dev/null 2>&1; then
kldload if_iwlwifi \
|| err 1 "Unable to load if_iwlwifi kernel module"
fi
# 2) Create the wlan interface if missing
log "Ensuring interface ${wpa_supplicant_iface} exists"
if ! ifconfig ${wpa_supplicant_iface} > /dev/null 2>&1; then
ifconfig iwlwifi0 create wlandev iwlwifi0 \
|| err 1 "Failed to create interface ${wpa_supplicant_iface}"
fi
# 3) Bring the wireless interface up
log "Bringing up ${wpa_supplicant_iface}"
ifconfig ${wpa_supplicant_iface} up \
|| err 1 "Could not bring up ${wpa_supplicant_iface}"
# 4) Handle stale ctrl_iface socket
ctrl_dir="/var/run/wpa_supplicant"
sock="${ctrl_dir}/${wpa_supplicant_iface}"
if [ -e "${sock}" ]; then
log "Found existing ctrl_iface socket ${sock}"
if [ -f "${pidfile}" ] && kill -0 "$(cat ${pidfile})" > /dev/null 2>&1; then
log "wpa_supplicant already running PID: $(cat ${pidfile})"
return 0
else
log "Removing stale socket ${sock}"
rm -f "${sock}" || err 1 "Failed to remove stale socket"
fi
fi
# 5) Start wpa_supplicant
log "Launching wpa_supplicant"
eval ${command} ${command_args} \
|| err 1 "wpa_supplicant failed to start"
# 6) Wait for pidfile
log "Waiting for PID file ${pidfile}"
for _ in $(seq 1 10); do
[ -f "${pidfile}" ] && break
sleep 0.2
done
[ -f "${pidfile}" ] || err 1 "PID file not created"
log "wpa_supplicant started PID: $(cat ${pidfile})"
}
#
# status: check the pidfile to see if wpa_supplicant is running
#
wpa_supplicant_status()
{
check_pidfile "${pidfile}" "${command}"
}
#
# stop: terminate the daemon and clean up the pidfile
#
wpa_supplicant_stop()
{
log "Stopping wpa_supplicant service"
if [ -f "${pidfile}" ]; then
kill -TERM "$(cat ${pidfile})" > /dev/null 2>&1 \
|| err 1 "Failed to kill wpa_supplicant PID: $(cat ${pidfile})"
rm -f "${pidfile}"
log "Removed PID file"
else
err 1 "PID file not found, is wpa_supplicant running?"
fi
}
run_rc_command "$1"
The revised code added a new block to handle the interface's socket and a new dedicated log file, useful for troubleshooting. The result?

In a massive surprise, this revised script seems to have solved the recurring, persistent loss of connection over time. The original version of the script got around three hours of connection time before issues would appear. Now the webGUI is accessible at just about anytime within the four hour window before the WiFi interface is rebooted, which is pretty close to the five 9s standard of accessibility (99.999% uptime).
It's not entirely clear why this is the case. Presumably, there's some sort of code interaction going on between the revised iwlwifi
driver and other parts of the FreeBSD OS that wasn't there in the OPNSense 25.1 OSes. This broke compatibility with the initial version of the script, but also possibly surfaced an issue that may have been present on those versions.
Reworking the Alias List
Since the examination of data from the week and a half stretch revealed that the existing alias lists weren't very useful, I decided to look up more effective lists.
Doing a bit of light research on r/OPNSense for more blocklists led me to this post, which in turn led to a Github repository full of IP blocklists. A number of these were not accessible as files for OPNSense to download, so that narrowed down the total number a fair bit.
Of the various lists available for OPNSense to download, most came with 1 day/7 day/30 day variants. With the various monitoring periods comes a highly variable amount of IPs in each blocklist. Unsurprisingly, the longer periods have a longer list, but are less "fresh", even with update cadences that are in the 10s of minutes.

These new alias lists push the total number of blocked IPs to 15% of the supported total. Based on my observations, the typical RAM use of the miniPC is at around 10-11%, so there's plenty of room for expansion, although it would probably be best to max out at 50% RAM utilization. That would provide a decent amount of head room for a sudden utilization spike, while also allowing for an absurd amount of IPs being blocked.
Improving the Rules List

Improving the Rules List was a natural next step. The log data analysis from before the OPNSense 25.7 was complicated a bit by the fact that the vast majority of blocking was done by a default rule I couldn't identify. Simply examining one noted spike in traffic was an exercise in trying to find a needle of interest in a haystack of benign (if unnecessary) traffic.
With the new Alias lists, I set up a number of new rules to block incoming and outgoing traffic. With 29 manually defined rules, plus the 13 auto generated rules, this should provide me with enough granularity to block potential threats before they hit the default rules.
Unfortunately, with the variable size of each Alias list, there's a possibility that one or two lists will dominate the reports, especially with the unoptimized order of this rule list.
Suricata Detections

Out of curiosity, I checked the Suricata alerts. This time, I found an interesting one, a TFTP attempt to grab passwd
files.
Fortiguard Labs has a good explanation of this attack:
Description
It indicates an attempt to access the password file on a host running the Trivial File Transfer Protocol (TFTP) service.
There is a long-known security hole in TFTP that allows any unauthenticated user to read any readable files and to write any writable files on a remote system. Attackers can take advantage of this to get sensitive password information.
It's somewhat annoying that the ISP doesn't block TFTP on sheer principle, since it's an insecure protocol that most non-commercial customers will never use.
However, since Suricata dropped the traffic, no harm was done.
VirusTotal does have detections associated with this IP (195.3.223.82):
Automatically downvoted: This IP raised 1 Successful administrator privilege gain alerts on our side.
This was reported 48 hours prior to my accessing of the VirusTotal page.
The most interesting thing about this detection is that it happened at the beginning of the month. The NTP DOS scan my network encountered was at the end of June/beginning of July. It may be a coincidence, but it could also be one of a few things:
- Preference to scan for harvestable credentials at the start of some month.
- A more long term credential harvesting scan that is just cycling through to my IP range at the start of August.
- Some sort of detection on the attacker's end triggering an attempt to harvest credentials.
Unfortunately, due to an OPNSense bug, I couldn't download the logs with that IP address, but I could take a screencap of the time window of the detection:

Interestingly, the TFTP credential harvesting attempt seemed to be part of a broader sweep/scan for any vulnerabilities. A majority of the entries were focused on DNS, suggesting a possible DNS poisoning/manipulation attempt.
Unfortunately, it's not possible to associate the Suricata alert with a specific policy, so it's hard to pinpoint why this one specific blocked connection was flagged.
Next Steps
The next steps are pretty simple. Collect more data, make sure everything keeps running smoothly.
Normally, I do a bit more than that, but I have a tech project coming up that dovetails neatly into this one...
I'll be setting up a new AI focused miniPC and doing comparative tests of local AI versus ChatGPT regarding log analysis.