Linux服务器安全加固:从SSH认证到iptables时间窗规则

发布时间:2026/10/21 23:18:37
Linux服务器安全加固:从SSH认证到iptables时间窗规则
如果你有一台公网可达的Linux服务器或者只是把实验室的主机暴露到办公网里22端口迟早会被扫描器盯上。这个IP只要在公网上露头几分钟内就会出现针对SSH的口令尝试这是常态不是运气问题。你给出的这条iptables规则片段关键词就在-m time这一节它想用“时间窗”来限制SSH入口的访问让非业务时段根本不给握手机会。这个思路很对很多人却卡在规则参数、时区和持久化上。顺着这条命令聊开我会把Linux系统安全拆成六个维度来展开入口认证、网络访问控制、主动封禁、系统基线、文件保护、日志审计。每一块都会给可直接落地的配置和操作步骤也会把这些年实际操作中踩过的坑一并讲清楚。这篇文章适合刚从“能跑就行”迈向“不想被黑”的开发者、小团队运维以及所有自己搭服务器的人。1. 先想清楚Linux系统安全到底该从哪几个方面做1.1 攻击者视角下的入侵路径每当有人问我“Linux系统怎么防攻击”我都会先让他站到攻击者的角度重新看一遍自己的服务器。攻击者的路径其实非常固定先做全端口扫描找出哪些端口开放了一旦发现22端口开放立刻开始SSH暴力破解如果密码弱几分钟就能进去进去之后第一件事就是提权、留后门、清理日志再往后就是横向移动拿你这台机器当跳板去碰同网段的其他机器。这套流程已经被各种自动化脚本跑了几百万遍几乎不需要人工干预。所以“安全”并不是某一个命令、某一条iptables规则能解决的问题而是一条完整的防御链。链条上有任何一个环节是空的攻击者就会从那里钻进来。常见的情况是有人把SSH密码改得很复杂却没有关root密码登录有人把22端口改了却忽略了其他服务的弱口令有人装了一堆fail2ban却忘了系统补丁几个月没打。1.2 分层防御从外网边界到数据兜底理解了攻击路径防御思路就清晰了。我们把整个系统从外到内看成几道防线每一道防线负责拦截一类问题。防线层级典型防护手段主要防什么网络边界iptables/nftables规则、时间窗、访问来源白名单端口扫描、非授权访问入口认证SSH密钥、禁止root、口令策略、二次验证暴力破解、弱口令撞库主动封禁fail2ban、ipset、日志联动脚本高频扫描、持续爆破系统基线最小化服务、系统补丁、内核参数漏洞利用、服务暴露文件与进程权限清理、不可变属性、完整性校验篡改文件、植入后门日志审计syslog、auditd、集中日志掩盖痕迹、恶意操作追溯这份表格就是整篇文章的地图。后面每一章都是在给这张地图补充能直接使用的配置细节。需要注意的是分层防御的意思不是把每一层都做满而是每一层都要有哪怕某一层很薄也比没有强。比如网络层规则就两条也比完全裸奔好一百倍。1.3 这套思路的适用边界这套方案主要针对的是中小规模的Linux服务器个人网站、小团队的自建系统、实验室环境这些场景的威胁模型是“自动化脚本扫描”和“无差别攻击”而不是有组织的高级威胁。如果面对的是有明确目标、投入大量资源的高水平攻击者那需要的远不止这些还要包括流量检测、蜜罐、值守团队等东西。另外安全加固一定要考虑可用性。把服务器锁到连自己都进不去那不是安全是事故。所以我在后面的每个小节里都会强调“先保住自己的逃生通道”。这可能是整篇文章最实用的经验之一任何一次加固操作都必须在明确“如果失败了我怎么还原”的前提下进行。2. 入口防线SSH认证和用户白名单加固2.1 22端口为何成为爆破灾区SSH之所以是重灾区原因很简单它默认监听在22端口而且绝大多数服务器都开着它。对攻击者来说根本不需要猜端口直接扫22就行。你去看服务器上的/var/log/auth.log或者journalctl -u sshd里面几乎每天都有大量Failed password记录大部分来自世界各地不同IP的扫描程序。这些爆破并不可怕真正可怕的是“密码”本身。很多人沿用简单密码比如123456、admin、root这类扫描器通常拿着一个集成了几万条常见口令的字典一轮跑下来命中率不低。所以第一件要做的事不是换端口而是把“密码登录”这条路直接堵上。2.2 密钥认证替换密码登录的落地步骤用SSH密钥替代密码登录是目前性价比最高的入口加固手段。操作并不复杂我直接给完整流程。首先在本地机器不是服务器生成密钥对ssh-keygen -t ed25519 -a 100 -f ~/.ssh/id_ed25519 -C 你的备注名称参数解释一下-t ed25519指定密钥算法Ed25519是目前推荐的现代算法安全性和性能都优于老式的RSA 2048-a 100表示做100次KDF迭代提高私钥的抗暴力破解能力。如果工具不支持Ed25519退一步用ssh-keygen -t rsa -b 4096也可以但Ed25519是更好的选择。然后把公钥传到服务器上ssh-copy-id -i ~/.ssh/id_ed25519.pub 用户名服务器IP执行这条命令需要输入一次服务器密码它会自动把公钥写入服务器的~/.ssh/authorized_keys。接下来测试一下纯密钥登录是否成功确认能登进去之后再执行后面的密码禁用操作。2.3 sshd_config 关键参数修改清单SSH服务的主配置在/etc/ssh/sshd_config。修改之前先备份cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.日期。然后按下面的清单逐项确认。参数推荐设置原因PermitRootLoginno禁止root直接登录避免root被爆破PasswordAuthenticationno禁用密码登录彻底断绝字典爆破PubkeyAuthenticationyes启用密钥认证默认就是yesMaxAuthTries3限制单次连接最大认证尝试次数ClientAliveInterval300每300秒探测一次客户端状态ClientAliveCountMax2连续2次无响应就断开连接AllowUsers指定用户名只允许白名单用户登录其余一律拒绝Protocol2只允许SSH 2协议X11Forwardingno不用图形转发就关闭它UseDNSno关闭反向DNS查询加快登录速度并避免DNS故障影响重点是PermitRootLogin no和PasswordAuthentication no这两项。前者让你日常必须用普通用户登录再用sudo执行管理操作后者直接消灭“密码被字典命中”这个可能性。修改完配置后先做语法检查再重载服务sshd -t systemctl reload sshd注意我用的是reload而不是restart。reload会平滑重载配置并保留现有SSH会话restart会断开当前所有连接。如果是远程操作一次不合时宜的restart可能让你直接失去入口。2.4 二次验证与最小权限如果这台机器上跑着重要业务建议再把二次验证加上。使用基于时间的一次性口令TOTP方案登录时除了密钥还要输入手机上动态验证码。相关工具主流发行版都有现成软件包服务端启用后不会太复杂。启用之前一定先确认手机应用已经绑定成功并生成正确验证码再修改sshd_config开启认证否则你有概率把自己锁在门外。最小权限是另一个容易被忽略的点。日常维护时一律不要用root直接敲命令而是用一个普通用户配合sudo。在/etc/sudoers.d/下给用户配置权限尽量只授权需要的命令而不是笼统给ALL。比如一个用户只需要重启某个服务那就只授权那一条命令。这样即使某个用户被攻破攻击者拿到的权限也是受限的。细节就是visudo检查语法不会的不要自己手工改/etc/sudoers。3. 网络访问控制拆解那条iptables时间窗规则3.1 逐段看懂标题里的命令你这条规则片段是-a input -p tcp -m tcp --dport 22 -m time --timest。我先说一个实战中常见的坑iptables命令行参数是区分大小写的。如果在命令行上直接执行追加规则应该写成大写-A INPUT链名也是大写INPUT。原文里的-a input是小写严格来说直接敲进去会报错。这种大小写错误在博客文章、聊天记录里非常常见抄的时候一定要看清楚。拆开来看每个片段的意思如下-m time --timest...调用time时间匹配模块配合--timestart和--timestop限定时间段。后面timest明显是参数被截断了正常应该是--timestart。-p tcp -m tcp --dport 22匹配TCP协议、目标端口为22的封包。整条规则的意图就是只在指定时间段内允许外部访问SSH端口。这条命令的思想是对的。但单独一条规则没法用原因是iptables默认不是“白名单制”。即使你加入了时间窗规则其他时间依然可以通过其他规则或者默认策略放行22端口。必须配合默认策略和兜底DROP规则。3.2 拼出一条完整可用的时间窗规则我直接把常用的完整规则组贴出来这套规则适合“工作日业务时间开放SSH其他时间关闭SSH”的场景# 默认策略入站和转发丢弃出站放行 iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT # 放行回环接口 iptables -A INPUT -i lo -j ACCEPT # 放行已建立连接和关联连接这是保命规则 iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # 时间窗内放行SSH iptables -A INPUT -p tcp -m tcp --dport 22 -m time \ --timestart 09:00:00 --timestop 21:00:00 \ --weekdays Mon,Tue,Wed,Thu,Fri \ --kerneltz -j ACCEPT # 时间窗外对SSH端口直接丢弃 iptables -A INPUT -p tcp --dport 22 -j DROP这里有几个关键点。--weekdays可以限制一周内的指定日期--kerneltz告诉模块使用系统本地时区否则它会按UTC计算中国时区会整整偏差8小时。这一点后面单开一节详细说因为这是最容易翻车的地方。如果你需要更精准的窗口还可以用--datestart和--datestop指定绝对日期用来做“某一段时间临时封锁”或者“假期封禁”非常方便。3.3 规则顺序与默认策略怎么配合iptables的规则是从上往下逐条匹配的命中即停止。所以规则顺序直接决定行为。最典型的错误是把-A INPUT -p tcp --dport 22 -j DROP放在ESTABLISHED,RELATED规则前面然后发现服务器上所有已有的SSH连接全部断开。为什么因为回包不属于ESTABLISHED吗不对实际上回包是ESTABLISHED但DDOS? 我们理一下你本地SSH客户端连接服务器22端口从服务器角度来看进入方向是目的端口22的SYN包属于NEW状态而响应出去的SYN-ACK是ESTABLISHED,RELATED不完全是。实际上SSH连接是TCP服务器收到SYN后回复SYN-ACK这属于连接跟踪中的ESTABLISHED的一部分。如果DROP规则在ESTABLISHED之前连服务器的回包都可能不会被特殊处理其实对于已建立的连接因为服务器首次收到SYN时就已经建立连接后续双向包都是ESTABLISHED所以对于已建立的SSH连接入站包目的端口是随机高端口而不是22所以22端口的DROP不会影响现有SSH连接。但如果你把默认策略改成INPUT DROP且没有放行ESTABLISHED,RELATED现有SSH连接也会断因为服务器要回复客户端但回复是OUTPUT方向默认OUTPUT ACCEPT所以没问题但是客户端发来的后续入站数据包不是ESTABLISHED它属于ESTABLISHED状态如果INPUT DROP且没有放行ESTABLISHED客户端发来的数据包会被丢弃连接体验马上就会中断。这就是为什么ESTABLISHED,RELATED是保命规则。无论如何-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT放在最前面是必须的。另外默认策略INPUT DROP请谨慎使用。如果是在远程机器上第一次配置防火墙建议先不要直接改默认策略而是把规则一条条加好用iptables -L -n -v确认最后再切默认策略。或者使用iptables-apply这个工具它会加载规则并等待用户确认超时或确认失败会自动恢复原规则避免远程操作时把自己锁在外面。3.4 让规则重启不丢失iptables规则的实质是内核netfilter框架里的一组运行时配置重启机器就没了。所以保存规则和配置规则同样重要。不同发行版有不同做法。RHEL/CentOS系较老的版本有service iptables save会自动写入/etc/sysconfig/iptablesDebian/Ubuntu系可以安装iptables-persistent安装时或之后执行netfilter-persistent save保存。但如果你用的是容器、精简系统或者只是想更可控我建议直接用最原始的方案iptables-save /etc/iptables.rules然后在开机启动时恢复iptables-restore /etc/iptables.rules可以用systemd服务或者/etc/rc.local来执行。最简单的做法是在systemd里加一个oneshot服务但大多数场景下iptables-persistent已经够用。需要留意的是如果你修改了规则一定要重新执行一次保存否则重启后又回到旧规则排查起来非常令人崩溃。3.5 最容易翻车的时区问题time模块默认按UTC处理时间。如果你在国内本地时间是晚上10点UTC时间却是下午2点差8小时。很多人配置了--timestart 09:00 --timestop 18:00结果早上发现9点了SSH还不能连晚上却一直开着就是时区没设置。解决办法就是加--kerneltz参数让模块直接使用内核时间也就是系统当前设置好的本地时区。在多数现代发行版上--kerneltz是可用且推荐的如果你的内核或iptables版本不支持就用--localtz。加了参数之后规则会严格按照服务器本地时间生效。还有一个隐藏坑如果服务器系统时间本身不准时间窗规则也会错乱。所以要让系统时间保持自动同步。使用systemd的发行版一般自带systemd-timesyncd也可以装NTP工具。不管用哪个保证date命令输出的本地时间准确是一切时间相关规则生效的基础。4. 主动防御fail2ban、手动封禁与入口隐藏4.1 fail2ban 如何自动拉黑攻击IP时间窗规则能挡住非工作时间的扫描但工作时间内的暴力破解还是要靠fail2ban这类主动防御工具。它的工作机制不复杂监听SSH日志文件用正则表达式识别登录失败的记录统计每个来源IP在指定时间窗口内的失败次数一旦超过阈值就调用系统防火墙把该IP封禁一段时间。配置起来很简单。安装好之后在/etc/fail2ban/jail.local里写[sshd] enabled true filter sshd logpath /var/log/auth.log backend systemd maxretry 5 findtime 10m bantime 1h如果发行版使用systemd日志logpath可以换成journalctl方式配置里用backend systemd即可。参数含义findtime10m表示只看10分钟内的记录maxretry5表示同一IP失败5次就触发bantime1h表示封禁1小时。启动之后用fail2ban-client status sshd查看封禁状态能看到当前被拉黑的IP列表。如果某个IP被误伤用fail2ban-client set sshd unbanip 具体IP即可解除。这里有个小经验bantime建议设置成-1表示永久封禁但对于动态IP环境不推荐因为DHCP用户可能共用IP一旦被封会影响正常用户访问。1到24小时是比较平衡的选择。4.2 不用工具也能写封禁脚本有时候环境不允许装第三方软件或者你想完全掌控封禁逻辑也可以自己写一个脚本。核心思路就是从SSH日志里提取失败IP统计次数超过阈值就交给iptables封掉。下面是一个简单的命令链可以快速列出最近10分钟内失败次数最多的IPjournalctl -u sshd --since 10 minutes ago | grep Failed password | grep -oE from [0-9.] | awk {print $2} | sort | uniq -c | sort -nr如果某个IP的次数超过你的阈值可以手动封禁iptables -I INPUT -s 该IP -j DROP封禁之后记得重新保存规则集否则重启失效。对于大量IP批量封禁的场景iptables一条条加效率太低我建议用ipset先建一个集合再在INPUT链上引用这个集合之后只需往集合里加IP即可ipset create brute-force iphash iptables -I INPUT -m set --match-set brute-force src -j DROP ipset add brute-force 1.2.3.4脚本化的完整版比这复杂要考虑日志轮转、白名单、封禁时间过期自动解封等。如果暂时没有精力维护老老实实用fail2ban就好它已经把这些逻辑都处理得很成熟。4.3 高敏感机器还可以做入口隐藏对安全级别要求高的机器还可以考虑“入口隐藏”也就是把SSH端口藏起来不让外部扫描器直接发现。扫描器扫到22端口开放会觉得“这有一台机器尝试破解一下”扫不到22端口大概率直接跳过。入口隐藏比较实用的是“端口敲门”思路正常情况下iptables对22端口的所有入站访问都直接DROP当客户端按特定顺序尝试连接一组端口后服务器才临时放行该来源IP的22端口访问。iptables自带的recent模块就可以实现不用装额外服务。思路如下访问端口7000登记为“第一步”访问端口7001登记为“第二步”然后才允许22端口。这中间还要配合时间限制避免敲门信号被重放。但这种方案有代价所有合法用户都要先执行敲门动作增加了使用复杂度recent模块在UDP和TCP混合场景下还有各种边界情况。我的建议是除非这台机器对外完全不提供服务、只有管理员自己访问否则优先采用“业务时间窗口来源IP白名单”而不是端口敲门。4.4 主动防御的边界在哪里一定要清醒fail2ban、ipset封禁这些手段本质是“降低攻击速度”而不是“阻断攻击”。攻击者可以用分布式IP绕过封禁也可以在你睡眠时间慢慢磨。它们能帮你挡住绝大多数无差别爆破但挡不住想进的人。所以主动防御必须和入口认证配合使用。如果你允许密码登录就算有fail2ban攻击者也可能在封禁窗口内试出正确密码如果你把密码登录关了、只用密钥就算被爆破对方没有私钥也进不来。我见过不少配置看起来“拉满了”但开了密码登录最后仍然被攻破的案例。主次不能颠倒。5. 系统基线加固服务、内核与文件三件事5.1 服务和端口最小化很多人用的系统是通用发行版装完自带一堆服务打印服务、蓝牙服务、各种不需要的守护进程。一台服务器上跑的服务越多攻击面越大。检查当前系统有哪些服务是开机启动的systemctl list-unit-files --stateenabled把不认识的、确定用不到的逐一禁用systemctl disable --now 服务名然后看看当前监听端口确认哪些是对外开放的ss -tulnss -tuln输出中0.0.0.0和::表示监听在所有网卡上127.0.0.1表示只监听本机回环。如果有服务没必要对外监听就去改它的配置让它只绑定在回环地址上。例如数据库、缓存这类服务永远不要监听在公网接口上。这条基线能直接影响你被攻击的概率监听端口越少扫描器看到的缝隙就越少。5.2 内核网络参数与漏洞修复系统补丁是最容易被忽略但性价比极高的环节。内核漏洞、OpenSSL漏洞、SSH服务的漏洞一旦有了公开POC互联网上立刻就会出现扫描脚本。建议保持系统包定期更新# Debian系 apt update apt upgrade # RHEL系 yum update注意生产环境升级前先做备份或快照内核升级后一般需要重启才生效把重启窗口安排在业务低峰期。内核网络参数建议按需收紧编辑/etc/sysctl.conf并执行sysctl -p生效# 关闭IP转发防止被当作跳板 net.ipv4.ip_forward 0 # 关闭ICMP重定向 net.ipv4.conf.all.accept_redirects 0 net.ipv4.conf.all.send_redirects 0 # 忽略广播ping减少被探测的可能 net.ipv4.icmp_echo_ignore_broadcasts 1 # 开启反向路径过滤防止源地址伪造 net.ipv4.conf.all.rp_filter 1这些参数的调整不会影响绝大多数正常业务但能有效减少一部分网络攻击面。修改sysctl同样需要谨慎有些参数比如ip_forward如果服务器本身要做路由转发就不能直接关掉。逐项确认是否影响现有业务再应用。5.3 关键文件的权限与不可变保护文件篡改是攻击者留后门最常用的一步。检查几个关键文件的权限ls -l /etc/passwd /etc/shadow /etc/ssh/sshd_config正常情况下/etc/passwd对普通用户可读/etc/shadow只有root可读。如果发现有异常权限立刻chmod修正。对变更非常少的文件可以用chattr i设置不可变属性。设置之后即使是root也无法修改、删除、重命名该文件。通常适合锁定的文件包括/etc/passwd、/etc/shadow。但要注意一旦锁定后续你想系统性地添加用户、修改密码必须先用chattr -i解锁否则操作会报错。对/etc/ssh/sshd_config这类经常调整的文件我不建议锁锁了反而给自己添麻烦。另外建议建立文件完整性基线。用完整性校验工具记录关键文件的哈希值定期比对能在攻击者篡改文件后及时发现。这类工具有的依赖数据库文件有的可以生成报告发送到远程日志。代价是需要维护基线并妥善保存初始基线数据否则攻击者看得到你的基线文件直接把基线也改了比对就失去了意义。5.4 不要一上来就关安全子系统很多发行版默认带强制访问控制机制它们会在传统的“用户权限”之外给每个进程、文件打标签限制进程能访问哪些资源。有些人觉得这东西碍事装完系统第一件事就是关掉。这是一个非常大的风险决定。强制访问控制确实是运维新手最头疼的环节之一因为它经常拦着正常业务不让你跑。但正确的做法不是关掉而是先切换成宽松模式看日志找出被拦截的项针对具体规则做适配最后再恢复到强制模式。一句话它可以不“正常工作”但你不该把它当成敌人。如果一台机器长期关闭了这套机制我建议找一个变更窗口开启强制模式并准备好回退方案。一旦在强制模式下启动因为策略主题的拒绝而被干掉的非法访问恰恰是平时你没注意到的风险点。6. 常见问题与排查技巧实录6.1 时间窗规则为何总差8小时症状明明配置了9点到18点开放SSH到了9点还是连不上晚上反而通了。排查第一步看系统时间是否准确。date命令输出本地时间。第二步查看iptables规则里有没有--kerneltz或--localtz。如果不加time模块默认使用UTC你的“9点”在模块内部被理解为UTC的9点换算成北京时间就是17点。所以“9点没开、傍晚开了”完全符合逻辑。解决在时间窗规则中加上--kerneltz重载规则。然后可以用iptables -L -n -v查看规则命中次数或者开一个临时端口做对比测试确认窗口行为符合预期。6.2 规则全是对的但流量就是不通常见原因有三个。第一规则顺序问题。DROP规则被放在了ACCEPT规则前面永远轮不到后面的ACCEPT执行。iptables匹配是“先到先得命中即止”把宽松的放行规则放在前面把严格的拒绝规则放在后面。第二ESTABLISHED,RELATED没放行。如果默认策略是INPUT DROP这条缺失会导致所有出站连接的回包进不来表现为“能主动访问别人但外部访问你的服务一直超时”。其实本地连接本身也可能出现“SSH连上了但打字回显卡死”的诡异现象。第三规则没有持久化。每次重启后规则恢复原状看起来像“偶尔通偶尔不通”。用iptables-save保存规则到持久化文件并在开机时恢复。6.3 重启后防火墙规则被清空iptables规则存在于内存中重启即丢失。如果你发现重启后所有规则没了不是系统出问题而是没有保存。解决思路RHEL系执行service iptables saveDebian系安装iptables-persistent并netfilter-persistent save或者自己写一个开机恢复脚本。另外修改规则后养成习惯测试通过后立刻保存。很多人是在重启之后才想起来“哦我没保存”但那时候已经晚了。6.4 SSH配置改错了怎么办先说预防手段。远程修改SSH配置前务必先开一个root shell并保持在线不要关掉当前SSH会话这次修改后先开一个新的SSH session验证可以登录再去关闭旧会话。改完执行sshd -t检查语法语法通过后systemctl reload sshd。这些动作按顺序执行能让你大概率不会把自己锁在门外。如果已经失联了就需要走“带外”通道云主机一般有控制台/网页终端虚拟机可以通过虚拟化管理界面进去物理机只能靠现场接显示器了。进去后恢复配置备份或者把sshd_config里改过的项调回来再systemctl restart sshd。所以改配置前备份的文件就是你的救命稻草。我强烈建议备份文件名里带日期多个历史版本留存以后排查问题也能对比。6.5 实战经验速查表问题可能原因快速处理SSH时间窗不生效时区差了8小时加--kerneltz确认系统时间准确重启后防火墙丢失没保存规则iptables-save持久化开机恢复新配置连不上SSHsshd_config有语法错误检查sshd -t用备份恢复再reloadfail2ban没有封禁日志路径不对、正则不匹配确认logpath/backend用fail2ban-client status查看所有入站连接异常少了ESTABLISHED,RELATED放行把状态放行规则放在最前面端口扫不到但业务异常服务没监听或防火墙没放行UDP核对ss -tuln与实际规则这些坑我基本上都亲身踩过一遍。最想强调的一点是任何加固操作先想“测试环境验证”、“备份”、“保留逃生通道”这三件事能过滤掉大多数人为故障。给自己留退路不是不够专业恰恰是专业的一部分。