当前位置: 首页 记笔 持久化监控ssh登录日志shell脚本

创建脚本脚本

mkdir -p /home/sa/ssh && cd $_ && nano 1.sh

粘贴下面代码

#!/usr/bin/env bash

set -u

HOST_NAME="$(hostname)"

# /**
#  * 推送 api 接口,留空不启用
#  */
PUSH_API_URL=""
AUTO_BLOCK_ENABLED=0
FIREWALL_CONFLICT=0
declare -A FAILURE_TIMESTAMPS=()
declare -A BLOCKED_NETWORKS=()

# /**
#  * 输出错误信息并退出
#  */
fail() {
    printf '错误:%s\n' "$1" >&2
    exit 1
}

# /**
#  * 检测当前防火墙类型
#  */
detect_firewall() {
    local ufw_active=0 firewalld_active=0

    if command -v ufw >/dev/null 2>&1 && LC_ALL=C ufw status 2>/dev/null | grep -q '^Status: active'; then
        ufw_active=1
    fi
    if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld 2>/dev/null; then
        firewalld_active=1
    fi

    if (( ufw_active == 1 && firewalld_active == 1 )); then
        printf '防火墙检测:UFW 与 firewalld 同时启用,存在管理冲突。\n'
        FIREWALL_CONFLICT=1
        return
    fi
    if (( ufw_active == 1 )); then
        printf '防火墙检测:UFW,已启用自动封禁。\n'
        AUTO_BLOCK_ENABLED=1
        return
    fi

    printf '防火墙检测:UFW 未启用,当前版本不启用自动封禁。\n'
}

# /**
#  * 推送 SSH 登录告警
#  */
send_notification() {
    local message="$1"
    local title="$2"

    if [[ -z "$PUSH_API_URL" ]]; then
        printf '跳过推送通知发送\n'
        return
    fi

    if curl --silent --show-error --fail \
        --max-time 30 \
        -X POST "$PUSH_API_URL" \
        --data-urlencode "msg=$message" \
        --data-urlencode "title=$title" \
        --output /dev/null; then
        printf '推送成功\n'
    else
        printf '推送失败\n' >&2
    fi
}

# /**
#  * 将 IPv4 地址转换为 /24 网段
#  */
get_ipv4_network() {
    local ip="$1"
    local a b c d

    IFS='.' read -r a b c d <<< "$ip"
    [[ "$a" =~ ^[0-9]+$ && "$b" =~ ^[0-9]+$ && "$c" =~ ^[0-9]+$ && "$d" =~ ^[0-9]+$ ]] || return 1
    (( 10#$a <= 255 && 10#$b <= 255 && 10#$c <= 255 && 10#$d <= 255 )) || return 1
    (( 10#$a != 127 )) || return 1
    printf '%d.%d.%d.0/24\n' "$((10#$a))" "$((10#$b))" "$((10#$c))"
}

# /**
#  * 永久封禁指定的 UFW 网段
#  */
block_network() {
    local network="$1"
    local display_network="${network%.0/24}.xxx"

    [[ -z "${BLOCKED_NETWORKS[$network]-}" ]] || return
    if LC_ALL=C ufw status 2>/dev/null | grep -Fq "$network"; then
        BLOCKED_NETWORKS["$network"]=1
        return
    fi

    if ufw insert 1 deny from "$network" >/dev/null; then
        BLOCKED_NETWORKS["$network"]=1
        unset 'FAILURE_TIMESTAMPS[$network]'
        printf '拉黑 %s\n' "$display_network"
        send_notification "$HOST_NAME 拉黑 $display_network" "SSH 自动拉黑"
    else
        printf '拉黑失败 %s\n' "$display_network" >&2
    fi
}

# /**
#  * 记录 IPv4 网段的登录失败次数
#  */
record_failed_ip() {
    local ip="$1"
    local network now cutoff timestamp retained="" count=0

    (( AUTO_BLOCK_ENABLED == 1 )) || return
    network="$(get_ipv4_network "$ip")" || return
    now="$(date '+%s')"
    cutoff=$((now - 600))

    for timestamp in ${FAILURE_TIMESTAMPS[$network]-}; do
        if (( timestamp >= cutoff )); then
            retained+="$timestamp "
            ((count += 1))
        fi
    done
    retained+="$now"
    ((count += 1))
    FAILURE_TIMESTAMPS["$network"]="$retained"

    if (( count >= 3 )); then
        block_network "$network"
    fi
}

# /**
#  * 持续读取新增的 SSH 日志
#  */
stream_logs() {
    if [[ -r /var/log/auth.log ]]; then
        tail -n 0 -F /var/log/auth.log
        return
    fi

    command -v journalctl >/dev/null 2>&1 || fail "无法读取 /var/log/auth.log,且系统没有 journalctl"
    journalctl --no-pager -n 0 -f _COMM=sshd
}

# /**
#  * 输出单条 SSH 登录告警
#  */
print_alert() {
    local line="$1"
    local event_time user ip message notification_title is_failure=0

    event_time="$(LC_ALL=C date -d "${line:0:15} $(date '+%Y')" '+%F %T' 2>/dev/null || printf '%s' "${line:0:15}")"

    if [[ "$line" =~ Accepted[[:space:]]+[^[:space:]]+[[:space:]]+for[[:space:]]+([^[:space:]]+)[[:space:]]+from[[:space:]]+([^[:space:]]+) ]]; then
        user="${BASH_REMATCH[1]}"
        ip="${BASH_REMATCH[2]}"
        message="$event_time $HOST_NAME $user 登录成功 $ip"
        notification_title="SSH 登录成功"
    elif [[ "$line" =~ Failed[[:space:]]+[^[:space:]]+[[:space:]]+for[[:space:]]+invalid[[:space:]]+user[[:space:]]+([^[:space:]]+)[[:space:]]+from[[:space:]]+([^[:space:]]+) ]]; then
        user="${BASH_REMATCH[1]}"
        ip="${BASH_REMATCH[2]}"
        message="$event_time $HOST_NAME $user 登录失败 $ip"
        notification_title="SSH 登录失败"
        is_failure=1
    elif [[ "$line" =~ Failed[[:space:]]+[^[:space:]]+[[:space:]]+for[[:space:]]+([^[:space:]]+)[[:space:]]+from[[:space:]]+([^[:space:]]+) ]]; then
        user="${BASH_REMATCH[1]}"
        ip="${BASH_REMATCH[2]}"
        message="$event_time $HOST_NAME $user 登录失败 $ip"
        notification_title="SSH 登录失败"
        is_failure=1
    else
        return
    fi

    printf '%s\n' "$message"
    send_notification "$message" "$notification_title"
    if (( is_failure == 1 )); then
        record_failed_ip "$ip"
    fi
}

# /**
#  * 执行 SSH 登录实时监听
#  */
main() {
    local line startup_message

    detect_firewall
    if [[ -n "$PUSH_API_URL" ]]; then
        command -v curl >/dev/null 2>&1 || fail "系统没有 curl,无法发送推送通知"
    fi
    startup_message="$HOST_NAME SSH 登录监控已启动"
    if (( FIREWALL_CONFLICT == 1 )); then
        startup_message+=";防火墙冲突,已停用自动封禁"
    elif (( AUTO_BLOCK_ENABLED == 1 )); then
        startup_message+=";防火墙已启用自动封禁"
    else
        startup_message+=";防火墙未启用,已停用自动封禁"
    fi
    send_notification "$startup_message" "SSH 监控启动"
    printf '正在实时监听 SSH 登录事件,按 Ctrl+C 停止。\n'
    while IFS= read -r line; do
        [[ "$line" == *"sshd["* ]] || continue
        print_alert "$line"
    done < <(stream_logs)
}

main

尝试启动

sed -i 's/\r$//' ./1.sh && chmod +x ./1.sh && ./1.sh

检查控制输出,没问题创建systemctl自启

cd /etc/systemd/system && nano my_sa_ssh.service

输入以下粘贴以下内容到ssh

[Unit]
Description=my_sa_ssh
After=network.target

[Service]
ExecStart=/usr/bin/bash /home/sa/ssh/1.sh
StandardOutput=append:/home/sa/ssh/Output.log
StandardError=append:/home/sa/ssh/Error.log
Restart=always

[Install]
WantedBy=multi-user.target

刷新systemctl配置并尝试启动,检查/home/sa/ssh/目录下的输出日志

systemctl daemon-reload
systemctl start my_sa_ssh.service

没问题,写入自启

systemctl enable my_sa_ssh.service

标签:无

持久化监控ssh登录日志shell脚本》 © 2026 by 吃蛋的小子 依据 CC BY-SA 4.0 许可协议授权

你的评论