The USB attack surface is one of the most underestimated risks in Linux server security — and honestly, I get why. When you're defending a production server, your mind goes straight to SSH configs, firewall rules, and network segmentation. Physical ports feel like someone else's problem. But they're not. While most hardening guides focus on network-facing threats, the USB ports on the majority of production servers are sitting wide open. A single rogue device — a reprogrammed flash drive, a BadUSB cable, or a HID-injecting Rubber Ducky — can bypass your firewall, IDS, and SSH hardening entirely, because the attack doesn't come from the network at all.
This guide walks through a layered USB security strategy for Linux: kernel module blacklisting, udev rules for device-level control, and full USBGuard policy enforcement with audit logging integrated throughout. By the end you'll have a defense-in-depth posture that's documented, auditable, and enforceable across your fleet.
Understanding the Linux USB Threat Landscape in 2026
USB threats have evolved well beyond the classic "someone plugged in a dodgy drive" scenario. In 2026, the attack surface looks like this:
- BadUSB / HID injection: Devices like the Rubber Ducky, O.MG Cable, and Flipper Zero present themselves as USB keyboards and inject keystrokes at machine speed — bypassing network-level controls entirely because the attack originates from a trusted local input device.
- USB mass storage exfiltration: Anyone with physical access and an uncontrolled USB port can copy gigabytes of data in minutes without triggering any network-based DLP.
- BadCam remote firmware attacks: Eclypsium researchers demonstrated in 2025 that Linux-based webcams can be remotely reflashed over a network connection, repurposing the device as a BadUSB attacker that survives OS reinstallation — the payload lives in peripheral firmware.
- Rogue network adapters: USB-to-Ethernet or RNDIS devices that silently add new network interfaces, reroute traffic, or create covert channels.
- Combo-class attacks: Modern BadUSB devices present multiple USB interface classes simultaneously — storage + HID, or network + HID. A device advertising both
03:*:* (HID) and 08:*:* (mass storage) is a classic indicator of a malicious device, because legitimate keyboards are just keyboards.
The root cause is that default Linux configurations automatically authorize every USB device the kernel enumerates. The kernel doesn't distinguish a genuine input device from one masquerading as one. Closing that gap requires layered controls in userspace and kernel space working together — which is exactly what we'll build here. For a broader foundation, it's worth pairing this with Linux kernel hardening via sysctl security parameters, which addresses complementary kernel-level attack surfaces.
Layer 1: Kernel Module Blacklisting
The fastest baseline control for servers is blocking the usb-storage kernel module from loading. This prevents USB mass storage devices from functioning at the kernel level — before udev, USBGuard, or any userspace process is even involved. It's a blunt instrument, but for servers that have no business accepting removable storage, that's exactly the point.
# Blacklist the usb-storage module
echo "blacklist usb-storage" | sudo tee /etc/modprobe.d/blacklist-usb-storage.conf
# Rebuild initramfs so the blacklist takes effect at next boot
sudo update-initramfs -u # Debian / Ubuntu
sudo dracut -f # RHEL / Fedora / AlmaLinux
Verify the blacklist is in effect:
sudo modprobe -n -v usb-storage
# Expected output: install /bin/false
lsmod | grep usb_storage
# Expected: no output (module not loaded)
For environments where some USB storage is needed selectively, skip the global blacklist and rely on USBGuard rules instead (covered in Layer 3). For air-gapped servers and CI/CD runner nodes that never require removable storage, this blacklist should be considered mandatory baseline — full stop.
Layer 2: udev Rules for Real-Time Device Control
The udev subsystem processes kernel device events and executes rules as devices appear. udev rules operate below USBGuard and can enforce device authorization at the sysfs level, making them a useful complement — particularly for logging every USB event to the system audit trail. So, let's wire that up.
Inspecting Device Attributes
# List connected USB devices with vendor/product IDs
lsusb
# Example: Bus 001 Device 003: ID 046d:c52b Logitech, Inc. MK345 Combo Receiver
# Get full udev attributes for a specific device node
udevadm info -a -n /dev/bus/usb/001/003 | grep -E "idVendor|idProduct|bDeviceClass|serial"
Key USB Interface Class Codes
| Class | Code | Server Policy |
| Mass Storage | 08 | Block on servers |
| HID (keyboard / mouse) | 03 | Allow only explicitly known devices |
| Hub | 09 | Allow (required for multi-port operation) |
| CDC Network (RNDIS) | 02 / E0 | Block unauthorized adapters |
| Vendor-specific | FF | Block by default |
Creating a udev USB Policy
Create /etc/udev/rules.d/01-usb-policy.rules with the following content:
SUBSYSTEM=="usb", ACTION=="add", \
RUN+="/usr/bin/logger -t usb-audit vendor=%s{idVendor} product=%s{idProduct} serial=%s{serial} class=%s{bDeviceClass}"
SUBSYSTEM=="usb", ACTION=="remove", \
RUN+="/usr/bin/logger -t usb-audit USB-removed vendor=%s{idVendor} product=%s{idProduct}"
# Deny-by-default on all USB host controllers
SUBSYSTEM=="usb", DRIVER=="usb", ATTR{authorized_default}="0"
# Always allow USB hubs (class 09) so multi-port devices work
SUBSYSTEM=="usb", ATTR{bDeviceClass}=="09", ATTR{authorized}="1"
# Alert when any HID device appears on a headless server
SUBSYSTEM=="usb", ATTR{bDeviceClass}=="03", ACTION=="add", \
RUN+="/usr/bin/logger -p auth.alert -t hid-alert ALERT-HID-connected manufacturer=%s{manufacturer} product=%s{product}"
Reload and apply the rules:
sudo udevadm control --reload-rules
sudo udevadm trigger
With authorized_default=0 set on the USB host controller, the kernel will enumerate devices but not authorize any of them for use. USBGuard then takes over to grant authorization based on your policy rules. This hand-off between layers is what makes the whole approach robust.
Layer 3: USBGuard — Policy-Based USB Authorization
USBGuard is the authoritative enforcement layer. It implements a policy engine on top of the Linux USB device authorization mechanism and provides an IPC interface for runtime management, a structured rule language, and pluggable audit backends. Think of it as a firewall, but for USB ports.
Installation
# Debian / Ubuntu
sudo apt install usbguard usbutils
# RHEL / Fedora / AlmaLinux / Rocky Linux
sudo dnf install usbguard usbutils
Step 1: Generate a Baseline Policy Before Starting the Daemon
Critical: Always generate the baseline policy from your currently connected, known-good devices before enabling the daemon. If you start USBGuard with an empty or missing rules file, all USB devices — including your keyboard and mouse — will be blocked immediately. I've seen this catch people off guard on remote servers (it's not fun to recover from).
# Must use sudo sh -c to handle redirect permissions correctly.
# A bare "sudo usbguard generate-policy >" fails because the shell
# redirection runs as your non-root user, not as root.
sudo sh -c "usbguard generate-policy > /etc/usbguard/rules.conf"
# Review what was generated
cat /etc/usbguard/rules.conf
Example output after generation on a server with a USB hub and one keyboard:
allow id 1d6b:0002 serial "" name "xHCI Host Controller" hash "..." with-interface 09:00:00
allow id 046d:c52b serial "" name "USB Receiver" hash "..." with-interface { 03:01:01 03:00:00 }
Step 2: Configure the Daemon
Edit /etc/usbguard/usbguard-daemon.conf:
# Block any device that does not match a rule
ImplicitPolicyTarget=block
# Apply the policy to devices already connected at daemon start
PresentDevicePolicy=apply-policy
# Apply the policy to devices inserted while the daemon is running
InsertedDevicePolicy=apply-policy
# Integrate with Linux Audit (auditd) for centralized event logging
AuditBackend=LinuxAudit
# Lock down IPC to root only — CRITICAL security control.
# Leaving this unconfigured exposes the interface to all local users.
IPCAllowedUsers=root
IPCAllowedGroups=wheel
Step 3: Enable and Start USBGuard
sudo systemctl enable --now usbguard
sudo systemctl status usbguard
Writing Advanced USBGuard Rules
Beyond the auto-generated baseline, you can write precise rules to address specific threat scenarios. Place additional rules in /etc/usbguard/rules.d/ — files are loaded in alphanumeric order, so prefix them with a two-digit number:
# /etc/usbguard/rules.d/10-server-policy.conf
# Block all mass storage devices (interface class 08)
block with-interface equals { 08:*:* }
# Reject any device presenting as both HID and storage — classic BadUSB indicator
reject with-interface all-of { 03:*:* 08:*:* }
# Reject any device presenting HID plus a network interface — another BadUSB pattern
reject with-interface all-of { 03:*:* 02:*:* }
# Reject (remove handle entirely) devices using vendor-specific interfaces
reject with-interface equals { ff:*:* }
# Allow only a known, serialized keyboard by vendor:product and serial number
allow id 046d:c52b serial "001CC0EC3A28E761F105" name "USB Receiver" \
with-interface { 03:01:01 03:00:00 }
Rule target semantics — this distinction matters a lot in practice:
allow — authorize the device; drivers load normally.
block — deauthorize the device; it's physically connected but inaccessible to the OS and re-evaluated when policy changes.
reject — deauthorize and remove the device handle; the device must be physically disconnected and reconnected before USBGuard re-evaluates it. Use this for definitively malicious device classes.
The reject target is the strongest response and is appropriate for any device class that should never appear on a server — vendor-specific interfaces, combo HID+storage devices, and unauthorized RNDIS adapters. When in doubt, reject rather than block.
Audit Logging and SIEM Integration
Enforcement without visibility is incomplete. Configure layered logging to ensure every USB event is captured and forwarded to your centralized logging stack. This integrates naturally with the broader multi-layer Linux intrusion detection system patterns covering auditd, Wazuh, and Suricata — USB events feed into the same pipeline you're likely already running.
auditd Rules for USB Events
# Add to /etc/audit/rules.d/usb.rules
-w /dev/bus/usb -p wa -k usb_access
# Reload audit rules
sudo augenrules --load
# Search auditd logs for USB events
sudo ausearch -k usb_access --start today | aureport -f -i
Querying USBGuard at Runtime
# List all enumerated devices and their authorization state
sudo usbguard list-devices
# List the active policy rules
sudo usbguard list-rules
# Stream real-time policy decisions (useful during testing)
sudo usbguard watch
# Query the USBGuard file audit log (when using FileAudit backend)
sudo tail -f /var/log/usbguard/usbguard-audit.log
Forwarding USB Events to Wazuh / Elastic SIEM
Configure the Wazuh agent to monitor the USBGuard audit log by adding to /var/ossec/etc/ossec.conf:
<localfile>
<log_format>syslog</log_format>
<location>/var/log/usbguard/usbguard-audit.log</location>
</localfile>
Add a custom Wazuh rule in /var/ossec/etc/rules/local_rules.xml to generate a level-10 alert on blocked devices:
<group name="usb,usbguard,">
<rule id="100500" level="10">
<decoded_as>json</decoded_as>
<field name="type">^DevicePolicy$</field>
<field name="target">^block|^reject</field>
<description>USBGuard blocked or rejected a USB device</description>
<group>hardware,policy_changed</group>
</rule>
</group>
Server Hardening Automation Script
The following script applies the complete layered USB defense to a production Linux server. Run it as root on a system where your known-good devices are already connected:
#!/bin/bash
# Linux USB security hardening — apply layered defense
set -euo pipefail
# 1. Blacklist usb-storage kernel module
echo "blacklist usb-storage" > /etc/modprobe.d/blacklist-usb-storage.conf
# 2. Set authorized_default=0 on all USB host controllers via udev
cat > /etc/udev/rules.d/00-usb-default-deny.rules <<'EOF'
SUBSYSTEM=="usb", DRIVER=="usb", ATTR{authorized_default}="0"
SUBSYSTEM=="usb", ACTION=="add", RUN+="/usr/bin/logger -t usb-audit vendor=%s{idVendor} product=%s{idProduct}"
EOF
udevadm control --reload-rules
# 3. Generate USBGuard baseline from currently connected devices
sh -c "usbguard generate-policy --no-hashes > /etc/usbguard/rules.conf"
# 4. Harden USBGuard daemon configuration
sed -i \
-e 's/^ImplicitPolicyTarget=.*/ImplicitPolicyTarget=block/' \
-e 's/^#\?IPCAllowedUsers=.*/IPCAllowedUsers=root/' \
-e 's/^#\?AuditBackend=.*/AuditBackend=LinuxAudit/' \
/etc/usbguard/usbguard-daemon.conf
# 5. Add server-specific deny rules for BadUSB combo-class devices and mass storage
cat > /etc/usbguard/rules.d/10-server-deny.conf <<'EOF'
block with-interface equals { 08:*:* }
reject with-interface all-of { 03:*:* 08:*:* }
reject with-interface all-of { 03:*:* 02:*:* }
reject with-interface equals { ff:*:* }
EOF
# 6. Enable USBGuard
systemctl enable --now usbguard
# 7. Add auditd USB monitoring rule
echo "-w /dev/bus/usb -p wa -k usb_access" >> /etc/audit/rules.d/usb.rules
augenrules --load
# 8. Rebuild initramfs to persist the module blacklist across reboots
if command -v update-initramfs >/dev/null 2>&1; then
update-initramfs -u
elif command -v dracut >/dev/null 2>&1; then
dracut -f
fi
echo "USB hardening complete. Verify with: sudo usbguard list-devices"
Temporarily Authorizing a Known Device
Legitimate scenarios — hardware maintenance, BIOS updates, emergency console access — sometimes require temporarily authorizing a specific USB device without permanently modifying the policy. Combining this with systemd service sandboxing ensures the USBGuard daemon itself runs with minimal privileges and can't be abused through its IPC interface.
# List devices and find the numeric ID of the device to allow
sudo usbguard list-devices
# Example output: 7: block id 0951:1666 serial "001CC0EC3A28E761F1050036" ...
# Temporarily allow device 7 for this session only (does not modify rules.conf)
sudo usbguard allow-device 7
# To permanently add it to the policy:
sudo usbguard allow-device 7 -p
Verifying Your USB Security Posture
Once you've deployed, verify everything is actually working — don't just assume the daemon started successfully means you're protected.
# Confirm USBGuard is active and listing rules
sudo systemctl is-active usbguard
sudo usbguard list-rules
# Confirm authorized_default=0 on all host controllers
for ctrl in /sys/bus/usb/devices/usb*/authorized_default; do
echo "$ctrl: $(cat $ctrl)"
done
# All values should be: 0
# Confirm usb-storage module is blacklisted
sudo modprobe -n -v usb-storage
# Expected: install /bin/false
# Check USBGuard policy decisions from the last hour
sudo journalctl -u usbguard --since "1 hour ago"
# Confirm auditd is watching the USB bus
sudo auditctl -l | grep usb
If any of those checks fail, don't move on — fix the gap first. A partial USB hardening posture gives a false sense of security while leaving real attack vectors open.
Frequently Asked Questions
Will USBGuard block my keyboard and mouse when I first enable it?
It will if you start the daemon without a rules file. Always run sudo sh -c "usbguard generate-policy > /etc/usbguard/rules.conf" while your keyboard and mouse are connected before enabling the USBGuard service. The generate-policy command creates allow rules for all currently connected devices so they continue working after the daemon starts enforcing policy.
What is the difference between "block" and "reject" in USBGuard rules?
block deauthorizes the device — it remains physically connected but the OS can't use it, and it'll be re-evaluated when the policy changes or the daemon restarts. reject deauthorizes the device and removes its kernel handle entirely, forcing a physical disconnect and reconnect before USBGuard will evaluate it again. Use reject for device classes that should never appear on a server, such as combo HID+storage devices.
Can USBGuard fully protect against BadUSB and HID injection attacks?
USBGuard significantly raises the bar by enforcing an explicit allowlist of authorized HID devices — any new keyboard-like device is blocked by ImplicitPolicyTarget=block. A sophisticated attacker who clones the exact vendor ID, product ID, and serial number of a whitelisted keyboard could bypass rule matching, so USBGuard should be layered with physical port controls, device serial binding, and audit alerting for maximum protection.
How do I temporarily allow a USB device without permanently changing the policy?
Use sudo usbguard list-devices to find the numeric device ID, then run sudo usbguard allow-device <id> to authorize it for the current daemon session only. Without the -p flag, the change isn't written to rules.conf and reverts when the USBGuard daemon restarts.
How do I integrate USB security events into my SIEM?
Set AuditBackend=LinuxAudit in /etc/usbguard/usbguard-daemon.conf to route USBGuard events through auditd, which your SIEM agent already reads from /var/log/audit/audit.log. Supplement this with a udev rule calling logger on every USB insertion, adding a syslog record with vendor ID, product ID, and serial for forensic correlation.