Debian服务器稳定运行多年,但安全事件往往在事后复盘时才发现日志早已记录下蛛丝马迹。问题在于,单台服务器的日志视角孤立,攻击者一旦清理了本地日志,入侵痕迹便彻底消失。集中审计解决的正是这个痛点——把所有日志实时转发到独立审计节点,让攻击者无法同时抹除两边的记录。更关键的是,异常登录发现必须建立在集中日志的基础上,因为只有把多台机器的认证日志汇聚到一起,才能识别出横向移动、撞库爆破这类跨主机的攻击模式。

日志集中架构的核心选型

在Debian环境下搭建日志集中系统,底层协议几乎没得选,syslog over TLS是最成熟可靠的方式。rsyslog是Debian默认安装的日志服务,它原生支持RELP和TCP+TLS两种可靠传输。很多人图省事用UDP转发,但UDP丢包且无加密,日志在半路被截获或丢失的风险太高。生产环境务必启用TCP+TLS,证书可以用内部CA签发,rsyslog的gtls模块对证书格式要求宽松,PEM格式直接可用。

客户端配置的关键在于队列缓冲机制。当审计节点宕机或网络中断时,本地日志不能丢失。在/etc/rsyslog.d/remote.conf中设置磁盘辅助队列,内存队列设为5000条消息,超出部分写入磁盘,这样即使审计节点离线数小时,恢复后日志仍能完整补齐。队列文件存放在/var/spool/rsyslog目录,务必确保该目录所在分区有足够空间,生产环境中因磁盘满导致rsyslog卡死的案例并不少见。

rsyslog客户端配置详解

Debian 12中rsyslog的imjournal模块默认从systemd journal拉取日志,但集中转发时建议直接读取/var/log下的传统文本文件,因为journal的二进制格式在转发时需要额外解析,性能开销大且容易丢失结构化字段。修改/etc/rsyslog.conf,确保imuxsock和imklog模块启用,然后创建独立的转发配置文件。

# /etc/rsyslog.d/00-forward.conf
module(load="builtin:omfwd")
module(load="builtin:omfile")

# 全局工作目录
global(workDirectory="/var/spool/rsyslog")

# 磁盘辅助队列模板
template(name="ForwardFormat" type="string" string="<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg%")

# 主队列配置
main_queue(
    queue.filename="main_disk_queue"
    queue.maxdiskspace="2g"
    queue.maxfilesize="100m"
    queue.type="LinkedList"
    queue.saveonshutdown="on"
    queue.dequeueBatchSize="256"
)

# 转发所有日志到审计节点
*.* action(
    type="omfwd"
    target="192.168.10.50"
    port="6514"
    protocol="tcp"
    template="ForwardFormat"
    StreamDriver="gtls"
    StreamDriverMode="1"
    StreamDriverAuthMode="x509/name"
    StreamDriverPermittedPeers="audit-server.internal"
    queue.type="LinkedList"
    queue.filename="forward_queue"
    queue.maxdiskspace="1g"
    queue.saveonshutdown="on"
    action.resumeRetryCount="-1"
    action.resumeInterval="10"
)

上述配置中action.resumeRetryCount设为-1表示无限重试,action.resumeInterval控制重连间隔。StreamDriverPermittedPeers要填写审计节点证书的CN字段,这是防止中间人攻击的关键校验。配置完成后执行systemctl restart rsyslog,然后用logger "test forward"生成测试日志,在审计节点用tcpdump抓包确认TLS握手成功。

审计节点接收端部署

审计节点同样运行Debian,rsyslog作为接收端需要监听6514端口并验证客户端证书。这里有个容易踩的坑:rsyslog的imtcp模块默认不启用TLS,必须显式加载gtls驱动并指定证书路径。证书部署建议使用Let's Encrypt或内部PKI签发,客户端和服务器端证书的CA必须一致。

# /etc/rsyslog.d/01-receiver.conf
module(load="imtcp")
module(load="gtls")

# TLS全局配置
global(
    DefaultNetstreamDriver="gtls"
    DefaultNetstreamDriverCAFile="/etc/rsyslog.d/certs/ca.pem"
    DefaultNetstreamDriverCertFile="/etc/rsyslog.d/certs/server-cert.pem"
    DefaultNetstreamDriverKeyFile="/etc/rsyslog.d/certs/server-key.pem"
)

# 监听器模板,按客户端IP分目录存储
template(name="PerHost" type="string" string="/var/log/remote/%FROMHOST-IP%/%$YEAR%-%$MONTH%/%$DAY%-syslog.log")

input(
    type="imtcp"
    port="6514"
    StreamDriver.Name="gtls"
    StreamDriver.Mode="1"
    StreamDriver.AuthMode="x509/name"
    ruleset="remote_logs"
)

ruleset(name="remote_logs") {
    action(type="omfile" dynaFile="PerHost" dirCreateMode="0750" fileCreateMode="0640")
}

接收端按客户端IP分目录存储是实践出来的最佳方案。当管理几十台服务器时,所有日志混在一个文件里根本没法审计。PerHost模板自动创建/var/log/remote/192.168.x.x/2025-01/15-syslog.log这样的层级结构,配合logrotate按天轮转,审计时能快速定位到具体机器和时间段。

日志标准化与结构化处理

原始syslog格式可读性差,集中后必须做结构化解析。rsyslog的mmjsonparse模块能将日志转成JSON,但更轻量的方案是用mmnormalize模块配合liblognorm规则库。Debian仓库中的liblognorm-utils包提供了大量预置解析规则,覆盖sshd、sudo、cron等常见服务的日志格式。解析后的结构化日志存入文件时,每条记录都包含统一的timestamp、hostname、program、pid、message字段,后续用jq或grep分析效率提升数倍。

对于sshd认证日志,解析后能直接提取出auth_result、user、source_ip、port等关键字段。这些结构化数据是异常登录检测的基础。如果条件允许,在审计节点上再部署一个Loki或Elasticsearch,把结构化日志注入时序数据库,配合Grafana做可视化仪表盘,安全态势一目了然。但即使不引入额外组件,仅靠rsyslog的解析能力和shell脚本,也完全能构建出有效的异常检测体系。

异常登录检测的核心指标

日志集中后,异常登录发现的核心思路是建立基线然后检测偏离。正常登录行为有规律可循:运维人员从固定IP段登录,使用密钥认证,登录时间集中在工作时间。异常行为则表现为:凌晨时段的登录、从未知IP的登录、密码认证失败后的成功登录、同一用户短时间内从不同IP登录等。这些规则用shell脚本配合awk就能实现,不需要机器学习也不依赖复杂工具。

首先关注sshd的Accepted日志行。一条正常的公钥认证成功日志长这样:Accepted publickey for root from 10.0.1.5 port 58342 ssh2。解析后提取用户、源IP、时间、认证方式四个维度。如果某个用户历史上一直用publickey认证,突然出现password认证成功,这就是高危信号——可能是私钥泄露后攻击者改了认证方式,也可能是运维违规开启了密码登录。

基于历史基线的检测脚本

下面这个脚本实现了最实用的基线对比检测。它维护一个用户-IP-认证方式的基线文件,每次运行时将新日志与基线对比,发现偏离立即告警。脚本设计成无状态,每次从日志文件中提取最近一小时的记录进行分析,适合cron定时执行。

#!/bin/bash
# 异常登录检测脚本 - 基于历史基线对比

BASELINE_FILE="/var/lib/login_baseline.db"
LOG_DIR="/var/log/remote"
ALERT_LOG="/var/log/login_alerts.log"
CHECK_WINDOW="1 hour ago"

# 从集中日志中提取最近一小时的sshd成功登录
extract_logins() {
    find "$LOG_DIR" -type f -name "*.log" -newermt "$CHECK_WINDOW" \
        -exec grep "Accepted " {} \; | while read -r line; do
        # 解析字段
        timestamp=$(echo "$line" | awk '{print $1, $2, $3}')
        hostname=$(echo "$line" | awk -F'/' '{print $5}')
        auth_method=$(echo "$line" | grep -oP 'Accepted \K\w+')
        user=$(echo "$line" | grep -oP 'for \K\w+')
        src_ip=$(echo "$line" | grep -oP 'from \K[\d.]+')
        
        echo "$hostname|$user|$src_ip|$auth_method|$timestamp"
    done
}

# 加载基线
declare -A baseline
if [ -f "$BASELINE_FILE" ]; then
    while IFS='|' read -r host user ip method; do
        key="${host}|${user}"
        baseline["$key"]="${ip}|${method}"
    done < "$BASELINE_FILE"
fi

# 检测异常
extract_logins | while IFS='|' read -r host user ip method timestamp; do
    key="${host}|${user}"
    
    if [ -n "${baseline[$key]}" ]; then
        old_ip=$(echo "${baseline[$key]}" | cut -d'|' -f1)
        old_method=$(echo "${baseline[$key]}" | cut -d'|' -f2)
        
        # 规则1: 认证方式突变
        if [ "$method" != "$old_method" ] && [ "$method" = "password" ]; then
            echo "ALERT: [$timestamp] $user@$host 认证方式异常: $old_method -> $method (IP: $ip)" | tee -a "$ALERT_LOG"
        fi
        
        # 规则2: 新IP首次登录
        if [ "$ip" != "$old_ip" ]; then
            # 检查是否为已知IP段
            if ! echo "$ip" | grep -qE '^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)'; then
                echo "ALERT: [$timestamp] $user@$host 从未知外部IP登录: $ip (历史: $old_ip)" | tee -a "$ALERT_LOG"
            fi
        fi
    else
        # 规则3: 全新的用户-主机组合
        echo "INFO: [$timestamp] 首次观察到 $user@$host 登录 (IP: $ip, 方式: $method)" | tee -a "$ALERT_LOG"
    fi
    
    # 更新基线
    echo "$host|$user|$ip|$method" >> "${BASELINE_FILE}.tmp"
done

# 去重更新基线
sort -u "${BASELINE_FILE}.tmp" > "$BASELINE_FILE" 2>/dev/null
rm -f "${BASELINE_FILE}.tmp"

这个脚本的核心价值在于基线对比逻辑。它不依赖固定规则,而是自适应学习每个用户的历史行为。新服务器上线第一周,脚本处于学习期,会记录所有INFO级别的"首次观察"日志。运行一个月后基线稳定,此时任何偏离都会触发ALERT。生产环境中建议将告警接入企业微信或邮件通知,实现实时响应。

暴力破解与撞库的实时检测

Failed password日志的价值常被低估。单次登录失败可能是手误,但同一源IP在一分钟内对多个用户名尝试登录,或者同一用户名从多个IP尝试登录,几乎可以断定是暴力破解。这类检测需要滑动窗口聚合,用awk的关联数组就能高效实现。

检测逻辑分两个维度:源IP维度,统计60秒内失败次数超过阈值的IP,直接加入iptables黑名单;用户维度,统计同一用户从不同IP的失败尝试,这往往是撞库攻击的特征——攻击者用泄露的密码库尝试登录,用户名分散但来源IP集中。两个维度的检测脚本可以合并,每分钟执行一次,输出结构化的告警信息。

sudo与特权操作审计

异常登录只是入口,真正的威胁在登录后的操作。Debian的sudo日志默认记录在/var/log/auth.log,集中审计时必须确保sudo的log_input和log_output配置已启用,这样能记录下用户执行命令时的终端输入输出。在/etc/sudoers中添加Defaults log_input, log_output, iolog_dir=/var/log/sudo-io,所有会话录像会保存在指定目录。审计节点收集这些日志后,重点关注sudo su -、sudo -i这类提权操作,以及curl/wget下载外部脚本并管道执行的可疑行为。

一个实用的检测规则是监控sudo执行后紧跟的网络连接。如果某个用户sudo执行了curl或wget,紧接着系统建立了到陌生IP的出站连接,这极可能是下载并执行恶意载荷。通过关联auth.log和syslog中的网络日志,能还原出完整的攻击链。这类关联分析用rsyslog的omprog模块调用外部脚本即可实现,无需引入SIEM系统。

日志保留与合规性

集中审计的日志保留策略直接影响取证能力。等保2.0要求日志保存不少于6个月,金融行业通常要求12个月以上。存储成本是现实问题,建议采用分层存储:最近30天的热数据存放在审计节点的SSD上,便于快速检索;30天到6个月的数据压缩后转存到NAS或对象存储;超过6个月的冷数据可以归档到磁带或低成本云存储。rsyslog的omfile支持动态路径和压缩管道,配合logrotate的compress选项,能自动完成热数据到压缩归档的流转。

日志完整性校验同样不可忽视。攻击者如果能入侵审计节点,第一件事就是清理相关日志。用sha256sum对每日日志文件生成校验和,单独存储在校验服务器上,配合只写一次的存储介质,能有效防止日志篡改。即使审计节点完全沦陷,历史日志的完整性仍可验证。

日常运维与故障排查

集中日志系统本身也需要监控。rsyslog的impstats模块能输出内部统计信息,包括队列长度、丢弃消息数、转发速率等。建议每5分钟采集一次这些指标,队列持续增长说明审计节点处理能力不足或网络带宽瓶颈,消息丢弃则是严重故障的前兆。Debian上用monit或systemd-timers配合简单的采集脚本就能实现自监控。

常见故障场景及排查路径:客户端日志不转发,先检查rsyslog服务状态和/var/log/syslog中的错误信息,重点看证书是否过期、DNS解析是否正常;审计节点收不到日志,用openssl s_client -connect audit-server:6514测试TLS握手,确认防火墙规则放行6514端口;日志时间错乱,检查所有服务器的NTP同步状态,集中审计对时间一致性要求极高,时间不同步会导致事件关联完全失效。

这套方案在Debian 11和12上经过长期验证,从单机到数百台服务器的规模都能平滑扩展。核心思想是用最少的组件做最有效的事——rsyslog负责传输和解析,shell脚本负责检测,基线对比负责降低误报。不依赖重量级平台,不引入复杂依赖,维护成本极低,安全收益却非常实在。当攻击者还在为突破边界沾沾自喜时,集中审计系统已经记录下他的每一步操作,这就是日志集中审计的核心价值。