Users Online
· Members Online: 0
· Total Members: 229
· Newest Member: Zarfdrilhor
Forum Threads
Latest Articles
Articles Hierarchy
Disabling USB Ports for Data Copying
Disabling USB Ports for Data Copying
This is a common enterprise Data Loss Prevention (DLP) control. Here are methods for both platforms:
Windows
Method 1: Group Policy (GPO) — Recommended for Enterprises
Computer Configuration →
Administrative Templates →
System →
Removable Storage Access
Set these policies to Enabled/Deny:
Removable Disks: Deny read accessRemovable Disks: Deny write access← blocks copying to USBAll Removable Storage classes: Deny all access← blocks everything
Apply via GPO to push across all domain-joined machines.
Method 2: Registry Edit
reg
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\USBSTOR]
"Start"=dword:00000004
|
Value |
Meaning |
|
|
USB storage enabled |
|
|
USB storage disabled |
Via Command Prompt (Admin):
cmd
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\USBSTOR" /v Start /t REG_DWORD /d 4 /f
Re-enable:
cmd
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\USBSTOR" /v Start /t REG_DWORD /d 3 /f
Method 3: Device Manager (Per Machine)
- Open Device Manager
- Expand Universal Serial Bus controllers
- Right-click each USB Mass Storage Device → Disable device
⚠️ This disables the device but doesn't prevent re-enabling by users with admin rights.
Method 4: PowerShell (Bulk Deployment)
powershell
# Disable USB storage
Set-ItemProperty-Path"HKLM:\SYSTEM\CurrentControlSet\Services\USBSTOR"`
-Name"Start"-Value 4
# Verify
Get-ItemProperty-Path"HKLM:\SYSTEM\CurrentControlSet\Services\USBSTOR"-Name"Start"
Method 5: Read-Only (Allow read, block write)
reg
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\StorageDevicePolicies]
"WriteProtect"=dword:00000001
cmd
reg add "HKLM\SYSTEM\CurrentControlSet\Control\StorageDevicePolicies" /v WriteProtect /t REG_DWORD /d 1 /f
This allows USB devices to be read but prevents any data from being copied TO the USB.
Linux
Method 1: Blacklist USB Storage Module
bash
# Create a blacklist config
echo"blacklist usb_storage"|sudotee/etc/modprobe.d/disable-usb-storage.conf
echo"blacklist uas"|sudotee-a /etc/modprobe.d/disable-usb-storage.conf
# Update initramfs
sudo update-initramfs -u # Debian/Ubuntu
sudo dracut --force # RHEL/CentOS/Fedora
# Reboot to apply
sudo reboot
Method 2: Unload Module Immediately (No Reboot)
bash
# Remove the module from running kernel
sudo modprobe -r usb_storage
sudo modprobe -r uas
# Verify it's unloaded
lsmod|grepusb_storage# Should return nothing
⚠️ Temporary — reloads on reboot unless blacklisted.
Method 3: udev Rules (Block Specific or All USB Storage)
bash
sudonano/etc/udev/rules.d/99-block-usb.rules
Block all USB storage:
udev
ACTION=="add", SUBSYSTEMS=="usb", SUBSYSTEM=="block", RUN{program}="/bin/sh -c 'echo 0 > /sys/\$devpath/authorized'"
Block write access only (read-only USB):
udev
ACTION=="add", KERNEL=="sd*", SUBSYSTEMS=="usb", RUN{program}="/sbin/blockdev --setro /dev/%k"
Reload rules:
bash
sudo udevadm control --reload-rules
sudo udevadm trigger
Method 4: USBGuard (Enterprise-Grade)
USBGuard is purpose-built for enterprise USB control:
bash
# Install
sudoaptinstallusbguard# Debian/Ubuntu
sudodnfinstallusbguard# RHEL/Fedora
# Generate policy from currently connected devices
sudousbguard generate-policy>/etc/usbguard/rules.conf
# Block all USB devices by default
echo'block'|sudotee/etc/usbguard/rules.conf
# Allow specific device by ID
echo'allow id 1234:5678'|sudotee-a /etc/usbguard/rules.conf
# Start and enable
sudosystemctlenable--now usbguard
Method 5: File System Permissions (Prevent Mounting)
bash
# Restrict who can mount USB
sudochmod700/media
sudochmod700/mnt
# Remove user ability to mount
sudonano/etc/fstab
# Add: noauto,noexec options for removable media
Enterprise-Level Solutions
For large-scale enforcement, consider dedicated DLP tools:
|
Tool |
Platform |
Feature |
|
Microsoft Intune |
Windows/macOS |
MDM-based USB control |
|
Symantec DLP |
Cross-platform |
Content-aware blocking |
|
CrowdStrike Falcon |
Cross-platform |
USB device control policies |
|
|
|
|
|
Forcepoint DLP |
Cross-platform |
Block by device class/ID |
|
USBGuard |
Linux |
Whitelist/blacklist USB |
|
McAfee ePO |
Windows |
Centralized USB policy |
Best Practice Summary
|
Goal |
Windows |
Linux |
|
Block all USB storage |
GPO or USBSTOR registry |
Blacklist |
|
Block write only |
|
udev read-only rule |
|
Allow specific USBs |
GPO device ID whitelist |
USBGuard whitelist |
|
Enterprise-wide |
Microsoft Intune / GPO |
USBGuard + Ansible |
|
Audit USB usage |
Windows Event Log (ID 2003) |
|
Would you like help with auditing USB usage logs, whitelisting specific devices, or deploying via Ansible/GPO at scale?
Auditing USB Usage Logs — Windows & Linux
Windows USB Audit Logs
Step 1: Enable Auditing via Group Policy
Computer Configuration →
Windows Settings →
Security Settings →
Advanced Audit Policy Configuration →
Object Access →
Audit Removable Storage → Enable (Success & Failure)
Or via command line:
cmd
auditpol /set /subcategory:"Removable Storage" /success:enable /failure:enable
Step 2: Key Event IDs to Monitor
|
Event ID |
Meaning |
|
6416 |
New external device recognized |
|
4663 |
File copied to/from removable storage |
|
4656 |
Handle requested on USB object |
|
4657 |
Registry value modified (USBSTOR) |
|
2003 |
USB device policy change |
Step 3: Query Logs via PowerShell
Find all USB device connections:
powershell
Get-WinEvent-LogName Security|Where-Object{$_.Id-eq6416}|
Select-ObjectTimeCreated,Message|
Format-List
Find file copy attempts to USB:
powershell
Get-WinEvent-LogName Security|Where-Object{$_.Id-eq4663}|
Where-Object{$_.Message-like"*RemovableStorage*"}|
Select-ObjectTimeCreated,Message|
Export-Csv"C:\USB_Audit_Log.csv"-NoTypeInformation
Check USB history from registry:
powershell
# Lists all USB devices ever connected
Get-ItemProperty-Path"HKLM:\SYSTEM\CurrentControlSet\Enum\USBSTOR\*\*"|
Select-ObjectFriendlyName,Mfg,@{N="LastConnected";E={$_.PSChildName}}|
Format-Table-AutoSize
Export last 7 days of USB events:
powershell
$startDate=(Get-Date).AddDays(-7)
Get-WinEvent-FilterHashtable @{
LogName = 'Security'
Id = 6416,4663
StartTime = $startDate
}|Select-ObjectTimeCreated,Id,Message|
Export-Csv"C:\USB_7Day_Report.csv"-NoTypeInformation
Write-Host "Report saved to C:\USB_7Day_Report.csv"
Step 4: Event Viewer (GUI)
Event Viewer →
Windows Logs →
Security →
Filter Current Log →
Event ID: 6416
Step 5: Audit via SIEM (Splunk Example)
spl
index=wineventlog EventCode=6416 OR EventCode=4663
| eval action=case(EventCode==6416,"Device Connected", EventCode==4663,"File Access")
| table _time, host, user, action, Message
| sort -_time
Linux USB Audit Logs
Method 1: syslog / journald (Built-in)
Check recent USB events:
bash
# systemd journal
journalctl -k|grep-i usb
# Kernel messages
dmesg|grep-i usb|grep-i"storage\|mass\|sd[a-z]"
# syslog
grep -i usb /var/log/syslog # Debian/Ubuntu
grep -i usb /var/log/messages # RHEL/CentOS
Watch USB events in real time:
bash
journalctl -kf|grep-i usb
Method 2: udevadm Monitor (Real-Time)
bash
# Monitor all USB add/remove events live
sudoudevadm monitor --subsystem-match=usb --property
# Compact view
sudoudevadm monitor --subsystem-match=block|grep-i usb
Output example:
UDEV [12345.678] add /devices/pci0000:00/usb1/1-1 (usb)
ACTION=add
ID_VENDOR=SanDisk
ID_MODEL=Ultra_USB_3.0
Method 3: auditd — Enterprise-Grade USB Auditing
Install auditd:
bash
sudoaptinstallauditd audispd-plugins# Debian/Ubuntu
sudodnfinstallaudit# RHEL/Fedora
sudosystemctlenable--now auditd
Add USB audit rules:
bash
sudonano/etc/audit/rules.d/usb.rules
bash
# Watch USB storage mounting
-a always,exit -Farch=b64 -Smount-F auid>=1000-F auid!=4294967295-k usb_mount
# Watch file writes to USB mount points
-w /media -p rwxa -k usb_media_access
-w /mnt -p rwxa -k usb_mnt_access
# Watch USB device node creation
-a always,exit -Farch=b64 -Sopen-Fdir=/dev -Fsuccess=1-k usb_dev_open
Reload rules:
bash
sudo augenrules --load
sudo systemctl restart auditd
Method 4: Query auditd Logs
Search all USB mount events:
bash
sudoausearch -k usb_mount|aureport -f -i
Search by user:
bash
sudoausearch -k usb_media_access -ua john|aureport -f -i
Generate summary report:
bash
# USB access summary for last 24 hours
sudoaureport --start today --end now -f|grep-i"media\|mnt"
Full USB audit report:
bash
sudo ausearch -k usb_mount -k usb_media_access \
--start$(date -d '7 days ago' '+%m/%d/%Y')\
--end$(date '+%m/%d/%Y')\
|aureport -f -i\
|tee/var/log/usb_weekly_report.txt
echo "Report saved: /var/log/usb_weekly_report.txt"
Method 5: USBGuard Audit Log
bash
# View USBGuard decision log
sudojournalctl -u usbguard|grep-E"allow|block|reject"
# Detailed USB policy decisions
sudo usbguard list-devices
# Devices that were blocked
sudojournalctl -u usbguard|grep"block"
Method 6: Inotify — Watch for File Copies to USB
bash
# Install inotify-tools
sudoaptinstallinotify-tools
# Monitor /media for any file writes (data exfiltration detection)
sudo inotifywait -m -r -e create,moved_to,close_write /media \
--format'%T %w %f %e'--timefmt'%Y-%m-%d %H:%M:%S'\
|tee/var/log/usb_file_watch.log
Comparison Summary
|
Capability |
Windows |
Linux |
|
Device connection log |
Event ID 6416 |
|
|
File copy detection |
Event ID 4663 |
|
|
Real-time monitoring |
Event Viewer / PowerShell |
|
|
Historical USB devices |
Registry (USBSTOR) |
|
|
Enterprise SIEM feed |
Windows Event Forwarding |
auditd → syslog → SIEM |
|
Policy enforcement log |
GPO audit logs |
USBGuard journal |
Centralizing Logs to a SIEM
Windows → SIEM:
powershell
# Forward events using Windows Event Forwarding (WEF)
wecutil qc/quiet# Configure as event collector
Linux → SIEM (rsyslog):
bash
# Forward auditd logs to central SIEM
echo"*.* @your-siem-server:514"|sudotee-a /etc/rsyslog.conf
sudo systemctl restart rsyslog
Automated USB Audit Report Scripts
Windows — PowerShell Script
Save as USB-Audit-Report.ps1:
powershell
# ============================================
# USB Audit Report Script — Windows
# Generates HTML + CSV report of USB activity
# ============================================
param(
[int]$DaysBack = 7,
[string]$ReportPath = "C:\Reports\USB_Audit",
[string]$SMTPServer = "smtp.company.com",
[string]$EmailTo = "security@company.com",
[string]$EmailFrom = "usb-audit@company.com",
[switch]$SendEmail
)
# --- Setup ---
$date = Get-Date -Format "yyyy-MM-dd_HH-mm"
$startDate = (Get-Date).AddDays(-$DaysBack)
if (-not (Test-Path $ReportPath)) { New-Item -ItemType Directory -Path $ReportPath | Out-Null }
$csvFile = "$ReportPath\USB_Report_$date.csv"
$htmlFile = "$ReportPath\USB_Report_$date.html"
Write-Host "`n[*] Collecting USB events from last $DaysBack days..." -ForegroundColor Cyan
# --- Collect Events ---
$events = @()
# Event 6416 - New device recognized
$dev = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 6416
StartTime = $startDate
} -ErrorAction SilentlyContinue
foreach ($e in $dev) {
$xml = [xml]$e.ToXml()
$data = $xml.Event.EventData.Data
$events += [PSCustomObject]@{
Time = $e.TimeCreated
EventID = $e.Id
EventType = "Device Connected"
User = ($data | Where-Object { $_.Name -eq "SubjectUserName" }).'#text'
Computer = $env:COMPUTERNAME
DeviceName = ($data | Where-Object { $_.Name -eq "DeviceDescription" }).'#text'
DeviceID = ($data | Where-Object { $_.Name -eq "DeviceInstanceId" }).'#text'
FilePath = "N/A"
}
}
# Event 4663 - File access on removable storage
$files = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4663
StartTime = $startDate
} -ErrorAction SilentlyContinue |
Where-Object { $_.Message -like "*Removable*" }
foreach ($e in $files) {
$xml = [xml]$e.ToXml()
$data = $xml.Event.EventData.Data
$events += [PSCustomObject]@{
Time = $e.TimeCreated
EventID = $e.Id
EventType = "File Access"
User = ($data | Where-Object { $_.Name -eq "SubjectUserName" }).'#text'
Computer = $env:COMPUTERNAME
DeviceName = "N/A"
DeviceID = "N/A"
FilePath = ($data | Where-Object { $_.Name -eq "ObjectName" }).'#text'
}
}
# --- USB Device History from Registry ---
Write-Host "[*] Reading USB device history from registry..." -ForegroundColor Cyan
$usbHistory = @()
$usbPath = "HKLM:\SYSTEM\CurrentControlSet\Enum\USBSTOR"
if (Test-Path $usbPath) {
Get-ChildItem $usbPath | ForEach-Object {
$deviceType = $_.PSChildName
Get-ChildItem $_.PSPath | ForEach-Object {
$props = Get-ItemProperty $_.PSPath
$usbHistory += [PSCustomObject]@{
DeviceType = $deviceType
FriendlyName = $props.FriendlyName
Manufacturer = $props.Mfg
SerialNumber = $_.PSChildName
}
}
}
}
# --- Export CSV ---
$events | Export-Csv $csvFile -NoTypeInformation
Write-Host "[+] CSV saved: $csvFile" -ForegroundColor Green
# --- Generate HTML Report ---
$totalEvents = $events.Count
$deviceEvents = ($events | Where-Object { $_.EventType -eq "Device Connected" }).Count
$fileEvents = ($events | Where-Object { $_.EventType -eq "File Access" }).Count
$uniqueUsers = ($events | Select-Object -Unique User).Count
$tableRows = $events | Sort-Object Time -Descending | ForEach-Object {
$rowColor = if ($_.EventType -eq "File Access") { "#fff3cd" } else { "#d4edda" }
"<tr style='background:$rowColor'>
<td>$($_.Time)</td>
<td>$($_.EventType)</td>
<td>$($_.User)</td>
<td>$($_.Computer)</td>
<td>$($_.DeviceName)</td>
<td>$($_.FilePath)</td>
</tr>"
}
$historyRows = $usbHistory | ForEach-Object {
"<tr>
<td>$($_.FriendlyName)</td>
<td>$($_.Manufacturer)</td>
<td>$($_.SerialNumber)</td>
<td>$($_.DeviceType)</td>
</tr>"
}
$html = @"
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<title>USB Audit Report</title>
<style>
body { font-family: Segoe UI, sans-serif; background: #f4f6f9; padding: 30px; }
h1 { color: #c0392b; }
h2 { color: #2c3e50; border-bottom: 2px solid #e74c3c; padding-bottom: 5px; }
.summary { display: flex; gap: 20px; margin-bottom: 30px; }
.card { background: white; border-radius: 8px; padding: 20px 30px;
box-shadow: 0 2px 6px rgba(0,0,0,0.1); text-align: center; }
.card h3 { margin: 0; font-size: 2em; color: #e74c3c; }
.card p { margin: 5px 0 0; color: #666; }
table { width: 100%; border-collapse: collapse; background: white;
border-radius: 8px; overflow: hidden;
box-shadow: 0 2px 6px rgba(0,0,0,0.1); margin-bottom: 30px; }
th { background: #2c3e50; color: white; padding: 12px; text-align: left; }
td { padding: 10px 12px; border-bottom: 1px solid #eee; font-size: 0.9em; }
tr:hover td { background: #f0f4ff; }
.footer { color: #999; font-size: 0.8em; margin-top: 20px; }
</style>
</head>
<body>
<h1>🔌 USB Audit Report</h1>
<p>Period: <strong>$(Get-Date $startDate -Format 'yyyy-MM-dd')</strong> to <strong>$(Get-Date -Format 'yyyy-MM-dd')</strong> | Host: <strong>$env:COMPUTERNAME</strong></p>
<div class='summary'>
<div class='card'><h3>$totalEvents</h3><p>Total Events</p></div>
<div class='card'><h3>$deviceEvents</h3><p>Devices Connected</p></div>
<div class='card'><h3>$fileEvents</h3><p>File Access Events</p></div>
<div class='card'><h3>$uniqueUsers</h3><p>Unique Users</p></div>
</div>
<h2>Event Log</h2>
<table>
<tr><th>Time</th><th>Event Type</th><th>User</th><th>Computer</th><th>Device</th><th>File Path</th></tr>
$($tableRows -join "`n")
</table>
<h2>USB Device History (Registry)</h2>
<table>
<tr><th>Friendly Name</th><th>Manufacturer</th><th>Serial Number</th><th>Device Type</th></tr>
$($historyRows -join "`n")
</table>
<div class='footer'>Generated: $(Get-Date) | USB Audit Script v1.0</div>
</body>
</html>
"@
$html | Out-File $htmlFile -Encoding UTF8
Write-Host "[+] HTML report saved: $htmlFile" -ForegroundColor Green
# --- Optional Email ---
if ($SendEmail) {
Send-MailMessage `
-To $EmailTo -From $EmailFrom `
-Subject "USB Audit Report - $env:COMPUTERNAME - $(Get-Date -Format 'yyyy-MM-dd')" `
-Body "Please find the USB audit report attached." `
-Attachments $csvFile, $htmlFile `
-SmtpServer $SMTPServer
Write-Host "[+] Email sent to $EmailTo" -ForegroundColor Green
}
Write-Host "`n[✔] Report complete! Events found: $totalEvents`n" -ForegroundColor Green
Run it:
powershell
# Basic run (last 7 days)
.\USB-Audit-Report.ps1
# Last 30 days + send email
.\USB-Audit-Report.ps1 -DaysBack 30 -SendEmail
# Schedule via Task Scheduler (weekly)
schtasks /create /tn "USB Audit Report" /tr "powershell -File C:\Scripts\USB-Audit-Report.ps1" /sc weekly /d MON /st 08:00 /ru SYSTEM
Linux — Bash Script
Save as usb_audit_report.sh:
bash
#!/bin/bash
# ============================================
# USB Audit Report Script — Linux
# Generates HTML + CSV report of USB activity
# ============================================
# --- Config ---
DAYS_BACK=${1:-7}
REPORT_DIR="/var/log/usb-audit-reports"
DATE=$(date +"%Y-%m-%d_%H-%M")
CSV_FILE="$REPORT_DIR/usb_report_$DATE.csv"
HTML_FILE="$REPORT_DIR/usb_report_$DATE.html"
EMAIL_TO="security@company.com"
SEND_EMAIL=false # Set to true to enable email
HOSTNAME=$(hostname)
mkdir -p "$REPORT_DIR"
echo "[*] Collecting USB events from last $DAYS_BACK days..."
# --- Collect from journald ---
SINCE=$(date -d "$DAYS_BACK days ago" "+%Y-%m-%d %H:%M:%S")
echo "Timestamp,Source,Event,Device,User,Details" > "$CSV_FILE"
# USB device connections from kernel log
journalctl -k --since="$SINCE" 2>/dev/null | \
grep -iE "usb|scsi|sd[a-z]|mass.storage" | \
while IFS= read -r line; do
ts=$(echo "$line" | awk '{print $1, $2, $3}')
echo "$ts,kernel,Device Event,USB Device,system,$line" >> "$CSV_FILE"
done
# USB mount events from syslog
grep -iE "usb|mount|sd[a-z]" /var/log/syslog 2>/dev/null | \
awk -v since="$SINCE" '$0 >= since' | \
while IFS= read -r line; do
ts=$(echo "$line" | awk '{print $1, $2, $3}')
user=$(echo "$line" | grep -oP 'user=\K\S+' || echo "system")
echo "$ts,syslog,Mount Event,USB Device,$user,$line" >> "$CSV_FILE"
done
# auditd USB events (if available)
if command -v ausearch &>/dev/null; then
ausearch -k usb_mount -k usb_media_access \
--start "$(date -d "$DAYS_BACK days ago" '+%m/%d/%Y')" \
--end "$(date '+%m/%d/%Y')" 2>/dev/null | \
grep -E "^time|uid|cmd|name" | \
paste - - - - | \
while IFS= read -r line; do
echo "$(date),auditd,USB Access,Removable Media,$(echo $line | grep -oP 'uid=\K\d+'),$(echo $line)" >> "$CSV_FILE"
done
fi
# --- Collect connected USB devices now ---
echo "[*] Scanning current USB devices..."
CURRENT_DEVICES=$(lsusb 2>/dev/null)
# --- Count stats ---
TOTAL=$(tail -n +2 "$CSV_FILE" | wc -l)
MOUNT_EVENTS=$(grep -c "Mount Event" "$CSV_FILE" 2>/dev/null || echo 0)
DEVICE_EVENTS=$(grep -c "Device Event" "$CSV_FILE" 2>/dev/null || echo 0)
AUDIT_EVENTS=$(grep -c "auditd" "$CSV_FILE" 2>/dev/null || echo 0)
# --- Build HTML Report ---
echo "[*] Generating HTML report..."
TABLE_ROWS=""
while IFS=',' read -r ts source event device user details; do
[[ "$ts" == "Timestamp" ]] && continue
if [[ "$event" == *"Mount"* ]]; then
ROW_COLOR="#fff3cd"
elif [[ "$source" == "auditd" ]]; then
ROW_COLOR="#f8d7da"
else
ROW_COLOR="#d4edda"
fi
TABLE_ROWS+="<tr style='background:$ROW_COLOR'>
<td>$ts</td><td>$source</td><td>$event</td>
<td>$device</td><td>$user</td>
<td style='font-size:0.8em;word-break:break-all'>${details:0:120}...</td>
</tr>"
done < "$CSV_FILE"
DEVICE_ROWS=""
while IFS= read -r line; do
BUS=$(echo "$line" | grep -oP 'Bus \K\d+')
DEV=$(echo "$line" | grep -oP 'Device \K\d+')
ID=$(echo "$line" | grep -oP 'ID \K[\w:]+')
NAME=$(echo "$line" | sed 's/.*ID [^ ]* //')
DEVICE_ROWS+="<tr><td>Bus $BUS</td><td>Device $DEV</td><td>$ID</td><td>$NAME</td></tr>"
done <<< "$CURRENT_DEVICES"
cat > "$HTML_FILE" <<EOF
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>USB Audit Report - $HOSTNAME</title>
<style>
body { font-family: Ubuntu, sans-serif; background: #f4f6f9; padding: 30px; }
h1 { color: #c0392b; }
h2 { color: #2c3e50; border-bottom: 2px solid #e74c3c; padding-bottom: 5px; }
.summary { display: flex; gap: 20px; margin-bottom: 30px; flex-wrap: wrap; }
.card { background: white; border-radius: 8px; padding: 20px 30px;
box-shadow: 0 2px 6px rgba(0,0,0,0.1); text-align: center; min-width: 130px; }
.card h3 { margin: 0; font-size: 2em; color: #e74c3c; }
.card p { margin: 5px 0 0; color: #666; font-size: 0.9em; }
table { width: 100%; border-collapse: collapse; background: white;
border-radius: 8px; overflow: hidden;
box-shadow: 0 2px 6px rgba(0,0,0,0.1); margin-bottom: 30px; }
th { background: #2c3e50; color: white; padding: 12px; text-align: left; }
td { padding: 9px 12px; border-bottom: 1px solid #eee; font-size: 0.85em; }
tr:hover td { background: #f0f4ff; }
.footer { color: #999; font-size: 0.8em; margin-top: 20px; }
</style>
</head>
<body>
<h1>🔌 USB Audit Report</h1>
<p>Host: <strong>$HOSTNAME</strong> | Period: <strong>Last $DAYS_BACK days</strong> | Generated: <strong>$(date)</strong></p>
<div class="summary">
<div class="card"><h3>$TOTAL</h3><p>Total Events</p></div>
<div class="card"><h3>$DEVICE_EVENTS</h3><p>Device Events</p></div>
<div class="card"><h3>$MOUNT_EVENTS</h3><p>Mount Events</p></div>
<div class="card"><h3>$AUDIT_EVENTS</h3><p>Audit Events</p></div>
</div>
<h2>Currently Connected USB Devices</h2>
<table>
<tr><th>Bus</th><th>Device</th><th>ID</th><th>Description</th></tr>
$DEVICE_ROWS
</table>
<h2>USB Event Log</h2>
<table>
<tr><th>Timestamp</th><th>Source</th><th>Event</th><th>Device</th><th>User</th><th>Details</th></tr>
$TABLE_ROWS
</table>
<div class="footer">
🟢 Device Connected |
🟡 Mount Event |
🔴 Audit Alert |
USB Audit Script v1.0
</div>
</body>
</html>
EOF
echo "[+] CSV saved : $CSV_FILE"
echo "[+] HTML saved: $HTML_FILE"
# --- Optional Email via sendmail/mailx ---
if [ "$SEND_EMAIL" = true ]; then
if command -v mailx &>/dev/null; then
echo "USB Audit Report for $HOSTNAME — $(date)" | \
mailx -s "USB Audit Report - $HOSTNAME - $(date +%Y-%m-%d)" \
-a "$HTML_FILE" -a "$CSV_FILE" \
"$EMAIL_TO"
echo "[+] Email sent to $EMAIL_TO"
else
echo "[!] mailx not found — install with: apt install mailutils"
fi
fi
echo ""
echo "[✔] Report complete! Total events: $TOTAL"
echo " HTML → $HTML_FILE"
echo " CSV → $CSV_FILE"
Deploy it:
bash
# Make executable
chmod +x usb_audit_report.sh
# Run manually (last 7 days)
sudo ./usb_audit_report.sh
# Run for last 30 days
sudo ./usb_audit_report.sh 30
# Schedule via cron (every Monday at 8 AM)
(crontab -l; echo "0 8 * * MON root /opt/scripts/usb_audit_report.sh 7") | sudo crontab -
# Enable email
sed -i 's/SEND_EMAIL=false/SEND_EMAIL=true/' usb_audit_report.sh
Output Sample (Both Platforms)
Both scripts produce:
/Reports/USB_Audit/
├── usb_report_2026-05-30_08-00.html ← Visual dashboard
└── usb_report_2026-05-30_08-00.csv ← Raw data for SIEM/Excel
The HTML report includes:
- Summary cards — total events, device connections, file access, mount events
- Currently connected devices table
- Full event log — color-coded by event type
- Registry/journal history of all USB devices ever connected
Deployment at Scale
|
Method |
Windows |
Linux |
|
Push script |
GPO Startup Script |
Ansible playbook |
|
Schedule |
Task Scheduler |
cron / systemd timer |
|
Collect reports |
Network share / SFTP |
rsync to central server |
|
Central dashboard |
SIEM (Splunk/Sentinel) |
ELK Stack |
Would you like a combined dashboard that aggregates reports from multiple machines, or help setting up real-time email alerts when a USB is plugged in?
Here's a full solution — a central server that collects USB audit data from all machines, plus a live web dashboard.
Architecture Overview
Windows Machines ──┐
├──► Central Server (Linux) ──► Web Dashboard
Linux Machines ──┘ /var/usb-central/
Part 1: Central Collection Server (Linux)
Setup collector script
Save as /opt/usb-central/collect.sh:
bash
#!/bin/bash
# Central USB report collector
# Pulls CSV reports from all agents via SSH/SFTP
CENTRAL_DIR="/var/usb-central/reports"
MACHINES_FILE="/opt/usb-central/machines.txt" # one host per line
SSH_KEY="/opt/usb-central/.ssh/usb_audit_key"
REPORT_GLOB="/var/log/usb-audit-reports/*.csv"
mkdir -p "$CENTRAL_DIR"
whileIFS='|'read-rhostos label;do
[["$host"=~^#.*$ || -z "$host" ]] && continue
echo"[*] Pulling from $label ($host) [$os]..."
DEST="$CENTRAL_DIR/$label"
mkdir-p"$DEST"
if[["$os"=="linux"]];then
rsync-az -e"ssh -i $SSH_KEY -o StrictHostKeyChecking=no"\
"root@$host:$REPORT_GLOB""$DEST/"2>/dev/null\
&&echo"[+] $label OK"||echo"[!] $label FAILED"
elif[["$os"=="windows"]];then
# Windows agents push via SFTP or SMB share
# Mount SMB share and copy CSVs
SHARE="\\\\$host\\USB_Reports"
smbclient"$SHARE"-A /opt/usb-central/smb.auth\
-c"lcd $DEST; mask *.csv; mget *"2>/dev/null\
&&echo"[+] $label OK"||echo"[!] $label FAILED (check SMB share)"
fi
done<"$MACHINES_FILE"
echo "[✔] Collection complete: $(date)"
machines.txt format:
# host|os|label
192.168.1.10|linux|web-server-01
192.168.1.11|linux|db-server-01
192.168.1.20|windows|workstation-win-01
192.168.1.21|windows|finance-pc-01
Part 2: Dashboard Generator
Save as /opt/usb-central/generate_dashboard.py:
python
#!/usr/bin/env python3
"""
USB Audit Central Dashboard Generator
Aggregates CSV reports from all machines → single HTML dashboard
"""
importos,csv,json,glob
fromdatetimeimportdatetime
fromcollectionsimportdefaultdict
CENTRAL_DIR="/var/usb-central/reports"
DASHBOARD="/var/www/html/usb-dashboard/index.html"
os.makedirs(os.path.dirname(DASHBOARD),exist_ok=True)
# ── Aggregate all CSVs ──────────────────────────────────────────────
all_events=[]
machine_stats=defaultdict(lambda:{"total":0,"mounts":0,"files":0,"last_seen":"N/A"})
formachine_dirinsorted(glob.glob(f"{CENTRAL_DIR}/*")):
machine=os.path.basename(machine_dir)
forcsv_fileinsorted(glob.glob(f"{machine_dir}/*.csv"),reverse=True):
withopen(csv_file,newline="",encoding="utf-8",errors="ignore")asf:
reader=csv.DictReader(f)
forrowinreader:
row["Machine"]=machine
all_events.append(row)
machine_stats[machine]["total"]+=1
machine_stats[machine]["last_seen"]=row.get("Timestamp","N/A")
evt=row.get("Event",row.get("EventType",""))
if"Mount"inevt:machine_stats[machine]["mounts"]+=1
if"File"inevt:machine_stats[machine]["files"]+=1
all_events.sort(key=lambdax:x.get("Timestamp",""),reverse=True)
# ── Summary stats ───────────────────────────────────────────────────
total_events=len(all_events)
total_machines=len(machine_stats)
total_mounts=sum(v["mounts"]forvinmachine_stats.values())
total_files=sum(v["files"]forvinmachine_stats.values())
# ── Build JSON for charts ────────────────────────────────────────────
machine_labels=json.dumps(list(machine_stats.keys()))
machine_counts=json.dumps([v["total"]forvinmachine_stats.values()])
# Events per day (last 14 days)
day_counts=defaultdict(int)
foreinall_events:
ts=e.get("Timestamp","")
day=ts[:10]iftselse"unknown"
day_counts[day]+=1
sorted_days=sorted(day_counts.keys())[-14:]
day_labels=json.dumps(sorted_days)
day_values=json.dumps([day_counts[d]fordinsorted_days])
# Event type breakdown
type_counts=defaultdict(int)
foreinall_events:
type_counts[e.get("Event",e.get("EventType","Unknown"))]+=1
type_labels=json.dumps(list(type_counts.keys())[:6])
type_values=json.dumps(list(type_counts.values())[:6])
# ── Table rows ───────────────────────────────────────────────────────
defevent_color(evt):
if"File"inevt:return"#fff3cd"
if"Mount"inevt:return"#f8d7da"
return"#d4edda"
table_rows=""
foreinall_events[:500]:# cap at 500 rows
evt=e.get("Event",e.get("EventType",""))
color=event_color(evt)
table_rows+=f"""<tr style='background:{color}'>
<td>{e.get('Timestamp','')[:19]}</td>
<td><span class='badge'>{e.get('Machine','')}</span></td>
<td>{e.get('Source','')}</td>
<td>{evt}</td>
<td>{e.get('User','')}</td>
<td class='detail'>{str(e.get('Details',''))[:100]}</td>
</tr>"""
machine_rows=""
form,sinmachine_stats.items():
health="🟢"ifs["total"]<50else"🟡"ifs["total"]<200else"🔴"
machine_rows+=f"""<tr>
<td>{health} {m}</td>
<td>{s['total']}</td>
<td>{s['mounts']}</td>
<td>{s['files']}</td>
<td>{str(s['last_seen'])[:19]}</td>
</tr>"""
# ── HTML Dashboard ───────────────────────────────────────────────────
html=f"""<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="300">
<title>USB Audit Dashboard</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js"></script>
<style>
* {{ box-sizing: border-box; margin: 0; padding: 0; }}
body {{ font-family: Segoe UI, Ubuntu, sans-serif; background: #0f1117; color: #e0e0e0; }}
header {{ background: #1a1d27; padding: 20px 30px; border-bottom: 2px solid #e74c3c;
display: flex; align-items: center; justify-content: space-between; }}
header h1 {{ color: #e74c3c; font-size: 1.5em; }}
header span {{ color: #666; font-size: 0.85em; }}
.main {{ padding: 25px 30px; }}
.summary {{ display: grid; grid-template-columns: repeat(4,1fr); gap: 16px; margin-bottom: 25px; }}
.card {{ background: #1a1d27; border-radius: 10px; padding: 20px 24px;
border-left: 4px solid #e74c3c; }}
.card h3 {{ font-size: 2.2em; color: #e74c3c; }}
.card p {{ color: #888; font-size: 0.85em; margin-top: 4px; }}
.charts {{ display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; margin-bottom: 25px; }}
.chart-box {{ background: #1a1d27; border-radius: 10px; padding: 20px; }}
.chart-box h2 {{ font-size: 0.95em; color: #aaa; margin-bottom: 15px; }}
.section {{ background: #1a1d27; border-radius: 10px; padding: 20px; margin-bottom: 20px; }}
.section h2 {{ font-size: 1em; color: #e74c3c; margin-bottom: 15px; border-bottom: 1px solid #2c2f3e; padding-bottom: 8px; }}
table {{ width: 100%; border-collapse: collapse; font-size: 0.85em; }}
th {{ background: #2c2f3e; color: #aaa; padding: 10px 12px; text-align: left; font-weight: 500; }}
td {{ padding: 9px 12px; border-bottom: 1px solid #1e2130; }}
tr:hover td {{ background: #1e2130; }}
.badge {{ background: #2c3e50; color: #7fb3d3; padding: 2px 8px;
border-radius: 4px; font-size: 0.8em; }}
.detail {{ color: #666; font-size: 0.8em; max-width: 300px;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }}
input[type=text] {{ background: #2c2f3e; border: 1px solid #444; color: #eee;
padding: 7px 12px; border-radius: 6px; width: 280px; margin-bottom: 12px; }}
@media(max-width:900px) {{
.summary {{ grid-template-columns: repeat(2,1fr); }}
.charts {{ grid-template-columns: 1fr; }}
}}
</style>
</head>
<body>
<header>
<h1>🔌USB Audit Central Dashboard</h1>
<span>Auto-refreshes every 5 min | Last update: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</span>
</header>
<div class="main">
<!-- Summary Cards -->
<div class="summary">
<div class="card"><h3>{total_events}</h3><p>Total Events</p></div>
<div class="card"><h3>{total_machines}</h3><p>Machines Monitored</p></div>
<div class="card"><h3>{total_mounts}</h3><p>USB Mounts</p></div>
<div class="card"><h3>{total_files}</h3><p>File Access Events</p></div>
</div>
<!-- Charts -->
<div class="charts">
<div class="chart-box">
<h2>📅Events Per Day (Last 14 Days)</h2>
<canvas id="dayChart" height="180"></canvas>
</div>
<div class="chart-box">
<h2>🖥️ Events by Machine</h2>
<canvas id="machineChart" height="180"></canvas>
</div>
<div class="chart-box">
<h2>📊Event Type Breakdown</h2>
<canvas id="typeChart" height="180"></canvas>
</div>
</div>
<!-- Machine Summary -->
<div class="section">
<h2>🖥️ Machine Summary</h2>
<table>
<tr><th>Machine</th><th>Total Events</th><th>USB Mounts</th><th>File Access</th><th>Last Event</th></tr>
{machine_rows}
</table>
</div>
<!-- Event Log -->
<div class="section">
<h2>📋Event Log (Latest 500)</h2>
<input type="text" id="searchBox" placeholder="🔍Filter by machine, user, event..." onkeyup="filterTable()">
<table id="eventTable">
<tr><th>Timestamp</th><th>Machine</th><th>Source</th><th>Event</th><th>User</th><th>Details</th></tr>
{table_rows}
</table>
</div>
</div>
<script>
// Charts
const chartOpts = (color) => ({{
responsive: true,
plugins: {{ legend: {{ display: false }} }},
scales: {{
x: {{ ticks: {{ color:'#666' }}, grid: {{ color:'#1e2130' }} }},
y: {{ ticks: {{ color:'#666' }}, grid: {{ color:'#1e2130' }} }}
}}
}});
new Chart(document.getElementById('dayChart'), {{
type: 'bar',
data: {{ labels: {day_labels}, datasets: [{{
data: {day_values}, backgroundColor: '#e74c3c', borderRadius: 4
}}]}},
options: chartOpts('#e74c3c')
}});
new Chart(document.getElementById('machineChart'), {{
type: 'bar',
data: {{ labels: {machine_labels}, datasets: [{{
data: {machine_counts}, backgroundColor: '#3498db', borderRadius: 4
}}]}},
options: chartOpts('#3498db')
}});
new Chart(document.getElementById('typeChart'), {{
type: 'doughnut',
data: {{ labels: {type_labels}, datasets: [{{
data: {type_values},
backgroundColor: ['#e74c3c','#f39c12','#2ecc71','#3498db','#9b59b6','#1abc9c']
}}]}},
options: {{ responsive: true, plugins: {{ legend: {{ labels: {{ color:'#aaa' }} }} }} }}
}});
// Filter table
function filterTable() {{
const q = document.getElementById('searchBox').value.toLowerCase();
document.querySelectorAll('#eventTable tr:not(:first-child)').forEach(row => {{
row.style.display = row.innerText.toLowerCase().includes(q) ? '' : 'none';
}});
}}
</script>
</body>
</html>"""
withopen(DASHBOARD,"w")asf:
f.write(html)
print(f"[✔] Dashboard generated: {DASHBOARD}")
print(f" Machines : {total_machines}")
print(f" Events : {total_events}")
print(f" Generated: {datetime.now()}")
Part 3: Deploy & Automate
bash
# Install dependencies on central server
sudoaptinstallpython3 nginxrsyncsamba-client -y
# Setup nginx to serve dashboard
sudomkdir-p /var/www/html/usb-dashboard
sudochmod755/var/www/html/usb-dashboard
# Make scripts executable
chmod +x /opt/usb-central/collect.sh
chmod +x /opt/usb-central/generate_dashboard.py
# Schedule: collect every 30 min, regenerate dashboard immediately after
crontab -e
cron
# Collect reports from all machines every 30 minutes
*/30 * * * * /opt/usb-central/collect.sh >> /var/log/usb-central.log 2>&1
# Regenerate dashboard after collection
*/30 * * * * sleep 60 && python3 /opt/usb-central/generate_dashboard.py >> /var/log/usb-central.log 2>&1
# Full daily report at 7 AM
0 7 * * * /opt/usb-central/collect.sh && python3 /opt/usb-central/generate_dashboard.py
bash
# Run manually right now
sudo /opt/usb-central/collect.sh
sudo python3 /opt/usb-central/generate_dashboard.py
# Access dashboard at:
# http://your-central-server/usb-dashboard/
Part 4: Windows Agent — Auto-Push Reports
Add this to your Windows USB-Audit-Report.ps1 to push CSVs to the central server:
powershell
# Add at end of existing Windows script
$CentralServer = "central-server.company.com"
$SharePath = "\\$CentralServer\USB_Reports\$env:COMPUTERNAME"
# Create machine folder on share if missing
if(-not(Test-Path$SharePath)){
New-Item-ItemType Directory-Path$SharePath-Force|Out-Null
}
# Push latest CSV to central server
Copy-Item$csvFile-Destination$SharePath-Force
Write-Host"[+] Report pushed to central server: $SharePath"-ForegroundColor Green
Dashboard Preview
┌─────────────────────────────────────────────────────┐
│🔌USB Audit Central Dashboard [auto-refresh]│
├──────────┬──────────────┬─────────────┬─────────────┤
│ 1,243 │ 12 │ 89 │ 204 │
│ Events │ Machines │ USB Mounts │ File Access │
├──────────┴──────────────┴─────────────┴─────────────┤
│ [Bar: Events/Day] [Bar: By Machine] [Pie: EventType]│
├─────────────────────────────────────────────────────┤
│ Machine Summary Table │
│🟢web-server-01 │ 12 │ 3 │ 0 │ 2026-05-30 │
│🟡db-server-01 │ 67 │ 12 │ 5 │ 2026-05-30 │
│🔴finance-pc-01 │ 231 │ 45 │ 31 │ 2026-05-30 │
├─────────────────────────────────────────────────────┤
│🔍[Filter box] Event Log (latest 500) │
└─────────────────────────────────────────────────────┘
Summary
|
Component |
Location |
Purpose |
|
|
Central server |
Pulls CSVs from all agents |
|
|
Central server |
Builds live HTML dashboard |
|
|
Central server |
List of all monitored hosts |
|
|
Each Windows host |
Generates + pushes CSV |
|
|
Each Linux host |
Generates CSV locally |
|
nginx |
Central server |
Serves the dashboard |
|
cron |
Central server |
Automates collection + rebuild |
Would you like to add real-time email/Slack alerts, user-based risk scoring, or role-based access to the dashboard?
Q: Which feature would you like to add? A: All three
Q: Which platform? A: Both Windows & Linux
10:42 pm
Here's the complete implementation for all three features across both platforms.
Part 1: Real-Time Email & Slack Alerts
Linux — USB Event Watcher + Alerter
Save as /opt/usb-central/usb_alerter.py:
python
#!/usr/bin/env python3
"""
Real-time USB Alert System — Linux
Watches udev events and sends Email + Slack alerts instantly
"""
importsubprocess,smtplib,json,time,socket,requests
frommime.multipartimportMIMEMultipart
frommime.textimportMIMEText
fromdatetimeimportdatetime
# ── Config ──────────────────────────────────────────────────────────
SMTP_SERVER="smtp.company.com"
SMTP_PORT=587
SMTP_USER="usb-alerts@company.com"
SMTP_PASS="your_smtp_password"
EMAIL_TO=["security@company.com","itadmin@company.com"]
SLACK_WEBHOOK="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
HOSTNAME=socket.gethostname()
LOG_FILE="/var/log/usb-realtime-alerts.log"
# Risk rules — devices matching these trigger HIGH alerts
HIGH_RISK_VENDORS=["Unknown","Generic","Cheap"]
HIGH_RISK_KEYWORDS=["sandisk","kingston","verbatim","usb drive"]
# ── Helpers ──────────────────────────────────────────────────────────
deflog(msg):
ts=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
line=f"[{ts}] {msg}"
print(line)
withopen(LOG_FILE,"a")asf:
f.write(line+"\n")
defget_risk_level(vendor,model,user):
"""Simple risk scoring for alerts"""
score=0
vendor_l=(vendoror"").lower()
model_l=(modelor"").lower()
ifany(kinvendor_lforkinHIGH_RISK_KEYWORDS):score+=2
ifany(kinmodel_lforkinHIGH_RISK_KEYWORDS):score+=2
ifvendor_lin["unknown",""]:score+=3
ifuserin["root","Administrator"]:score+=2
ifscore>=5:return"🔴 CRITICAL"
ifscore>=3:return"🟡 HIGH"
return"🟢 LOW"
defsend_email(subject,html_body):
try:
msg=MIMEMultipart("alternative")
msg["Subject"]=subject
msg["From"]=SMTP_USER
msg["To"]=", ".join(EMAIL_TO)
msg.attach(MIMEText(html_body,"html"))
withsmtplib.SMTP(SMTP_SERVER,SMTP_PORT)ass:
s.starttls()
s.login(SMTP_USER,SMTP_PASS)
s.sendmail(SMTP_USER,EMAIL_TO,msg.as_string())
log(f"[EMAIL] Sent: {subject}")
exceptExceptionase:
log(f"[EMAIL ERROR] {e}")
defsend_slack(message,color,fields):
try:
payload={
"attachments":[{
"color":color,
"title":"🔌 USB Alert",
"text":message,
"fields":fields,
"footer":f"USB Monitor | {HOSTNAME}",
"ts":int(time.time())
}]
}
requests.post(SLACK_WEBHOOK,json=payload,timeout=5)
log("[SLACK] Alert sent")
exceptExceptionase:
log(f"[SLACK ERROR] {e}")
defalert(action,vendor,model,serial,user="unknown"):
ts=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
risk=get_risk_level(vendor,model,user)
color={"🔴 CRITICAL":"danger","🟡 HIGH":"warning"}.get(risk,"good")
emoji="🔴"if"CRITICAL"inriskelse"🟡"if"HIGH"inriskelse"🟢"
subject=f"{emoji} USB {action} — {HOSTNAME} — {risk}"
# ── HTML Email ──
html=f"""
<div style="font-family:sans-serif;max-width:600px;margin:auto">
<div style="background:#c0392b;color:white;padding:15px 20px;border-radius:8px 8px 0 0">
<h2 style="margin:0">🔌 USB {action} Detected</h2>
</div>
<div style="background:#f9f9f9;padding:20px;border:1px solid #ddd">
<table style="width:100%;border-collapse:collapse">
<tr><td style="padding:8px;color:#666;width:140px">Risk Level</td>
<td style="padding:8px;font-weight:bold">{risk}</td></tr>
<tr style="background:#fff"><td style="padding:8px;color:#666">Host</td>
<td style="padding:8px">{HOSTNAME}</td></tr>
<tr><td style="padding:8px;color:#666">Time</td>
<td style="padding:8px">{ts}</td></tr>
<tr style="background:#fff"><td style="padding:8px;color:#666">Vendor</td>
<td style="padding:8px">{vendor}</td></tr>
<tr><td style="padding:8px;color:#666">Model</td>
<td style="padding:8px">{model}</td></tr>
<tr style="background:#fff"><td style="padding:8px;color:#666">Serial</td>
<td style="padding:8px">{serial}</td></tr>
<tr><td style="padding:8px;color:#666">User</td>
<td style="padding:8px">{user}</td></tr>
<tr style="background:#fff"><td style="padding:8px;color:#666">Action</td>
<td style="padding:8px">{action}</td></tr>
</table>
</div>
<div style="background:#eee;padding:10px 20px;font-size:0.8em;color:#999;
border-radius:0 0 8px 8px">
USB Monitor v1.0 | {HOSTNAME} | Auto-generated alert
</div>
</div>"""
# ── Slack Fields ──
fields=[
{"title":"Host","value":HOSTNAME,"short":True},
{"title":"Risk","value":risk,"short":True},
{"title":"Vendor","value":vendor,"short":True},
{"title":"Model","value":model,"short":True},
{"title":"Serial","value":serial,"short":True},
{"title":"User","value":user,"short":True},
]
send_email(subject,html)
send_slack(f"USB *{action}* on `{HOSTNAME}` by `{user}`",color,fields)
log(f"[ALERT] {action} | {vendor} {model} | {serial} | {user} | {risk}")
# ── udev Monitor Loop ────────────────────────────────────────────────
def get_current_user():
try:
result=subprocess.run(["who"],capture_output=True,text=True)
users=[line.split()[0]forlineinresult.stdout.strip().splitlines()ifline]
returnusers[0]ifuserselse"unknown"
except:
return"unknown"
def watch_usb():
log("[*] USB Real-time Monitor started")
proc=subprocess.Popen(
["udevadm","monitor","--subsystem-match=usb","--property"],
stdout=subprocess.PIPE,stderr=subprocess.DEVNULL,text=True
)
props={}
forlineinproc.stdout:
line=line.strip()
ifnotline:
# End of event block — process it
action=props.get("ACTION","")
ifactionin("add","remove"):
vendor=props.get("ID_VENDOR","Unknown")
model=props.get("ID_MODEL","Unknown")
serial=props.get("ID_SERIAL_SHORT","N/A")
devtype=props.get("DEVTYPE","")
ifdevtype=="usb_device":
user=get_current_user()
alert(
action="Connected"ifaction=="add"else"Removed",
vendor=vendor,model=model,
serial=serial,user=user
)
props={}
elif"="inline:
k,_,v=line.partition("=")
props[k.strip()]=v.strip()
if__name__=="__main__":
watch_usb()
Deploy as a systemd service:
bash
sudotee/etc/systemd/system/usb-alerter.service<<EOF
[Unit]
Description=USB Real-Time Alert Monitor
After=network.target
[Service]
ExecStart=/usr/bin/python3 /opt/usb-central/usb_alerter.py
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudosystemctlenable--now usb-alerter
sudo systemctl status usb-alerter
Windows — PowerShell USB Watcher + Alerter
Save as USB-RealTime-Alert.ps1:
powershell
# ============================================
# USB Real-Time Alert — Windows
# Monitors WMI for USB events → Email + Slack
# ============================================
param(
[string]$SMTPServer="smtp.company.com",
[string]$EmailFrom="usb-alerts@company.com",
[string[]]$EmailTo= @("security@company.com","itadmin@company.com"),
[string]$SlackWebhook="https://hooks.slack.com/services/YOUR/WEBHOOK/URL",
[string]$LogFile="C:\Logs\USB-Alerts.log"
)
if(-not(Test-Path(Split-Path$LogFile))){
New-Item-ItemType Directory-Path(Split-Path$LogFile)-Force|Out-Null
}
functionWrite-Log($msg){
$ts=Get-Date-Format"yyyy-MM-dd HH:MM:ss"
"$ts $msg"|Tee-Object-FilePath$LogFile-Append
}
functionGet-RiskLevel($deviceName,$user){
$score= 0
$dn=$deviceName.ToLower()
if($dn-match"unknown|generic"){$score+=3}
if($dn-match"sandisk|kingston|usb"){$score+=2}
if($user-match"admin|administrator"){$score+=2}
if($score-ge5){return"CRITICAL"}
if($score-ge3){return"HIGH"}
return"LOW"
}
functionSend-SlackAlert($action,$device,$user,$risk){
$color=switch($risk){
"CRITICAL"{"danger"}
"HIGH"{"warning"}
default{"good"}
}
$body= @{
attachments = @(@{
color = $color
title = "USB $action Detected"
text = "USB *$action* on ``$env:COMPUTERNAME`` by ``$user``"
fields = @(
@{title="Host";value=$env:COMPUTERNAME;short=$true},
@{title="Risk";value=$risk;short=$true},
@{title="Device";value=$device;short=$true},
@{title="User";value=$user;short=$true},
@{title="Time";value=(Get-Date-Format"yyyy-MM-dd HH:mm:ss");short=$true}
)
footer = "USB Monitor | $env:COMPUTERNAME"
})
}|ConvertTo-Json-Depth 5
try{
Invoke-RestMethod-Uri$SlackWebhook-Method POST-Body$body`
-ContentType"application/json"|Out-Null
Write-Log"[SLACK] Alert sent for $device"
}catch{
Write-Log"[SLACK ERROR] $_"
}
}
functionSend-EmailAlert($action,$device,$user,$risk){
$emoji=if($risk-eq"CRITICAL"){"🔴"}elseif($risk-eq"HIGH"){"🟡"}else{"🟢"}
$subject="$emoji USB $action — $env:COMPUTERNAME — $risk"
$html= @"
<div style='font-family:sans-serif;max-width:600px;margin:auto'>
<div style='background:#c0392b;color:white;padding:15px 20px;border-radius:8px 8px 0 0'>
<h2 style='margin:0'>🔌 USB $action Detected</h2>
</div>
<div style='background:#f9f9f9;padding:20px;border:1px solid #ddd'>
<table style='width:100%;border-collapse:collapse'>
<tr><td style='padding:8px;color:#666;width:120px'>Risk</td>
<td style='padding:8px;font-weight:bold'>$risk</td></tr>
<tr style='background:#fff'><td style='padding:8px;color:#666'>Host</td>
<td style='padding:8px'>$env:COMPUTERNAME</td></tr>
<tr><td style='padding:8px;color:#666'>Time</td>
<td style='padding:8px'>$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')</td></tr>
<tr style='background:#fff'><td style='padding:8px;color:#666'>Device</td>
<td style='padding:8px'>$device</td></tr>
<tr><td style='padding:8px;color:#666'>User</td>
<td style='padding:8px'>$user</td></tr>
</table>
</div>
</div>
"@
try{
Send-MailMessage-To$EmailTo-From$EmailFrom-Subject$subject`
-Body$html-BodyAsHtml-SmtpServer$SMTPServer
Write-Log"[EMAIL] Sent: $subject"
}catch{
Write-Log"[EMAIL ERROR] $_"
}
}
# ── WMI Event Watcher ──────────────────────────────────────────────
Write-Log "[*] USB Real-Time Monitor started on $env:COMPUTERNAME"
$query = "SELECT * FROM __InstanceCreationEvent WITHIN 2
WHERE TargetInstance ISA 'Win32_USBHub'"
Register-WmiEvent-Query$query-SourceIdentifier"USBConnected"-Action{
$device=$event.SourceEventArgs.NewEvent.TargetInstance.Description
$user=(Get-WmiObjectWin32_ComputerSystem).UserName
$risk=Get-RiskLevel-deviceName$device-user$user
Write-Log"[ALERT] USB Connected: $device | User: $user | Risk: $risk"
Send-EmailAlert-action"Connected"-device$device-user$user-risk$risk
Send-SlackAlert-action"Connected"-device$device-user$user-risk$risk
}
Write-Log "[*] Watching for USB events... Press Ctrl+C to stop"
while($true){Start-Sleep-Seconds 5}
Run as a Windows Service:
powershell
# Install NSSM (Non-Sucking Service Manager)
choco install nssm-y
# Create service
nssm install USBAlertMonitor powershell.exe"-ExecutionPolicy Bypass -File C:\Scripts\USB-RealTime-Alert.ps1"
nssmsetUSBAlertMonitorStartSERVICE_AUTO_START
nssmstartUSBAlertMonitor
Part 2: User-Based Risk Scoring
Save as /opt/usb-central/risk_engine.py:
python
#!/usr/bin/env python3
"""
USB User Risk Scoring Engine
Tracks per-user USB behavior and generates risk scores
"""
importjson,os,csv,glob
fromdatetimeimportdatetime,timedelta
fromcollectionsimportdefaultdict
CENTRAL_DIR="/var/usb-central/reports"
RISK_DB="/var/usb-central/risk_scores.json"
RISK_REPORT="/var/www/html/usb-dashboard/risk.html"
# ── Risk Rules ───────────────────────────────────────────────────────
RULES={
"usb_connect_per_day":{"threshold":5,"points":10,"label":"Excessive USB connections/day"},
"file_access_per_day":{"threshold":20,"points":15,"label":"Excessive file access/day"},
"after_hours_activity":{"threshold":0,"points":20,"label":"USB activity outside business hours"},
"multiple_machines":{"threshold":2,"points":25,"label":"USB used on multiple machines same day"},
"unknown_device":{"threshold":0,"points":30,"label":"Unknown/unregistered USB device"},
"repeated_connect_remove":{"threshold":10,"points":15,"label":"Rapid connect/remove cycles"},
}
RISK_LEVELS={
(0,29):("LOW","#2ecc71"),
(30,59):("MEDIUM","#f39c12"),
(60,89):("HIGH","#e67e22"),
(90,999):("CRITICAL","#e74c3c"),
}
defget_risk_level(score):
for(lo,hi),(label,color)inRISK_LEVELS.items():
iflo<=score<=hi:
returnlabel,color
return"UNKNOWN","#999"
# ── Load & Analyze Events ────────────────────────────────────────────
defload_events(days=30):
events=[]
since=datetime.now()-timedelta(days=days)
forcsv_fileinglob.glob(f"{CENTRAL_DIR}/**/*.csv",recursive=True):
machine=os.path.basename(os.path.dirname(csv_file))
withopen(csv_file,newline="",encoding="utf-8",errors="ignore")asf:
forrowincsv.DictReader(f):
try:
ts=datetime.strptime(row.get("Timestamp","")[:19],"%Y-%m-%d %H:%M:%S")
ifts>=since:
row["machine"]=machine
row["_ts"]=ts
events.append(row)
except:
pass
returnevents
defscore_users(events):
user_data=defaultdict(lambda:{
"events":[],"machines":set(),"devices":set(),
"score":0,"violations":[],"last_seen":None
})
foreinevents:
user=e.get("User",e.get("user","unknown"))or"unknown"
user_data[user]["events"].append(e)
user_data[user]["machines"].add(e.get("machine",""))
user_data[user]["devices"].add(e.get("DeviceID",e.get("Details",""))[:30])
user_data[user]["last_seen"]=e.get("Timestamp","")
scores={}
foruser,datainuser_data.items():
score=0
violations=[]
evts=data["events"]
# Group by day
by_day=defaultdict(list)
foreinevts:
day=str(e["_ts"].date())
by_day[day].append(e)
connects=sum(1foreinevtsif"Connect"ine.get("Event",e.get("EventType","")))
file_accesses=sum(1foreinevtsif"File"ine.get("Event",e.get("EventType","")))
# Rule: Excessive connections/day
forday,day_evtsinby_day.items():
day_connects=sum(1foreinday_evtsif"Connect"ine.get("Event",""))
ifday_connects>RULES["usb_connect_per_day"]["threshold"]:
score+=RULES["usb_connect_per_day"]["points"]
violations.append(f"Excessive connections on {day} ({day_connects})")
# Rule: Excessive file access/day
forday,day_evtsinby_day.items():
day_files=sum(1foreinday_evtsif"File"ine.get("Event",""))
ifday_files>RULES["file_access_per_day"]["threshold"]:
score+=RULES["file_access_per_day"]["points"]
violations.append(f"Excessive file access on {day} ({day_files})")
# Rule: After-hours activity (before 7am or after 8pm)
after_hours=[eforeinevtsife["_ts"].hour<7ore["_ts"].hour>=20]
ifafter_hours:
score+=RULES["after_hours_activity"]["points"]*len(after_hours)
violations.append(f"After-hours USB activity ({len(after_hours)} events)")
# Rule: Multiple machines same day
forday,day_evtsinby_day.items():
machines_today=set(e.get("machine","")foreinday_evts)
iflen(machines_today)>RULES["multiple_machines"]["threshold"]:
score+=RULES["multiple_machines"]["points"]
violations.append(f"USB on {len(machines_today)} machines on {day}")
level,color=get_risk_level(min(score,999))
scores[user]={
"score":min(score,999),
"level":level,
"color":color,
"violations":violations[:10],
"total_events":len(evts),
"connects":connects,
"file_access":file_accesses,
"machines":list(data["machines"]),
"last_seen":data["last_seen"],
}
returndict(sorted(scores.items(),key=lambdax:x[1]["score"],reverse=True))
# ── Generate Risk HTML Page ──────────────────────────────────────────
defgenerate_risk_page(scores):
rows=""
foruser,sinscores.items():
violations_html="".join(f"<li>{v}</li>"forvins["violations"])or"<li>None</li>"
bar_w=min(s["score"],100)
rows+=f"""
<tr>
<td><strong>{user}</strong></td>
<td>
<div style='background:#2c2f3e;border-radius:4px;height:12px;width:150px'>
<div style='background:{s["color"]};width:{bar_w}%;height:12px;border-radius:4px'></div>
</div>
<span style='font-size:0.8em;color:#aaa'>{s["score"]} pts</span>
</td>
<td><span style='background:{s["color"]};color:white;padding:3px 10px;
border-radius:12px;font-size:0.8em'>{s["level"]}</span></td>
<td>{s["total_events"]}</td>
<td>{s["connects"]}</td>
<td>{s["file_access"]}</td>
<td>{", ".join(s["machines"][:3])}</td>
<td>{str(s["last_seen"])[:16]}</td>
<td><ul style='margin:0;padding-left:16px;font-size:0.8em;color:#aaa'>{violations_html}</ul></td>
</tr>"""
critical=sum(1forsinscores.values()ifs["level"]=="CRITICAL")
high=sum(1forsinscores.values()ifs["level"]=="HIGH")
html=f"""<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="300">
<title>USB Risk Scores</title>
<style>
body {{ font-family:Segoe UI,Ubuntu,sans-serif; background:#0f1117; color:#e0e0e0; padding:30px; }}
h1 {{ color:#e74c3c; }}
.summary {{ display:flex; gap:16px; margin:20px 0; flex-wrap:wrap; }}
.card {{ background:#1a1d27; border-radius:10px; padding:18px 24px;
border-left:4px solid #e74c3c; min-width:130px; text-align:center; }}
.card h3 {{ font-size:2em; color:#e74c3c; margin:0; }}
.card p {{ color:#888; font-size:0.85em; margin:4px 0 0; }}
table {{ width:100%; border-collapse:collapse; background:#1a1d27;
border-radius:10px; overflow:hidden; margin-top:20px; }}
th {{ background:#2c2f3e; color:#aaa; padding:11px 13px; text-align:left; font-size:0.85em; }}
td {{ padding:10px 13px; border-bottom:1px solid #1e2130; font-size:0.85em; vertical-align:top; }}
tr:hover td {{ background:#1e2130; }}
</style>
</head>
<body>
<h1>👤 USB User Risk Scores</h1>
<p style='color:#666'>Last 30 days | Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
<div class="summary">
<div class="card"><h3>{len(scores)}</h3><p>Users Tracked</p></div>
<div class="card"><h3>{critical}</h3><p>Critical Risk</p></div>
<div class="card"><h3>{high}</h3><p>High Risk</p></div>
</div>
<table>
<tr>
<th>User</th><th>Risk Score</th><th>Level</th><th>Events</th>
<th>Connects</th><th>File Access</th><th>Machines</th>
<th>Last Seen</th><th>Violations</th>
</tr>
{rows}
</table>
</body>
</html>"""
withopen(RISK_REPORT,"w")asf:
f.write(html)
withopen(RISK_DB,"w")asf:
json.dump({u:{k:vfork,vins.items()ifk!="_ts"}
foru,sinscores.items()},f,indent=2,default=str)
print(f"[✔] Risk report: {RISK_REPORT}")
print(f"[✔] Risk DB : {RISK_DB}")
returnscores
if__name__=="__main__":
print("[*] Loading events...")
events=load_events(days=30)
print(f"[*] Scoring {len(set(e.get('User','?') for e in events))} users...")
scores=score_users(events)
generate_risk_page(scores)
print(f"\nTop Risk Users:")
foruser,sinlist(scores.items())[:5]:
print(f" {s['level']:8} {s['score']:4} pts {user}")
Part 3: Role-Based Dashboard Access (nginx + htpasswd)
Setup RBAC with nginx
bash
# Install tools
sudoaptinstallnginx apache2-utils -y
# Create user DB
sudomkdir-p /etc/nginx/usb-auth
# Add roles
# Admin — sees everything
sudo htpasswd -c /etc/nginx/usb-auth/admins.htpasswd admin
sudo htpasswd /etc/nginx/usb-auth/admins.htpasswd soc_analyst
# Auditors — read-only, no risk page
sudo htpasswd -c /etc/nginx/usb-auth/auditors.htpasswd auditor1
sudo htpasswd /etc/nginx/usb-auth/auditors.htpasswd auditor2
# Managers — summary only
sudo htpasswd -c /etc/nginx/usb-auth/managers.htpasswd manager1
nginx config — save as /etc/nginx/sites-available/usb-dashboard:
nginx
server {
listen 443 ssl;
server_name usb-monitor.company.com;
ssl_certificate /etc/ssl/certs/usb-monitor.crt;
ssl_certificate_key /etc/ssl/private/usb-monitor.key;
root /var/www/html/usb-dashboard;
index index.html;
# ── Admin: full access ──────────────────────────
location /{
auth_basic "USB Dashboard — Admin";
auth_basic_user_file /etc/nginx/usb-auth/admins.htpasswd;
try_files $uri $uri/ =404;
}
# ── Auditors: dashboard + reports, no risk page ─
location ~ ^/(index\.html|reports/)${
auth_basic "USB Dashboard — Auditor";
auth_basic_user_file /etc/nginx/usb-auth/auditors.htpasswd;
try_files $uri $uri/ =404;
}
# ── Managers: summary only ──────────────────────
location /summary.html{
auth_basic "USB Dashboard — Manager";
auth_basic_user_file /etc/nginx/usb-auth/managers.htpasswd;
try_files $uri =404;
}
# ── Risk page: admins only ──────────────────────
location /risk.html{
auth_basic "USB Risk Report — Admin Only";
auth_basic_user_file /etc/nginx/usb-auth/admins.htpasswd;
try_files $uri =404;
}
# Block direct CSV access for non-admins
location ~* \.csv${
auth_basic "USB Reports — Admin Only";
auth_basic_user_file /etc/nginx/usb-auth/admins.htpasswd;
try_files $uri =404;
}
# Security headers
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000";
}
# Redirect HTTP → HTTPS
server {
listen 80;
server_name usb-monitor.company.com;
return 301 https://$host$request_uri;
}
bash
# Enable site
sudoln-s /etc/nginx/sites-available/usb-dashboard /etc/nginx/sites-enabled/
sudonginx -t&&sudosystemctl reload nginx
Full Automation — Single Crontab
cron
# Collect agent reports every 30 min
*/30 * * * * /opt/usb-central/collect.sh
# Regenerate main dashboard
*/30 * * * * sleep 90 && python3 /opt/usb-central/generate_dashboard.py
# Regenerate risk scores every hour
0 * * * * python3 /opt/usb-central/risk_engine.py
# Daily full run at 7 AM + email summary
0 7 * * * /opt/usb-central/collect.sh && \
python3 /opt/usb-central/generate_dashboard.py && \
python3 /opt/usb-central/risk_engine.py
Complete System Overview
┌─────────────────────────────────────────────────────────────┐
│ USB Security Platform │
├────────────┬────────────────────┬───────────────────────────┤
│ Agents │ Central Server │ Dashboard │
│ │ │ │
│ Windows │ collect.sh │ /index.html ← Admins │
│ WMI Watch │ (every 30 min) │ /risk.html ← Admins │
│ → CSV │ ↓ │ /reports/ ← Auditors │
│ → Push │ generate_ │ /summary.html← Managers │
│ │ dashboard.py │ │
│ Linux │ ↓ │ nginx RBAC │
│ udevadm │ risk_engine.py │ + SSL/TLS │
│ → CSV │ ↓ │ + Auth headers │
│ → rsync │ usb_alerter.py │ │
│ │ (real-time) │ Slack ← Instant alerts │
│ │ ↓ │ Email ← Instant alerts │
└────────────┴────────────────────┴───────────────────────────┘
Role Access Matrix
|
Page |
Admin |
SOC Analyst |
Auditor |
Manager |
|
|
✅ |
✅ |
✅ |
❌ |
|
|
✅ |
✅ |
❌ |
❌ |
|
|
✅ |
✅ |
✅ |
❌ |
|
|
✅ |
✅ |
✅ |
✅ |
|
Real-time alerts (Slack/Email) |
✅ |
✅ |
❌ |
❌ |
