引言
ssh(secure shell)是 linux 系统管理员远程管理服务器最常用的协议。它安全、稳定、功能强大,但也正因为如此,ssh 服务往往成为黑客攻击的首要目标 —— 尤其是暴力破解(brute force attack)。攻击者通过自动化脚本不断尝试用户名和密码组合,试图获得系统访问权限。一旦成功,后果不堪设想:数据泄露、系统被控、沦为肉鸡、甚至被用于发动进一步攻击。
在本文中,我们将深入探讨如何从多个维度有效防御 ssh 暴力破解攻击。不仅会介绍传统配置方法,还会提供 java 实现的实时监控与自动封禁工具,并结合可视化图表帮助你理解攻击模式和防御机制。
什么是 ssh 暴力破解?
ssh 暴力破解是指攻击者利用自动化程序,反复尝试不同的用户名和密码组合,以期“撞开”系统的登录大门。这类攻击通常具有以下特征:
- 高频次连接请求(每秒数次到数百次)
- 使用常见用户名(如
root,admin,test) - 使用弱密码字典(如
123456,password,qwerty) - 来源 ip 分布广泛或集中在某些恶意 ip 段
- 攻击持续时间长,可能持续数天甚至数周
小知识:根据 shodan.io 的统计,全球每天有超过 200 万次 ssh 登录尝试来自恶意扫描器。
ssh 攻击趋势分析(mermaid 图表)
我们先来看一组模拟的 ssh 攻击频率随时间变化图,这有助于理解为何我们需要主动防御:

从上图可见,攻击并非均匀分布,而是集中在工作时间段,且周三出现“持续攻击”,说明攻击者也在“上班”。我们需要的是动态、智能、多层次的防御策略。
方法一:修改默认 ssh 端口
这是最基础但非常有效的一步。默认的 ssh 端口是 22,绝大多数自动化脚本都只会扫这个端口。改个端口,能过滤掉 90% 的低级扫描。
操作步骤:
- 编辑 ssh 配置文件:
sudo nano /etc/ssh/sshd_config
- 找到
port 22,修改为其他端口(比如 2222):
port 2222
- 重启 ssh 服务:
sudo systemctl restart sshd
- 别忘了在防火墙放行新端口:
sudo ufw allow 2222/tcp
注意:修改端口前请确保你有其他方式访问服务器(如控制台),避免把自己锁在外面!
方法二:禁用 root 登录
root 是 linux 的超级用户,也是攻击者的首要目标。禁用其直接登录可大幅提升安全性。
操作步骤:
编辑 /etc/ssh/sshd_config:
permitrootlogin no
然后重启 ssh:
sudo systemctl restart sshd
之后,你需要先用普通用户登录,再通过 su - 或 sudo 提权。
方法三:启用密钥认证,禁用密码登录
密码容易被暴力破解,而 ssh 密钥几乎不可能被暴力攻破(前提是私钥保管好)。
操作步骤:
- 在本地生成密钥对(如果还没有):
ssh-keygen -t rsa -b 4096
- 将公钥上传到服务器:
ssh-copy-id -p 2222 user@your-server-ip
- 修改
/etc/ssh/sshd_config:
passwordauthentication no pubkeyauthentication yes
- 重启 ssh:
sudo systemctl restart sshd
现在只能通过私钥登录,暴力破解彻底失效!
方法四:使用 fail2ban 自动封禁恶意 ip
fail2ban 是一个入侵防御软件,它能监控日志文件,发现多次失败登录后自动将 ip 加入防火墙黑名单。
安装与配置:
sudo apt update sudo apt install fail2ban
复制默认配置:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
编辑 jail.local,设置 ssh 监控:
[sshd] enabled = true port = 2222 filter = sshd logpath = /var/log/auth.log maxretry = 3 bantime = 86400 findtime = 600
解释:
maxretry = 3:10分钟内失败3次就封bantime = 86400:封禁24小时findtime = 600:监控最近10分钟内的日志
启动服务:
sudo systemctl enable fail2ban sudo systemctl start fail2ban
查看封禁状态:
sudo fail2ban-client status sshd
方法五:配置防火墙限制访问来源
如果你的团队只有固定几个 ip 需要访问 ssh,那就只允许这些 ip 连接。
使用 ufw(ubuntu):
sudo ufw allow from 192.168.1.100 to any port 2222 sudo ufw deny 2222 sudo ufw enable
使用 firewalld(centos/rhel):
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.100" port protocol="tcp" port="2222" accept' sudo firewall-cmd --permanent --remove-service=ssh sudo firewall-cmd --reload
这样,非白名单 ip 根本连不上 ssh 端口。
方法六:部署自研 java 监控工具(实战代码!)
虽然 fail2ban 很强大,但有时我们需要更灵活的控制 —— 比如对接企业内部告警系统、发送邮件、记录数据库、或对接云平台 api。
下面是一个用 java 编写的简易 ssh 暴力破解监控器,它读取 /var/log/auth.log,检测失败登录,自动调用 iptables 封禁 ip。
项目结构简洁,适合二次开发集成到现有运维平台。
项目依赖(maven)
<dependencies>
<dependency>
<groupid>ch.qos.logback</groupid>
<artifactid>logback-classic</artifactid>
<version>1.4.11</version>
</dependency>
<dependency>
<groupid>org.apache.commons</groupid>
<artifactid>commons-lang3</artifactid>
<version>3.13.0</version>
</dependency>
</dependencies>核心代码:sshmonitor.java
import org.apache.commons.lang3.stringutils;
import org.slf4j.logger;
import org.slf4j.loggerfactory;
import java.io.bufferedreader;
import java.io.file;
import java.io.filereader;
import java.io.ioexception;
import java.time.localdatetime;
import java.time.format.datetimeformatter;
import java.util.*;
import java.util.concurrent.executors;
import java.util.concurrent.scheduledexecutorservice;
import java.util.concurrent.timeunit;
import java.util.regex.matcher;
import java.util.regex.pattern;
public class sshmonitor {
private static final logger logger = loggerfactory.getlogger(sshmonitor.class);
// 日志文件路径(ubuntu/debian)
private static final string auth_log_path = "/var/log/auth.log";
// 匹配失败登录的正则表达式
private static final string failed_login_pattern =
".*failed password for (?:invalid user )?(\\s+) from (\\d+\\.\\d+\\.\\d+\\.\\d+).*";
// 白名单ip(不封禁)
private static final set<string> white_list = set.of(
"127.0.0.1",
"192.168.1.1", // 示例,替换成你的管理ip
"10.0.0.1"
);
// 失败次数阈值
private static final int max_retry = 5;
// 封禁时长(秒)
private static final int ban_duration = 3600; // 1小时
// 存储ip失败次数
private final map<string, integer> failedattempts = new hashmap<>();
// 存储已封禁ip及解封时间
private final map<string, localdatetime> bannedips = new hashmap<>();
public void startmonitoring() {
scheduledexecutorservice scheduler = executors.newscheduledthreadpool(1);
// 每10秒扫描一次日志
scheduler.scheduleatfixedrate(this::scanauthlog, 0, 10, timeunit.seconds);
// 每分钟清理过期封禁
scheduler.scheduleatfixedrate(this::unbanexpiredips, 0, 1, timeunit.minutes);
logger.info("ssh 暴力破解监控器已启动...");
}
private void scanauthlog() {
file logfile = new file(auth_log_path);
if (!logfile.exists()) {
logger.warn("日志文件不存在: {}", auth_log_path);
return;
}
try (bufferedreader reader = new bufferedreader(new filereader(logfile))) {
string line;
pattern pattern = pattern.compile(failed_login_pattern);
while ((line = reader.readline()) != null) {
matcher matcher = pattern.matcher(line);
if (matcher.matches()) {
string user = matcher.group(1);
string ip = matcher.group(2);
if (white_list.contains(ip)) {
continue; // 白名单跳过
}
failedattempts.merge(ip, 1, integer::sum);
int attempts = failedattempts.get(ip);
logger.warn("检测到失败登录: 用户={} ip={} 尝试次数={}", user, ip, attempts);
if (attempts >= max_retry && !bannedips.containskey(ip)) {
banip(ip);
}
}
}
} catch (ioexception e) {
logger.error("读取日志文件失败", e);
}
}
private void banip(string ip) {
try {
// 调用 iptables 封禁
string cmd = string.format("iptables -a input -s %s -j drop", ip);
process process = runtime.getruntime().exec(cmd);
process.waitfor();
localdatetime unbantime = localdatetime.now().plusseconds(ban_duration);
bannedips.put(ip, unbantime);
logger.info("⛔ 已封禁 ip: {},解封时间: {}", ip, unbantime.format(datetimeformatter.ofpattern("yyyy-mm-dd hh:mm:ss")));
// 可选:发送邮件/企业微信/钉钉通知
// sendalert("ssh暴力破解警报", "ip " + ip + " 因多次失败登录已被封禁");
} catch (exception e) {
logger.error("封禁ip失败: {}", ip, e);
}
}
private void unbanexpiredips() {
localdatetime now = localdatetime.now();
list<string> tounban = new arraylist<>();
for (map.entry<string, localdatetime> entry : bannedips.entryset()) {
if (now.isafter(entry.getvalue())) {
tounban.add(entry.getkey());
}
}
for (string ip : tounban) {
try {
string cmd = string.format("iptables -d input -s %s -j drop", ip);
process process = runtime.getruntime().exec(cmd);
process.waitfor();
bannedips.remove(ip);
failedattempts.remove(ip); // 清除计数
logger.info("✅ 已解封 ip: {}", ip);
} catch (exception e) {
logger.error("解封ip失败: {}", ip, e);
}
}
}
// 可扩展:发送告警通知
private void sendalert(string title, string content) {
// 此处可集成邮件、slack、企业微信、钉钉等
// 示例略...
}
public static void main(string[] args) {
new sshmonitor().startmonitoring();
}
}如何运行?
- 编译打包成 jar:
mvn clean package
- 后台运行(需 root 权限,因为要执行 iptables):
sudo java -jar ssh-monitor.jar
- 查看日志:
tail -f logs/app.log
可扩展功能建议:
- 对接 redis 存储状态,支持多节点部署
- 记录攻击日志到 mysql/elasticsearch
- 调用云厂商 api(如阿里云、aws)添加安全组规则
- 发送 telegram/slack/dingtalk 告警
- 支持 geoip 定位攻击者地理位置
攻击者地理分布模拟(mermaid 图表)
了解攻击来源有助于制定更有针对性的防御策略。以下是模拟的攻击 ip 地理分布:
渲染错误: mermaid 渲染失败: parsing failed: lexer error on line 3, column 5: unexpected character: ->“<- at offset: 34, skipped 4 characters. lexer error on line 3, column 10: unexpected character: ->:<- at offset: 39, skipped 1 characters. lexer error on line 4, column 5: unexpected character: ->“<- at offset: 48, skipped 4 characters. lexer error on line 4, column 10: unexpected character: ->:<- at offset: 53, skipped 1 characters. lexer error on line 5, column 5: unexpected character: ->“<- at offset: 62, skipped 5 characters. lexer error on line 5, column 11: unexpected character: ->:<- at offset: 68, skipped 1 characters. lexer error on line 6, column 5: unexpected character: ->“<- at offset: 77, skipped 4 characters. lexer error on line 6, column 10: unexpected character: ->:<- at offset: 82, skipped 1 characters. lexer error on line 7, column 5: unexpected character: ->“<- at offset: 91, skipped 4 characters. lexer error on line 7, column 10: unexpected character: ->:<- at offset: 96, skipped 1 characters. lexer error on line 8, column 5: unexpected character: ->“<- at offset: 104, skipped 4 characters. lexer error on line 8, column 10: unexpected character: ->:<- at offset: 109, skipped 1 characters. parse error on line 3, column 12: expecting token of type 'eof' but found `35`. parse error on line 4, column 12: expecting token of type 'eof' but found `25`. parse error on line 5, column 13: expecting token of type 'eof' but found `15`. parse error on line 6, column 12: expecting token of type 'eof' but found `10`. parse error on line 7, column 12: expecting token of type 'eof' but found `8`. parse error on line 8, column 12: expecting token of type 'eof' but found `7`.
可见,攻击来源分布广泛,无法单纯靠地域屏蔽。但可以结合威胁情报(如 abuseipdb)动态更新黑名单。
方法七:使用云端威胁情报服务
一些第三方服务提供全球恶意 ip 数据库,我们可以定期拉取并更新本地防火墙规则。
推荐两个免费 api:
示例:定时拉取 abuseipdb 黑名单
#!/bin/bash
# download-blacklist.sh
api_key="your_api_key_here"
blacklist_url="https://api.abuseipdb.com/api/v2/blacklist?confidenceminimum=90"
curl -g $blacklist_url \
-h "key: $api_key" \
-h "accept: application/json" \
--data-urlencode "confidenceminimum=90" \
| jq -r '.data[].ipaddress' > /tmp/abuseip_blacklist.txt
while read ip; do
iptables -a input -s $ip -j drop
done < /tmp/abuseip_blacklist.txt
logger "已从 abuseipdb 更新 $(wc -l < /tmp/abuseip_blacklist.txt) 个恶意ip"添加到 crontab,每天凌晨更新:
0 2 * * * /path/to/download-blacklist.sh
方法八:双因素认证(2fa)
即使密码或密钥泄露,2fa 也能提供最后一道防线。推荐使用 google authenticator。
ubuntu 安装步骤:
sudo apt install libpam-google-authenticator
运行初始化:
google-authenticator
按提示操作,保存备用码,扫描二维码绑定手机 app。
编辑 pam 配置:
sudo nano /etc/pam.d/sshd
添加:
auth required pam_google_authenticator.so
编辑 ssh 配置:
sudo nano /etc/ssh/sshd_config
确保:
challengeresponseauthentication yes authenticationmethods publickey,keyboard-interactive
重启 ssh:
sudo systemctl restart sshd
下次登录时,除了密钥,还需输入手机上的 6 位验证码。
方法九:定期审计与日志分析
再好的防御也需要定期检查。建议每周审查 ssh 登录日志:
# 查看最近成功登录
last
# 查看失败登录
grep "failed password" /var/log/auth.log | tail -20
# 统计攻击最频繁的ip
grep "failed password" /var/log/auth.log | awk '{print $11}' | sort | uniq -c | sort -nr | head -10
也可以用 goaccess、elk、graylog 等工具做可视化分析。
方法十:使用端口敲门(port knocking)
这是一种“隐写术”式的安全机制:ssh 端口默认关闭,只有按特定顺序“敲击”几个端口后,才临时开放 ssh。
安装 knockd:
sudo apt install knockd
配置 /etc/knockd.conf:
[options]
usesyslog
[openssh]
sequence = 7000,8000,9000
seq_timeout = 10
command = /sbin/iptables -a input -s %ip% -p tcp --dport 2222 -j accept
tcpflags = syn
[closessh]
sequence = 9000,8000,7000
seq_timeout = 10
command = /sbin/iptables -d input -s %ip% -p tcp --dport 2222 -j accept
tcpflags = syn
启动服务:
sudo systemctl start knockd sudo systemctl enable knockd
客户端敲门:
knock your-server-ip 7000 8000 9000 ssh -p 2222 user@your-server-ip
完成操作后,再敲反向序列关闭端口:
knock your-server-ip 9000 8000 7000
隐蔽性极强,但操作略繁琐,适合高安全场景。
测试你的防御是否生效
部署完上述措施后,务必进行测试:
- 端口扫描测试:
nmap -p 22,2222 your-server-ip
应只看到 2222 开放(或完全无响应)。
- 暴力破解模拟(仅限自己服务器!):
hydra -l root -p /path/to/passwords.txt ssh://your-server-ip:2222 -t 4
观察是否被 fail2ban 或 java 工具封禁。
- 登录测试:
确保你仍能通过密钥 + 2fa 正常登录。
总结:构建纵深防御体系
没有单一方案能 100% 防御 ssh 暴力破解。最佳实践是分层防御:

每一层都是保险丝,即使某一层被绕过,下一层仍能阻止入侵。
结语
ssh 是我们管理 linux 服务器的生命线,保护它就是保护我们的数字资产。通过本文介绍的十种方法 —— 从简单改端口,到编写 java 自动防御程序 —— 你可以根据自身需求构建一套坚固、灵活、智能的防御体系。
记住:安全不是一次性任务,而是持续的过程。定期更新、审计、演练,才能在真正的攻击来临时从容应对。
保持警惕,安全无忧。
如果你觉得本文有帮助,欢迎分享给你的运维小伙伴!一起提升 linux 服务器的安全水位!
本文所有代码示例均可在标准 linux + java 11+ 环境下运行。生产环境使用前请充分测试并做好备份。
以上就是linux防止ssh暴力破解的多种方法的详细内容,更多关于linux防止ssh暴力破解的资料请关注代码网其它相关文章!
发表评论