secubox-openwrt/package/secubox/luci-app-secubox-security-threats/root/usr/libexec/rpcd/luci.secubox-security-threats
CyberMind-FR 8c0f6a9b73 fix(security-threats): Detect mitmproxy running in LXC container
The process name is mitmweb (not mitmdump) when running inside the
mitmproxy LXC container. Use pgrep -f mitmweb for correct detection.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 11:34:51 +01:00

319 lines
10 KiB
Bash
Executable File

#!/bin/sh
# SPDX-License-Identifier: Apache-2.0
# SecuBox Security Intelligence - RPCD backend
# Copyright (C) 2026 CyberMind.fr - Gandalf
#
# Combines mitmproxy threat detection, CrowdSec blocking,
# and P2P mesh threat intelligence sharing
. /lib/functions.sh
. /usr/share/libubox/jshn.sh
CSCLI="/usr/bin/cscli"
THREAT_INTEL="/usr/lib/secubox/threat-intel.sh"
# ==============================================================================
# HELPERS
# ==============================================================================
execute_block() {
local ip="$1" duration="$2" reason="$3"
[ -x "$CSCLI" ] || return 1
$CSCLI decisions add --ip "$ip" --duration "$duration" --reason "$reason" >/dev/null 2>&1
}
# ==============================================================================
# SECURITY STATS (firewall counters)
# ==============================================================================
get_security_stats() {
local wan_drops=0 fw_rejects=0 cs_bans=0 cs_alerts_24h=0 haproxy_conns=0 invalid_conns=0
# WAN dropped packets from nftables
if command -v nft >/dev/null 2>&1; then
wan_drops=$(nft list chain inet fw4 input_wan 2>/dev/null | \
grep -oE 'packets [0-9]+' | awk '{sum+=$2} END {print sum+0}')
local reject_drops=$(nft list chain inet fw4 reject_from_wan 2>/dev/null | \
grep -oE 'packets [0-9]+' | awk '{sum+=$2} END {print sum+0}')
[ -n "$reject_drops" ] && wan_drops=$((${wan_drops:-0} + reject_drops))
fi
# Firewall rejects
if command -v nft >/dev/null 2>&1; then
fw_rejects=$(nft list chain inet fw4 handle_reject 2>/dev/null | \
grep -oE 'packets [0-9]+' | awk '{sum+=$2} END {print sum+0}')
if [ "${fw_rejects:-0}" = "0" ]; then
fw_rejects=$(nft -a list ruleset 2>/dev/null | \
grep -E "reject|drop" | grep "counter" | \
grep -oE 'packets [0-9]+' | awk '{sum+=$2} END {print sum+0}')
fi
fi
# CrowdSec bans and alerts
if [ -x "$CSCLI" ]; then
if command -v jq >/dev/null 2>&1; then
cs_bans=$($CSCLI decisions list -o json 2>/dev/null | jq 'length' 2>/dev/null)
cs_alerts_24h=$($CSCLI alerts list -o json --since 24h 2>/dev/null | jq 'length' 2>/dev/null)
else
local cs_json=$($CSCLI decisions list -o json 2>/dev/null)
[ -n "$cs_json" ] && [ "$cs_json" != "null" ] && [ "$cs_json" != "[]" ] && \
cs_bans=$(echo "$cs_json" | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
local alerts_json=$($CSCLI alerts list -o json --since 24h 2>/dev/null)
[ -n "$alerts_json" ] && [ "$alerts_json" != "null" ] && [ "$alerts_json" != "[]" ] && \
cs_alerts_24h=$(echo "$alerts_json" | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
fi
fi
# Invalid connections
if [ -f /proc/net/nf_conntrack ]; then
invalid_conns=$(grep -c "\[INVALID\]" /proc/net/nf_conntrack 2>/dev/null)
fi
# HAProxy connections
if [ -S /var/run/haproxy/admin.sock ]; then
haproxy_conns=$(echo "show stat" | socat stdio /var/run/haproxy/admin.sock 2>/dev/null | \
tail -n+2 | awk -F, '{sum+=$5} END {print sum+0}')
elif [ -S /var/lib/haproxy/stats ]; then
haproxy_conns=$(echo "show stat" | socat stdio /var/lib/haproxy/stats 2>/dev/null | \
tail -n+2 | awk -F, '{sum+=$5} END {print sum+0}')
elif command -v lxc-info >/dev/null 2>&1 && lxc-info -n haproxy -s 2>/dev/null | grep -q "RUNNING"; then
haproxy_conns=$(lxc-attach -n haproxy -- sh -c '
for sock in /stats /run/haproxy.sock /var/lib/haproxy/stats /var/run/haproxy/admin.sock; do
[ -S "$sock" ] && { echo "show stat" | socat stdio "$sock" 2>/dev/null; break; }
done' 2>/dev/null | tail -n+2 | awk -F, '{sum+=$5} END {print sum+0}')
fi
# Sanitize to numeric
wan_drops=$(printf '%s' "${wan_drops:-0}" | tr -cd '0-9'); : ${wan_drops:=0}
fw_rejects=$(printf '%s' "${fw_rejects:-0}" | tr -cd '0-9'); : ${fw_rejects:=0}
cs_bans=$(printf '%s' "${cs_bans:-0}" | tr -cd '0-9'); : ${cs_bans:=0}
cs_alerts_24h=$(printf '%s' "${cs_alerts_24h:-0}" | tr -cd '0-9'); : ${cs_alerts_24h:=0}
invalid_conns=$(printf '%s' "${invalid_conns:-0}" | tr -cd '0-9'); : ${invalid_conns:=0}
haproxy_conns=$(printf '%s' "${haproxy_conns:-0}" | tr -cd '0-9'); : ${haproxy_conns:=0}
cat << EOF
{
"wan_dropped": $wan_drops,
"firewall_rejects": $fw_rejects,
"crowdsec_bans": $cs_bans,
"crowdsec_alerts_24h": $cs_alerts_24h,
"invalid_connections": $invalid_conns,
"haproxy_connections": $haproxy_conns
}
EOF
}
# ==============================================================================
# UBUS INTERFACE
# ==============================================================================
case "$1" in
list)
json_init
json_add_object "status"; json_close_object
json_add_object "get_security_stats"; json_close_object
json_add_object "get_active_threats"; json_close_object
json_add_object "get_blocked_ips"; json_close_object
json_add_object "block_threat"
json_add_string "ip" "string"
json_add_string "duration" "string"
json_add_string "reason" "string"
json_close_object
json_add_object "whitelist_host"
json_add_string "ip" "string"
json_add_string "reason" "string"
json_close_object
json_add_object "remove_whitelist"
json_add_string "ip" "string"
json_close_object
json_add_object "get_threat_intel"; json_close_object
json_add_object "get_mesh_iocs"; json_close_object
json_add_object "get_mesh_peers"; json_close_object
json_add_object "publish_intel"; json_close_object
json_add_object "apply_intel"; json_close_object
json_dump
;;
call)
case "$2" in
status)
json_init
json_add_boolean "netifyd_running" $(pgrep netifyd >/dev/null 2>&1 && echo 1 || echo 0)
json_add_boolean "crowdsec_running" $(pgrep crowdsec >/dev/null 2>&1 && echo 1 || echo 0)
json_add_boolean "mitmproxy_running" $(pgrep -f mitmweb >/dev/null 2>&1 && echo 1 || echo 0)
json_add_boolean "cscli_available" $([ -x "$CSCLI" ] && echo 1 || echo 0)
json_add_boolean "threat_intel_available" $([ -x "$THREAT_INTEL" ] && echo 1 || echo 0)
json_dump
;;
get_security_stats)
get_security_stats
;;
get_active_threats)
_log_file="/srv/mitmproxy/threats.log"
if [ -f "$_log_file" ]; then
_threats=$(tail -50 "$_log_file" 2>/dev/null | jq -sc '
map({
ip: .source_ip,
severity: .severity,
score: (if .severity == "critical" then 90
elif .severity == "high" then 70
elif .severity == "medium" then 50
else 30 end),
type: .type,
pattern: .pattern,
host: .host,
country: (.country // "??"),
timestamp: .timestamp,
request: (.request // "-"),
is_bot: (.is_bot // false),
bot_type: (.bot_type // null),
cve: (.cve // null)
}) | unique_by(.ip) | sort_by(.score) | reverse
' 2>/dev/null || echo "[]")
printf '{"threats":%s}\n' "$_threats"
else
echo '{"threats":[]}'
fi
;;
get_blocked_ips)
if [ -x "$CSCLI" ]; then
_decisions=$($CSCLI decisions list -o json 2>/dev/null || echo "[]")
printf '{"blocked":%s}\n' "$_decisions"
else
echo '{"blocked":[]}'
fi
;;
block_threat)
read -r input
json_load "$input"
json_get_var ip ip
json_get_var duration duration
json_get_var reason reason
duration=${duration:-4h}
reason=${reason:-"Manual block from Security Dashboard"}
if [ -z "$ip" ]; then
json_init
json_add_boolean "success" 0
json_add_string "error" "IP address required"
json_dump
elif execute_block "$ip" "$duration" "$reason"; then
json_init
json_add_boolean "success" 1
json_add_string "message" "IP $ip blocked for $duration"
json_dump
else
json_init
json_add_boolean "success" 0
json_add_string "error" "Failed to block IP"
json_dump
fi
;;
whitelist_host)
read -r input
json_load "$input"
json_get_var ip ip
json_get_var reason reason
reason=${reason:-"Whitelisted from Security Dashboard"}
if [ -z "$ip" ]; then
json_init
json_add_boolean "success" 0
json_add_string "error" "IP address required"
json_dump
else
local section="whitelist_${ip//./_}"
uci set "secubox_security_threats.${section}=whitelist"
uci set "secubox_security_threats.${section}.ip=$ip"
uci set "secubox_security_threats.${section}.reason=$reason"
uci set "secubox_security_threats.${section}.added_at=$(date -Iseconds)"
uci commit secubox_security_threats
json_init
json_add_boolean "success" 1
json_add_string "message" "IP $ip whitelisted"
json_dump
fi
;;
remove_whitelist)
read -r input
json_load "$input"
json_get_var ip ip
if [ -z "$ip" ]; then
json_init
json_add_boolean "success" 0
json_add_string "error" "IP address required"
json_dump
else
local section="whitelist_${ip//./_}"
uci delete "secubox_security_threats.${section}" 2>/dev/null
uci commit secubox_security_threats
json_init
json_add_boolean "success" 1
json_add_string "message" "IP $ip removed from whitelist"
json_dump
fi
;;
get_threat_intel)
if [ -x "$THREAT_INTEL" ]; then
"$THREAT_INTEL" status 2>/dev/null
else
echo '{"enabled":false,"error":"threat-intel not available"}'
fi
;;
get_mesh_iocs)
if [ -x "$THREAT_INTEL" ]; then
_iocs=$("$THREAT_INTEL" list received 2>/dev/null)
printf '{"iocs":%s}\n' "${_iocs:-[]}"
else
echo '{"iocs":[]}'
fi
;;
get_mesh_peers)
if [ -x "$THREAT_INTEL" ]; then
_peers=$("$THREAT_INTEL" peers 2>/dev/null)
printf '{"peers":%s}\n' "${_peers:-[]}"
else
echo '{"peers":[]}'
fi
;;
publish_intel)
if [ -x "$THREAT_INTEL" ]; then
"$THREAT_INTEL" collect-and-publish >/dev/null 2>&1 &
json_init
json_add_boolean "started" 1
json_add_string "message" "Publish started in background"
json_dump
else
echo '{"error":"threat-intel not available"}'
fi
;;
apply_intel)
if [ -x "$THREAT_INTEL" ]; then
"$THREAT_INTEL" apply-pending 2>/dev/null
"$THREAT_INTEL" status 2>/dev/null
else
echo '{"error":"threat-intel not available"}'
fi
;;
*)
json_init
json_add_string "error" "Unknown method: $2"
json_dump
;;
esac
;;
esac