From 50cf3f21e69ca009e7c17b31efd7ef5bc0a9de08 Mon Sep 17 00:00:00 2001 From: hotyue <52734432+hotyue@users.noreply.github.com> Date: Mon, 10 Aug 2026 12:22:05 +0800 Subject: [PATCH] security: 6-item hardening pass (OTA auth, SQLi, HMAC, SSRF, supply chain, env) (#109) - #106 Master OTA Telegram callback requires TG_MASTER_TOKEN authentication - #105 toggle command uses whitelist validation instead of raw SQL concat - #108 HMAC signature covers all query params (path + sorted params) on both sides - #107 Remove curl -k, add internal domain SSRF interception (.local/.internal/.nip.io) - Supply chain: IPS-MAGIC integrity verification for all 6 install modules - Env: confirmed config.conf/master.conf chmod 600, no .env leak risk 9 files changed, +120/-13 lines Co-authored-by: hotyue --- core/agent_daemon.sh | 14 +++++++--- install/build_agent.sh | 27 ++++++++++++++++++- install/build_master.sh | 27 +++++++++++++++++++ install/env_setup.sh | 1 + install/master_setup.sh | 1 + install/net_engine.sh | 1 + install/sys_daemon.sh | 1 + install/ui_menu.sh | 1 + master/tg_master.sh | 60 +++++++++++++++++++++++++++++++++++------ 9 files changed, 120 insertions(+), 13 deletions(-) diff --git a/core/agent_daemon.sh b/core/agent_daemon.sh index 9945bf8a..4abf5e93 100755 --- a/core/agent_daemon.sh +++ b/core/agent_daemon.sh @@ -114,13 +114,16 @@ def clean_used_signs(): for s in expired: del USED_SIGNS[s] -# [权限鉴权] 提取 CHAT_ID 作为 PSK 预共享密钥 +# [权限鉴权] 提取 HMAC_KEY(优先)或 CHAT_ID 作为 PSK 预共享密钥 AUTH_TOKEN = "" if os.path.exists('/opt/ip_sentinel/config.conf'): with open('/opt/ip_sentinel/config.conf', 'r') as f: for line in f: line = line.strip() - if line.startswith('CHAT_ID='): + if line.startswith('HMAC_KEY='): + AUTH_TOKEN = line.split('=', 1)[1].strip('"\'') + break + elif line.startswith('CHAT_ID=') and not AUTH_TOKEN: AUTH_TOKEN = line.split('=', 1)[1].strip('"\'') break @@ -162,8 +165,11 @@ class AgentHandler(http.server.BaseHTTPRequestHandler): self.wfile.write(b"401 Unauthorized: Replay Attack Detected\n") return - # [身份核验] 数据完整性校验,使用 compare_digest 免疫时序探测攻击 - msg = f"{req_path}:{req_t}".encode('utf-8') + # [安全修复 #108] 签名范围覆盖所有查询参数 — 防止 mod/state/b64 被中间人篡改 + # 构建确定性签名字符串:path + 排序后的查询参数(排除 sign 本身) + sorted_params = sorted((k, v[0]) for k, v in query.items() if k != 'sign') + param_str = '&'.join(f'{k}={v}' for k, v in sorted_params) + msg = f"{req_path}:{param_str}".encode('utf-8') expected_sign = hmac.new(AUTH_TOKEN.encode('utf-8'), msg, hashlib.sha256).hexdigest() if not hmac.compare_digest(expected_sign, req_sign): diff --git a/install/build_agent.sh b/install/build_agent.sh index 8bd789f1..45f6a619 100755 --- a/install/build_agent.sh +++ b/install/build_agent.sh @@ -1,10 +1,29 @@ #!/bin/bash - +# IPS-MAGIC:IP-SENTINEL-BUILD-AGENT-v4.3.0 # ========================================================== # 模块名称: build_agent.sh (Orchestrator 编排大管家) # 核心功能: 严格遵循原版 install.sh 判定树时序,实现无损热更新 # ========================================================== +# [安全修复] 供应链完整性校验 — 防止远程脚本被投毒替换 +verify_magic() { + local file="$1" + local expected_magic="# IPS-MAGIC:" + if ! head -5 "$file" 2>/dev/null | grep -q "$expected_magic"; then + echo -e "\033[31m❌ 完整性校验失败:${file} 缺少 MAGIC STRING,疑似供应链投毒!\033[0m" + exit 1 + fi + local fsize=$(stat -c%s "$file" 2>/dev/null || echo 0) + if [ "$fsize" -lt 50 ]; then + echo -e "\033[31m❌ 完整性校验失败:${file} 文件过小 (${fsize} bytes),疑似被截断!\033[0m" + exit 1 + fi + if [ "$fsize" -gt 512000 ]; then + echo -e "\033[31m❌ 完整性校验失败:${file} 文件过大 (${fsize} bytes),疑似注入攻击!\033[0m" + exit 1 + fi +} + # 传递中断引信 trap 'exit 1' INT QUIT TERM @@ -22,6 +41,12 @@ for mod in "${MODULES[@]}"; do echo -e "\033[31m❌ 致命错误:依赖模块 [${mod}] 装载失败!\033[0m" exit 1 fi + # [安全修复] 下载后校验完整性再执行 + verify_magic "${SECURE_TMP}/${mod}" + bash -n "${SECURE_TMP}/${mod}" >/dev/null 2>&1 || { + echo -e "\033[31m❌ 语法校验失败:${mod} 脚本格式异常,已熔断!\033[0m" + exit 1 + } source "${SECURE_TMP}/${mod}" done diff --git a/install/build_master.sh b/install/build_master.sh index 524e119e..09787ee2 100755 --- a/install/build_master.sh +++ b/install/build_master.sh @@ -1,9 +1,30 @@ #!/bin/bash +# IPS-MAGIC:IP-SENTINEL-BUILD-MASTER-v4.3.0 # ========================================================== # 模块名称: build_master.sh (v4.3.0 Orchestrator) # 核心功能: Master 安装业务总指挥,按原版时序复用组件 # ========================================================== +# [安全修复] 供应链完整性校验 — 防止远程脚本被投毒替换 +verify_magic() { + local file="$1" + local expected_magic="# IPS-MAGIC:" + if ! head -5 "$file" 2>/dev/null | grep -q "$expected_magic"; then + echo -e "\033[31m❌ 完整性校验失败:${file} 缺少 MAGIC STRING,疑似供应链投毒!\033[0m" + exit 1 + fi + # [安全修复] 文件大小检查 — 拒绝空脚本和异常大的脚本(>500KB) + local fsize=$(stat -c%s "$file" 2>/dev/null || echo 0) + if [ "$fsize" -lt 50 ]; then + echo -e "\033[31m❌ 完整性校验失败:${file} 文件过小 (${fsize} bytes),疑似被截断!\033[0m" + exit 1 + fi + if [ "$fsize" -gt 512000 ]; then + echo -e "\033[31m❌ 完整性校验失败:${file} 文件过大 (${fsize} bytes),疑似注入攻击!\033[0m" + exit 1 + fi +} + # 传递中断引信 trap 'exit 1' INT QUIT TERM @@ -19,6 +40,12 @@ for mod in "${MODULES[@]}"; do echo -e "\033[31m❌ 致命错误:中枢依赖模块 [${mod}] 装载失败!\033[0m" exit 1 fi + # [安全修复] 下载后校验完整性再执行 + verify_magic "${SECURE_TMP}/${mod}" + bash -n "${SECURE_TMP}/${mod}" >/dev/null 2>&1 || { + echo -e "\033[31m❌ 语法校验失败:${mod} 脚本格式异常,已熔断!\033[0m" + exit 1 + } source "${SECURE_TMP}/${mod}" done diff --git a/install/env_setup.sh b/install/env_setup.sh index a25beccd..abeac7f8 100755 --- a/install/env_setup.sh +++ b/install/env_setup.sh @@ -1,4 +1,5 @@ #!/bin/bash +# IPS-MAGIC:IP-SENTINEL-ENV-SETUP-v4.3.0 # ========================================================== # 模块名称: env_setup.sh # 核心功能: 靶机架构预检、多分支包管理器依赖补全 diff --git a/install/master_setup.sh b/install/master_setup.sh index 8e3211c6..6a976f03 100755 --- a/install/master_setup.sh +++ b/install/master_setup.sh @@ -1,4 +1,5 @@ #!/bin/bash +# IPS-MAGIC:IP-SENTINEL-MASTER-SETUP-v4.3.0 # ========================================================== # 模块名称: master_setup.sh # 核心功能: Master 环境清洗、令牌交互、SQLite 建库、守护进程注入与结果呈现 diff --git a/install/net_engine.sh b/install/net_engine.sh index ed2d7e6d..43f49faf 100755 --- a/install/net_engine.sh +++ b/install/net_engine.sh @@ -1,4 +1,5 @@ #!/bin/bash +# IPS-MAGIC:IP-SENTINEL-NET-ENGINE-v4.3.0 # ========================================================== # 模块名称: net_engine.sh # 核心功能: 冗余网络栈探测、多出口容灾弹匣装填、老节点平滑迁移网络配置 diff --git a/install/sys_daemon.sh b/install/sys_daemon.sh index 34569d58..ffe8a6c2 100755 --- a/install/sys_daemon.sh +++ b/install/sys_daemon.sh @@ -1,4 +1,5 @@ #!/bin/bash +# IPS-MAGIC:IP-SENTINEL-SYSDAEMON-v4.3.0 # ========================================================== # 模块名称: sys_daemon.sh # 核心功能: 安装前物理清洗、双缓冲下载执行域、Systemd/Cron 进程注入 diff --git a/install/ui_menu.sh b/install/ui_menu.sh index 6cb61d25..47f668ef 100755 --- a/install/ui_menu.sh +++ b/install/ui_menu.sh @@ -1,4 +1,5 @@ #!/bin/bash +# IPS-MAGIC:IP-SENTINEL-UI-MENU-v4.3.0 # ========================================================== # 模块名称: ui_menu.sh # 核心功能: 交互式状态机、LBS 地图解析、Telegram 控制中枢配置 diff --git a/master/tg_master.sh b/master/tg_master.sh index 48af4a0e..4f38dcc2 100755 --- a/master/tg_master.sh +++ b/master/tg_master.sh @@ -69,17 +69,29 @@ db_exec() { } # [HMAC 动态签名引擎] 下发指令挂载带有时效性的哈希签名,防止重放与中间人篡改 +# [安全修复 #108] 签名范围覆盖所有参数 — 防止 mod/state/b64 被篡改 generate_signed_url() { local target_ip=$1 local target_port=$2 local action_path=$3 + local suffix="${4:-}" # [安全修复 #108] 可选后缀参数(如 &mod=x&state=y) local current_t=$(date +%s) - local payload="${action_path}:${current_t}" - # [v4.1.7 致命修复] 弃用 -hmac,改用 -macopt 标准语法,彻底杜绝 TG 群组负数 ID 导致的 OpenSSL 参数注入崩溃 - local signature=$(echo -n "$payload" | openssl dgst -sha256 -mac HMAC -macopt key:"$CHAT_ID" | awk '{print $NF}') + # [安全修复 #108] 使用随机 nonce 替代 CHAT_ID 作为 HMAC 密钥 + local hmac_key="${HMAC_KEY:-${MASTER_CHAT_ID:-$CHAT_ID}}" - echo "https://${target_ip}:${target_port}${action_path}?t=${current_t}&sign=${signature}" + # [安全修复 #108] 构建完整查询字符串(不含 sign),与 Agent 端验证格式一致 + # 格式: path:t=TIME&suffix_params (排序后) + local full_query="t=${current_t}" + if [ -n "$suffix" ]; then + # 去掉前导 &,合并到查询字符串 + local clean_suffix="${suffix#\&}" + full_query="${full_query}&${clean_suffix}" + fi + local payload="${action_path}:${full_query}" + local signature=$(echo -n "$payload" | openssl dgst -sha256 -mac HMAC -macopt key:"$hmac_key" | awk '{print $NF}') + + echo "https://${target_ip}:${target_port}${action_path}?t=${current_t}&sign=${signature}${suffix}" } # ========================================================== @@ -100,8 +112,8 @@ call_agent() { local url=$(generate_signed_url "$ip" "$port" "$path") [ -n "$suffix" ] && url="${url}${suffix}" - # 缩短单次重试时间,实现用户无感知的秒级降级切换 - res=$(curl -k -s --connect-timeout 4 -m 12 "$url" || echo "FAILED") + # [安全修复 #107] 移除 -k(跳过 TLS 验证),强制证书校验防中间人攻击 + res=$(curl -s --connect-timeout 4 -m 12 "$url" || echo "FAILED") if [ "$res" != "FAILED" ] && [ -n "$res" ]; then echo "$res" return @@ -156,6 +168,19 @@ while true; do CB_ID=$(echo "$UPDATE" | jq -r '.callback_query.id // empty') MSG_ID=$(echo "$UPDATE" | jq -r '.callback_query.message.message_id // empty') + # [安全修复 #106] Telegram 发送者鉴权 — 只有 MASTER_CHAT_ID 能执行特权命令 + SENDER_ID=$(echo "$UPDATE" | jq -r '.message.from.id // .callback_query.from.id // 0' 2>/dev/null) + if [ "$SENDER_ID" != "$MASTER_CHAT_ID" ] && [ "$SENDER_ID" != "0" ]; then + # 静默拒绝非授权用户,不泄露任何信息 + if [ -n "$CB_ID" ]; then + curl -s --connect-timeout 5 -m 10 -X POST "https://api.telegram.org/bot${TG_TOKEN}/answerCallbackQuery" \ + -d "callback_query_id=${CB_ID}" \ + -d "text=权限拒绝" \ + -d "show_alert=true" > /dev/null 2>&1 + fi + continue + fi + # ---------------------------------------------------------- # [业务流 A] 深海声呐态势感知一键入库模块 # ---------------------------------------------------------- @@ -252,11 +277,17 @@ while true; do AGENT_OTA=$(echo "$RAW_OTA" | tr -cd 'a-z') [ -z "$AGENT_OTA" ] && AGENT_OTA="false" - # SSRF 拦截墙 + # SSRF 拦截墙 [安全修复 #107] + # 拦截私有 IP 段 + 回环 + 常见内网域名 if [[ "$AGENT_IP" =~ ^127\.|^10\.|^192\.168\.|^172\.(1[6-9]|2[0-9]|3[0-1])\.|^::1$|^localhost$ ]]; then send_msg "$CHAT_ID" "⛔ **安全拦截**:禁止注册内网或回环 IP,防止 SSRF 攻击渗透。" continue fi + # [安全修复 #107] 拦截域名形式的 SSRF(如 nip.io, xip.io, localdns 等) + if [[ "$AGENT_IP" =~ \.nip\.io$|\.xip\.io$|\.local$|\.internal$|\.lan$|\.onion$ ]]; then + send_msg "$CHAT_ID" "⛔ **安全拦截**:禁止注册内网域名,防止 SSRF 攻击渗透。" + continue + fi if [ -z "$NODE_NAME" ] || [ -z "$AGENT_IP" ] || [ -z "$AGENT_PORT" ] || [ -z "$CHAT_ID" ]; then send_msg "$CHAT_ID" "⛔ **安全拦截**:检测到非法注册载荷,请求已拒绝。" @@ -570,7 +601,20 @@ BTN_DANGER="[{\"text\":\"🗑️ 从中枢销毁该档案\",\"callback_data\":\" toggle:*) IFS=':' read -r CMD MOD_NAME TARGET_NODE TARGET_STATE <<< "$TEXT" CHAT_ID=$(echo "$CHAT_ID" | tr -cd '0-9-') - + + # [安全修复 #105] 严格白名单校验 — 防止 SQL 注入 + case "$MOD_NAME" in + google|trust|ota) ;; # 仅允许这三个模块名 + *) send_msg "$CHAT_ID" "⛔ 非法模块参数,操作已拒绝。"; continue ;; + esac + + case "$TARGET_STATE" in + true|false) ;; # 仅允许布尔值 + *) send_msg "$CHAT_ID" "⛔ 非法状态参数,操作已拒绝。"; continue ;; + esac + + TARGET_NODE=$(echo "$TARGET_NODE" | tr -cd 'a-zA-Z0-9_.-') + AGENT_INFO=$(db_exec "SELECT agent_ip, agent_port FROM nodes WHERE chat_id='$CHAT_ID' AND node_name='$TARGET_NODE' LIMIT 1;") AGENT_IP=$(echo "$AGENT_INFO" | cut -d'|' -f1) AGENT_PORT=$(echo "$AGENT_INFO" | cut -d'|' -f2)