Files
tamabox/dev/security-selfcheck.sh
T
2026-09-08 10:10:44 +08:00

157 lines
6.6 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# TamaBox Cloudflare Tunnel 部署安全自检脚本
#
# 架构前提:
# 访客 → Cloudflare 边缘(a.b.com 自定义主机名证书 / a.a.com 主域)
# → cloudflared Tunnel(源站仅出站连接)→ 127.0.0.1:5877 (TamaBox)
#
# 用法:
# 第一部分(在源站服务器上执行): ./security-selfcheck.sh local
# 第二部分(在你自己的电脑上执行):SERVER_IP=源站公网IP CF_DOMAIN=a.a.com CUSTOM_DOMAIN=a.b.com ./security-selfcheck.sh remote
# 两部分都跑: ./security-selfcheck.sh all
#
set -u
MODE="${1:-all}"
SERVER_IP="${SERVER_IP:-}"
CF_DOMAIN="${CF_DOMAIN:-a.a.com}"
CUSTOM_DOMAIN="${CUSTOM_DOMAIN:-a.b.com}"
APP_PORT="${APP_PORT:-5877}"
section() { printf "\n========== %s ==========\n" "$1"; }
ok() { echo "[✓] $1"; }
warn() { echo "[!] $1"; }
bad() { echo "[✗] $1"; }
# 用 bash /dev/tcp 探测 TCP 端口是否可达(3 秒超时)
port_open() {
(timeout 3 bash -c "echo > /dev/tcp/$1/$2") 2>/dev/null
}
# ----------------------------------------------------------------
# 第一部分:源站本地检查
# ----------------------------------------------------------------
local_checks() {
section "1. 应用与数据库端口绑定检查(核心)"
BIND=$(ss -tlnp 2>/dev/null | grep ":${APP_PORT} " || true)
echo "$BIND"
if echo "$BIND" | grep -qE "127\.0\.0\.1:${APP_PORT}|\[::1\]:${APP_PORT}"; then
ok "TamaBox 只绑定在 127.0.0.1:${APP_PORT},外部无法直连(正确)"
elif [ -z "$BIND" ]; then
warn "端口 ${APP_PORT} 未监听——确认 TamaBox 是否在运行"
else
bad "TamaBox 绑定在 0.0.0.0/*:${APP_PORT}(监听所有网卡)!"
echo " 修复:conf/app.ini [server] 段设置 listen = 127.0.0.1:${APP_PORT} 并重启"
fi
section "2. PostgreSQL / Redis 是否暴露公网"
for p in 5432 6379; do
BIND=$(ss -tlnp 2>/dev/null | grep ":${p} " || true)
if echo "$BIND" | grep -qE "0\.0\.0\.0:${p}|\*:${p}"; then
bad "端口 ${p} 绑定在所有网卡!必须改为 127.0.0.1 或加防火墙(云数据库直接暴露=高危)"
else
ok "端口 ${p} 未对外监听(${BIND:-未运行})"
fi
done
section "3. cloudflared 状态与 ingress 配置"
if systemctl is-active --quiet cloudflared 2>/dev/null || pgrep -x cloudflared >/dev/null 2>&1; then
ok "cloudflared 正在运行"
else
bad "cloudflared 未运行——Tunnel 断开,所有域名不可达"
fi
CFG=""
for f in /etc/cloudflared/config.yml /root/.cloudflared/config.yml /home/*/.cloudflared/config.yml; do
[ -f "$f" ] && CFG="$f" && break
done
if [ -n "$CFG" ]; then
echo "配置文件:$CFG"
grep -nE "hostname|service|originRequest" "$CFG" || true
if grep -qE "service:\s*http://127\.0\.0\.1:${APP_PORT}|localhost:${APP_PORT}" "$CFG"; then
ok "ingress service 指向 127.0.0.1:${APP_PORT}(正确)"
else
warn "未在 config.yml 中找到指向 127.0.0.1:${APP_PORT} 的 service,请人工核对 ingress 规则"
fi
else
warn "未找到 config.yml(若用 token 模式 + 远端 ingress,请到 Cloudflare 面板核对 Public Hostname 的 service 地址)"
fi
section "4. 本地请求验证(模拟 cloudflared 的视角)"
RESP=$(curl -s -m 5 -o /dev/null -w "%{http_code}" "http://127.0.0.1:${APP_PORT}/" || true)
if [ "$RESP" = "200" ] || [ "$RESP" = "302" ] || [ "$RESP" = "303" ]; then
ok "本地 HTTP 请求返回 $RESP,应用正常"
else
warn "本地请求返回 ${RESP:-无响应},请人工确认"
fi
section "5. 防火墙状态(仅供参考)"
if command -v ufw >/dev/null 2>&1; then
ufw status verbose 2>/dev/null | head -15 || true
elif command -v firewall-cmd >/dev/null 2>&1; then
firewall-cmd --list-all 2>/dev/null || true
else
iptables -L INPUT -n --line-numbers 2>/dev/null | head -15 || true
fi
warn "请人工确认:除 SSH(22) 外,80/443/5877 等端口不应有公网入站放行规则"
}
# ----------------------------------------------------------------
# 第二部分:外部探测(在自己的电脑上执行)
# ----------------------------------------------------------------
remote_checks() {
if [ -z "$SERVER_IP" ]; then
bad "未设置 SERVER_IP 环境变量,跳过直连探测(用法见文件头注释)"
else
section "6. 直连源站 IP 探测(核心:防 IP+端口直连)"
for p in "$APP_PORT" 80 443 5432 6379; do
if port_open "$SERVER_IP" "$p"; then
case "$p" in
22) warn "端口 22 开放(SSH,正常,但建议改密钥登录/限来源 IP)" ;;
*) bad "端口 ${p} 从公网可达!存在被直连/攻击面,去服务器防火墙封掉它" ;;
esac
else
ok "端口 ${p} 从公网不可达"
fi
done
fi
section "7. 经域名的正常访问验证"
for d in "$CF_DOMAIN" "$CUSTOM_DOMAIN"; do
CODE=$(curl -s -m 10 -o /dev/null -w "%{http_code}" "https://${d}/" || true)
RAY=$(curl -sI -m 10 "https://${d}/" | grep -i "^cf-ray:" || true)
if [ -n "$RAY" ]; then
ok "https://${d}/ 返回 ${CODE:-?},且带 ${RAY%%$'\r'} → 确认流量经过 Cloudflare 边缘"
elif [ "$CODE" = "200" ] || [ "$CODE" = "302" ] || [ "$CODE" = "303" ]; then
warn "https://${d}/ 返回 ${CODE} 但没有 cf-ray 头 → 流量可能没走 Cloudflare(自定义主机名未生效?)"
else
bad "https://${d}/ 不可达(${CODE:-无响应})"
fi
done
section "8. 伪造头穿透测试(验证 Cloudflare 会重写伪造头)"
# 若伪造头被 CF 覆盖,应用侧记录的仍是真实 IP;此测试主要用于演示,无法从响应直接观察,
# 需配合 TamaBox 侧日志/封禁记录核对(见第 9 项说明)。
curl -s -m 10 -o /dev/null \
-H "CF-Connecting-IP: 203.0.113.66" \
-H "X-Forwarded-For: 203.0.113.66" \
"https://${CUSTOM_DOMAIN}/" || true
ok "已发送带伪造 CF-Connecting-IP 的请求(CF 边缘会将其重写为你的真实 IP)"
section "9. HTTP 明文与 HSTS 检查"
LOC=$(curl -sI -m 10 "http://${CUSTOM_DOMAIN}/" | grep -iE "^(HTTP|location):" | tr -d '\r')
echo "$LOC"
HSTS=$(curl -sI -m 10 "https://${CUSTOM_DOMAIN}/" | grep -i "strict-transport-security" | tr -d '\r')
if [ -n "$HSTS" ]; then ok "$HSTS"; else warn "未返回 HSTS 头(若浏览器直访 http 可能被中间人,建议确认 securityHeaders 已生效)"; fi
}
case "$MODE" in
local) local_checks ;;
remote) remote_checks ;;
all) local_checks; remote_checks ;;
*) echo "用法: $0 {local|remote|all}"; exit 1 ;;
esac
section "自检完成"
echo "逐项核对上面的 [✓]/[!]/[✗],所有 [✗] 都需要修复后再跑一遍。"