跳转至

新服上线安全加固

01 前置理论与核心机制

1. 公网环境的安全现状

  • 全网自动化扫描:新开通的云服务器一旦绑定公网 IP,通常在数分钟内就会收到全网自动化僵尸网络、弱口令扫描器及漏洞爬虫的探测请求。
  • 数据对比:在同一统计周期内,未做安全加固的默认主机通常会遭受数万次 SSH 字典爆破尝试(如 52414 次);而经过底层暴露面收敛的主机,可将无效探测与盲扫请求降低数个数量级(如降至 17 次)。

2. 暴露面收敛 (Attack Surface Reduction) 核心原则

主机安全防御并非依赖单一配置,而是通过多层拦截策略(纵深防御)持续缩减公网暴露面,提高攻击者的探测与攻击成本。核心原则为:隐藏默认端口、收敛公网入口、应用层白名单放行、内核层动态封禁。

02 安全加固与配置闭环 SOP

为防止公网服务器被植入木马或遭遇勒索攻击,新节点上线前需按以下四道防线完成基线加固:

第一道防线:修改 SSH 默认端口与禁用密码认证

自动化扫描脚本主要针对默认的 22 端口进行批量探测,修改默认端口并强制密钥登录可规避绝大多数自动化脚本攻击。

# 过滤注释和空行,核查当前生效的 SSH 核心配置
grep -E -v "^#|^$" /etc/ssh/sshd_config
  • 核心配置项:
  • Port 28475:将默认的 22 端口修改为高位端口(建议范围为 10000-65535)。
  • PasswordAuthentication no:直接关闭密码认证,仅允许基于公私钥对(SSH Key)的身份认证,从根源上阻断字典暴力破解。
  • 生效验证:修改配置后,需先在防火墙与云安全组中放行新端口,随后执行 systemctl restart sshd。注意保持当前终端会话不断开,另开一个新终端窗口测试新端口连通性,防止配置失误导致失联。

第二道防线:利用 Nginx 四层 Stream 模块收敛公网端口

避免在公网直接暴露过多业务端口(如内部管理系统、代理服务或中间件端口)。可利用 Nginx 的 stream 模块基于 SNI(Server Name Indication)进行四层流量分发,实现多服务复用单一公网端口。

# /etc/nginx/nginx.conf 中的 stream 四层配置块
stream {
    # 开启 TLS SNI 预读,根据客户端请求携带的域名动态匹配后端上游
    map $ssl_preread_server_name $backend_name {
        www.hesychia.top  web_server;
        frp.hesychia.top  frp_server;
        default           web_server;
    }

    upstream web_server { server 127.0.0.1:1234; }
    upstream frp_server { server 127.0.0.1:7000; }

    server {
        # 公网统一仅暴露 443 端口
        listen 443;
        listen [::]:443;
        proxy_pass $backend_name;
        # 启用客户端 Hello 报文中的 SNI 字段预读
        ssl_preread on;
    }
}
  • 架构逻辑:内部各业务进程仅绑定本地回环地址(127.0.0.1),公网侧统一仅暴露 443 端口。Nginx 在不解密 TLS 流量的前提下通过 SNI 识别目标域名,并在本机内部完成端口转发,大幅缩减外部端口扫描可见性。

第三道防线:Nginx 七层应用级白名单与敏感路径过滤

在 HTTP 应用层,针对恶意 IP 探测与目录扫描配置严格的过滤规则:

# 1. 拦截未携带有效域名的纯 IP 扫描请求
server {
    listen 80 default_server;
    listen 127.0.0.1:1234 ssl default_server;
    server_name _;
    # 返回 Nginx 特有的 444 状态码,直接关闭底层 TCP 连接,不返回任何响应头
    return 444;
}

# 2. 拦截敏感后缀探测与非白名单路径
server {
    listen 127.0.0.1:1234 ssl;
    server_name www.hesychia.top;

    # 针对探测备份文件、配置文件的爬虫请求,直接断开连接且关闭日志记录,防止占满磁盘空间
    location ~* \.(php|sql|env|bak|xml|cgi|pl|jsp|config|log|swp|zip|tar|gz)$ {
        access_log off;
        log_not_found off;
        return 444;
    }

    # 3. 业务路由白名单精确放行
    location ~ ^/(admin|login|api|static|$) {
        proxy_pass [http://127.0.0.1:8080](http://127.0.0.1:8080);
    }

    # 4. 全局兜底拦截 (拒绝所有未匹配白名单的异常路径探测)
    location / {
        access_log /var/log/nginx/blocked.log;
        return 444;
    }
}

第四道防线:部署 Fail2ban 实现动态内核级封禁

针对绕过常规限制或持续对高端口发起高频探测的 IP,配合 Fail2ban 实现自动化封禁:

# 查看 Fail2ban 运行状态及当前生效的监控策略 (Jail)
fail2ban-client status

  • 联动机制:Fail2ban 实时监控 /var/log/secure(或 auth.log)以及 Nginx 的 /var/log/nginx/blocked.log。一旦检测到同一源 IP 在设定时间窗口内认证失败或触发 444 规则达到阈值,立即调用底层 iptables 或 firewalld 将该 IP 在网络层直接丢弃(DROP)。

03 真实生产故障现象与案例库

案例 1:保留默认 22 端口导致认证日志占满系统盘

  • 故障现象:某新上线的云主机运行两天后,监控告警根分区磁盘使用率达到 100%,导致业务服务无法写入数据而中断。
  • 排查与处置:执行 du -sh /var/log/* 排查发现,/var/log/secure(认证日志)和 /var/log/btmp(记录失败登录的二进制日志)体积膨胀至数十 GB。原因是服务器保留了默认的 22 端口且开启了密码登录,遭到外部僵尸网络持续高频并发爆破。处置方案为清空历史日志,修改 SSH 默认端口、禁用密码登录并启用 Fail2ban,此后异常登录日志量大幅下降。

案例 2:.env 配置文件暴露导致数据库凭证泄露

  • 故障现象:某基于 PHP 框架开发的业务系统,其数据库表被恶意清空并被写入勒索信息。
  • 根因与处置:排查 Nginx 访问日志发现,部署代码时误将包含数据库账号密码的 .env 环境配置文件放置在 Web 公开根目录下,被自动化漏洞扫描器直接下载读取;同时数据库的 3306 端口直接暴露在公网上,导致攻击者直接连入库中执行破坏。处置方案为:立即收回数据库的公网监听(修改为仅监听内网或 127.0.0.1),轮换全部凭证,并在 Nginx 层配置针对 .env、.sql、.git 等敏感后缀的全局 444 拦截规则。

04 生产排障速查表(主机安全加固基线)

防御层级核心配置项拦截定性防护目的与收益SSH 通道层Port 28475 与 PasswordAuthentication no规避默认端口与禁用密码验证防扫描/防爆破。过滤绝大多数针对 22 端口的自动化字典爆破脚本四层接入层Nginx stream 模块 SNI 路由分发内部服务仅监听 127.0.0.1收敛暴露面。公网仅开放 443 端口,隐藏内部组件与代理端口七层应用层server_name _; return 444;拒绝未绑定域名的纯 IP 访问防网络空间测绘。防止 Shodan/Fofa 等测绘引擎通过 IP 直接抓取业务证书与特征七层应用层location ~* .(env|bak|sql)...匹配并直接断开敏感后缀请求防敏感信息泄露。防止因目录权限配置不当导致配置文件或源码备份被下载内核防火墙层Fail2ban 监控认证与拦截日志触发阈值后下发 iptables DROP防高频探测/CC。在内核网络栈直接丢弃恶意 IP 报文,保护系统资源

05 面试应答参考话术

面试官问:“如果给你一台刚开通、处于初始状态的公网云服务器部署业务,上线前你会做哪些安全加固操作?”

可以这样回答:带有公网 IP 的服务器一旦上线,就会面临全网自动化的端口扫描和字典爆破。如果不做前置加固,很容易出现认证日志占满磁盘或因弱口令被植入挖矿木马。 我的加固核心思路是“纵深防御、收敛暴露面”,主要落实为以下四道防线:第一:加固 SSH 访问通道。

首先修改默认的 22 端口为高位端口,规避全网针对默认端口的批量盲扫;同时在 sshd_config 中将 PasswordAuthentication 设为 no,强制仅使用 SSH 密钥对登录,从机制上杜绝密码字典暴力破解。第二:利用四层网关收敛公网端口。

遵循最小暴露原则,不允许数据库、缓存或内部管理服务直接监听在 0.0.0.0 公网上,而是让它们全部只监听本地回环地址 127.0.0.1。公网防火墙仅放行 443 端口,通过 Nginx 的 stream 四层模块利用 TLS SNI 预读域名,在服务器内部将流量动态转发给对应的本地端口,大幅缩减外部可见的端口暴露面。第三:配置 Nginx 七层应用过滤。

针对直接使用 IP 地址发起探测的扫描器,在 Nginx 的 default_server 块中配置 return 444 直接断开连接,防止被网络空间测绘引擎抓取特征。同时利用正则匹配屏蔽对 .env、.sql、.bak、.git 等敏感后缀的访问,并采用路由白名单机制,仅放行已知的业务接口路径。第四:部署 Fail2ban 动态封禁兜底。

针对持续探测高端口或高频触发 Nginx 拦截规则的恶意 IP,使用 Fail2ban 实时分析日志。一旦单 IP 失败次数达到阈值,自动联动系统底层的 iptables 在网络层将其封禁,阻断其后续的探测请求。通过这套组合策略,可以将日常数万次的无效扫描和爆破请求收敛至个位数,保障线上主机的基线安全。

06 面试高频追问与应答预案

追问 1:既然禁用了密码登录且只允许密钥登录,如果本地电脑损坏导致私钥丢失,无法连接服务器怎么办?

  • 回答要点:
  • 带外管理与 VNC 应急登录:云服务器可通过云厂商控制台提供的 VNC 远程终端(或物理机的 IPMI/iDRAC 带外管理口)直接进入系统本地控制台。该方式相当于直接接上显示器和键盘登录操作系统,不经过网络层的 SSH 协议,因此受本地 root 账号密码控制(不受 sshd_config 中的 PasswordAuthentication no 限制),登录后即可重新替换写入新的 SSH 公钥。
  • 日常预防規範:生产环境中通常会配置统一的堡垒机(跳板机)管理主机密钥,或将管理公钥妥善加密备份在密码管理系统中,避免单点私钥丢失风险。

追问 2:Nginx 配置中的 return 444 状态码与常见的 403 Forbidden 有什么区别?为什么安全拦截推荐使用 444?

  • 回答要点:
  • 403 Forbidden 是标准的 HTTP 状态码。Nginx 返回 403 时,仍需构建完整的 HTTP 响应头与错误页面 HTML 内容并发送给客户端,这依然会消耗服务器的网络带宽与连接资源,且向扫描器暴露了服务端正在运行 Nginx(及具体版本号)的指纹信息。
  • 444 是 Nginx 内部定义的非标准状态码,代表 无响应直接关闭连接(Connection Closed Without Response)。当匹配到该规则时,Nginx 不会向客户端返回任何字节的 HTTP 报文,而是直接调用系统底层发送 TCP RST 或 FIN 包断开连接。这种方式能以最低的系统开销打断恶意扫描器的探测连接。

07 主机安全基线与暴露面自动巡检脚本 (check_sec_baseline.sh)

该脚本用于快速检测当前服务器的 SSH 爆破历史统计、SSH 认证配置合规性、公网监听端口暴露面以及 Fail2ban 防护状态。

#!/usr/bin/env bash
# ==============================================================================
# 脚本名称: check_sec_baseline.sh
# 功能描述: 自动检测公网服务器安全基线配置与端口暴露面 (纯只读)
# 使用方式: 在宿主机以 root 权限执行
# ==============================================================================

set -o pipefail

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
PLAIN='\033[0m'

echo -e "${BLUE}================================================================${PLAIN}"
echo -e "${BLUE}           云主机底层安全基线与公网暴露面自动化巡检             ${PLAIN}"
echo -e "${BLUE}================================================================${PLAIN}"

# 1. 统计近期遭受暴力破解尝试的次数
echo -e "\n${YELLOW}[1/4] 正在统计近期登录失败记录 (/var/log/btmp)...${PLAIN}"
if command -v lastb &>/dev/null; then
    # 修复了这里的管道符乱码
    FAIL_COUNT=$(lastb 2>/dev/null | grep -v "^$" | grep -v "^btmp begins" | wc -l)
    if [ "$FAIL_COUNT" -gt 1000 ]; then
        echo -e "  -> ${RED}[ALERT] 检测到累计 ${FAIL_COUNT} 次登录失败记录,主机正面临高频暴力破解探测!${PLAIN}"
    else
        echo -e "  -> ${GREEN}[OK] 近期登录失败记录为 ${FAIL_COUNT} 次,探测频率处于可控范围。${PLAIN}"
    fi
else
    echo -e "  -> ${YELLOW}[INFO] 未找到 lastb 命令,跳过登录失败次数统计。${PLAIN}"
fi

# 2. 检查 SSH 服务安全基线配置
echo -e "\n${YELLOW}[2/4] 检查 SSH 服务安全基线配置 (/etc/ssh/sshd_config)...${PLAIN}"
SSH_PORT=$(grep -iE "^[[:space:]]*Port[[:space:]]+" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}' | head -n 1)
PWD_AUTH=$(grep -iE "^[[:space:]]*PasswordAuthentication[[:space:]]+" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}' | tail -n 1)

# 修复了这里的逻辑或乱码
if [ -z "$SSH_PORT" ] || [ "$SSH_PORT" == "22" ]; then
    echo -e "  -> 端口配置: ${RED}22 (默认高危端口)${PLAIN}"
    echo -e "  -> ${YELLOW}[建议] 默认 22 端口极易遭受全网自动化扫描,建议修改为高位端口。${PLAIN}"
else
    echo -e "  -> 端口配置: ${GREEN}${SSH_PORT} (非默认端口,合规)${PLAIN}"
fi

# 修复了这里的逻辑或乱码
if [[ "$PWD_AUTH" == "no" || "$PWD_AUTH" == "No" ]]; then
    echo -e "  -> 密码认证: ${GREEN}已禁用 (仅允许密钥登录,合规)${PLAIN}"
else
    echo -e "  -> 密码认证: ${RED}已开启 (存在字典暴力破解风险)${PLAIN}"
fi

# 3. 检查公网监听端口暴露面
echo -e "\n${YELLOW}[3/4] 扫描全网监听 (0.0.0.0 / :::) 的服务端口暴露面...${PLAIN}"
PUBLIC_PORTS=$(ss -tulnp | awk 'NR>1 && ($5 ~ /^0\.0\.0\.0:/ || $5 ~ /^\[::\]:/ || $5 ~ /^\*:/) {print $1, $5, $7}' | sort -u)

if [ -n "$PUBLIC_PORTS" ]; then
    echo -e "  -> ${YELLOW}[WARN] 检测到以下端口处于全网监听状态,请核实是否需要对外暴露:${PLAIN}"
    echo "$PUBLIC_PORTS" | while read -r proto addr_port proc; do
        p_name=$(echo "$proc" | grep -o '("[^"]*"' | head -n 1 | tr -d '"(')
        [ -z "$p_name" ] && p_name="未知/内核进程"
        # 修复了这里的输出字符串管道符乱码
        echo -e "     协议: ${proto} | 监听地址: ${YELLOW}${addr_port}${PLAIN} | 进程: ${RED}${p_name}${PLAIN}"
    done
    echo -e "  -> ${GREEN}[收敛建议] 内部数据库、缓存及后端应用建议修改为监听 127.0.0.1,统一通过网关对外暴露。${PLAIN}"
else
    echo -e "  -> ${GREEN}[OK] 未检测到监听在 0.0.0.0 的服务端口。${PLAIN}"
fi

# 4. 检查动态封禁机制 (Fail2ban)
echo -e "\n${YELLOW}[4/4] 检查动态防爆破组件状态 (Fail2ban)...${PLAIN}"
if command -v fail2ban-client &>/dev/null; then
    F2B_STATUS=$(systemctl is-active fail2ban 2>/dev/null)
    if [ "$F2B_STATUS" == "active" ]; then
        JAILS=$(fail2ban-client status 2>/dev/null | grep "Jail list:" | sed 's/.*Jail list://' | tr -d '\t')
        echo -e "  -> 运行状态: ${GREEN}运行中 (active)${PLAIN}"
        echo -e "  -> 启用策略:${GREEN}${JAILS}${PLAIN}"
    else
        echo -e "  -> 运行状态: ${RED}未运行 ($F2B_STATUS)${PLAIN}"
    fi
else
    echo -e "  -> 组件状态: ${RED}未安装 Fail2ban${PLAIN}"
    echo -e "  -> ${YELLOW}[建议] 建议部署 Fail2ban 联动防火墙实现对高频恶意探测 IP 的自动封禁。${PLAIN}"
fi

echo -e "\n${BLUE}================================================================${PLAIN}"