持久化监控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 许可协议授权