1. 项目概述:为什么你的Linux服务器可能“裸奔”?
最近在帮几个朋友的公司做安全巡检,发现一个挺普遍的现象:很多运维兄弟把服务部署上线后,除了改个密码,几乎没做任何额外的安全加固。问起来,大家的回答出奇地一致:“感觉默认配置就挺安全的啊”、“业务能跑起来就行,哪有时间搞那么细”。结果呢?轻则被挖矿脚本入侵,CPU跑满;重则数据被加密勒索,整个业务停摆。今天,我就以一个踩过无数坑的“老运维”身份,跟你聊聊Linux系统安全加固那些真正关键、但99%的教程都语焉不详的实战配置。
这不仅仅是一份检查清单。我会带你深入每个配置项的背后,讲清楚“为什么要这么改”、“不这么改会有什么风险”,以及“改了之后万一出问题怎么快速回滚”。我们的目标很明确:打造一个既安全又稳定的生产环境,让你能睡个安稳觉。无论你是刚接触Linux的新手,还是有一定经验的开发者、运维,这篇指南都会提供从基础到进阶的、可直接抄作业的实操步骤。
2. 安全加固的核心思路:纵深防御与最小权限
在动手改任何一个配置文件之前,我们必须先统一思想。安全加固不是东一榔头西一棒子地打补丁,而是一个系统工程。我把它总结为两个核心原则:纵深防御和最小权限原则。
2.1 理解纵深防御:没有一劳永逸的银弹
很多朋友以为,配置了复杂的防火墙规则就高枕无忧了。这是非常危险的误解。纵深防御的意思是,我们需要在攻击者通往核心资产的路径上,设置多层、不同类型的防御措施。即使某一层被突破(这是必然的,没有攻不破的盾),后续层还能继续提供保护,为我们的应急响应争取时间。
一个典型的Linux服务器纵深防御体系可以这么看:
- 网络边界层:靠防火墙(如
iptables/nftables、firewalld)控制哪些IP、哪些端口可以访问服务器。这是第一道大门。 - 服务访问层:对暴露的服务本身进行加固,比如SSH、Web服务器(Nginx/Apache)、数据库。即使攻击者到了门口,也得有正确的“钥匙”和“暗号”才能进。
- 系统内核与资源层:通过内核参数调优、文件系统权限、资源限制(ulimit)等,防止攻击者在系统内部进行提权、资源耗尽攻击。
- 审计与监控层:通过日志审计(auditd)、入侵检测系统(如AIDE, rkhunter)以及实时监控,确保我们能发现异常行为,并留下可供追溯的证据。
这套体系里,任何单点失效都不应导致全线崩溃。我们的配置工作,就是围绕这四层逐一展开。
2.2 贯彻最小权限原则:只给必要的,一点不多
这是安全领域的黄金法则,但也是最容易被忽视的。它的核心是:每个用户、每个进程、每个服务,都应该只拥有完成其任务所必需的最小权限。
我举个例子你就明白了。很多部署脚本为了方便,直接让Web应用(比如一个PHP程序)以root身份运行,或者给它/etc目录的写权限。这意味着,一旦这个Web应用存在漏洞(比如文件上传漏洞),攻击者就能通过它直接篡改系统关键配置、植入后门,瞬间获得整个服务器的控制权。
正确的做法是什么?为这个Web应用单独创建一个低权限的系统用户(如www-data或nginx),让它只能访问自己的网站目录和必要的日志目录。这样即使被入侵,破坏范围也被限制在非常小的范围内。
在接下来的所有配置中,请你时刻用这个原则来审视自己的操作:这个用户真的需要sudo权限吗?这个服务真的需要监听所有网卡(0.0.0.0)吗?这个脚本真的需要可执行权限吗?多问一句,风险就降低一分。
3. 网络与访问控制:扎紧篱笆的第一道关卡
服务器暴露在网络上,就像房子开了门。我们的第一步就是把不必要的门都关上,给必要的门加上最结实的锁。
3.1 防火墙配置:不仅仅是开关端口
现在主流的Linux发行版基本都集成了firewalld(RHEL/CentOS/Fedora)或UFW(Ubuntu/Debian),它们比原始的iptables命令友好得多。但友好不代表简单,里面有很多细节值得深究。
使用firewalld构建区域隔离策略
不要只用一个public区域应付所有。我建议根据服务器角色划分区域:
# 假设这是一台Web服务器,有内网管理接口eth0和公网业务接口eth1 # 1. 为内网管理接口创建可信区域 sudo firewall-cmd --permanent --new-zone=trusted-lan sudo firewall-cmd --permanent --zone=trusted-lan --add-source=192.168.1.0/24 # 假设内网段 sudo firewall-cmd --permanent --zone=trusted-lan --add-service=ssh # 内网可以SSH # 2. 为公网业务接口配置严格规则 sudo firewall-cmd --permanent --zone=public --change-interface=eth1 sudo firewall-cmd --permanent --zone=public --add-service=http sudo firewall-cmd --permanent --zone=public --add-service=https # 明确拒绝其他所有入站流量(默认就是拒绝,但显式声明更清晰) sudo firewall-cmd --permanent --zone=public --set-target=DROP # 3. 将内网接口关联到可信区域 sudo firewall-cmd --permanent --zone=trusted-lan --change-interface=eth0 # 4. 重载配置并设为开机启动 sudo firewall-cmd --reload sudo systemctl enable firewalld --now注意:
--permanent参数表示将规则写入永久配置,否则重启后失效。但--reload会加载永久配置并覆盖当前运行时配置。生产环境操作顺序永远是:先在运行时测试(不加--permanent),确认无误后再--permanent+--reload。
容易被忽略的“富规则”(Rich Rules)富规则让你能进行更精细的控制,比如限制SSH的访问频率,防止暴力破解。
# 在public区域,限制每分钟只能尝试3次SSH连接,超过则拒绝10分钟 sudo firewall-cmd --permanent --zone=public --add-rich-rule=' rule family="ipv4" source address="0.0.0.0/0" service name="ssh" limit value="3/m" reject '这条规则比单纯改SSH端口有用得多,因为它直接在网络层拦截了高频攻击。
3.2 SSH深度加固:告别密码,拥抱密钥与堡垒机
SSH是运维的生命线,也是攻击者最常攻击的入口。仅修改端口和禁止root登录是远远不够的。
1. 密钥认证与禁用密码登录这是必须做的第一步。生成密钥对,将公钥上传到服务器,然后彻底关闭密码登录。
# 本地生成密钥(推荐ed25519,比rsa更安全更快) ssh-keygen -t ed25519 -C "your_email@example.com" -f ~/.ssh/id_ed25519_work # 将公钥上传到服务器,并设置正确的权限(这一步至关重要!) # 在服务器上操作: mkdir -p ~/.ssh chmod 700 ~/.ssh cat >> ~/.ssh/authorized_keys # 然后粘贴你的公钥内容,按Ctrl+D结束 chmod 600 ~/.ssh/authorized_keys然后编辑/etc/ssh/sshd_config:
PubkeyAuthentication yes PasswordAuthentication no # 关键!禁用密码登录 ChallengeResponseAuthentication no UsePAM no # 如果不需要PAM认证,可以关闭以简化流程 PermitRootLogin no # 禁止root直接登录实操心得:在禁用
PasswordAuthentication前,务必先用新的密钥会话测试登录成功,并保持一个已认证的旧会话窗口打开。万一新密钥配置有误,你还可以通过旧会话修复。这是血的教训。
2. 使用监听套接字(Socket)而非常驻端口这是一个高级技巧,能极大降低SSH端口的暴露时间。我们让SSH服务不直接监听端口,而是通过systemd socket在连接到来时按需启动。
# 编辑 /etc/ssh/sshd_config,注释掉原来的 Port 22,改为: #Port 22 # 然后启用并配置socket sudo systemctl enable ssh.socket sudo systemctl start ssh.socket sudo systemctl disable ssh.service # 注意:是disable常驻服务,启用socket现在,只有当有连接尝试访问22端口时,sshd服务才会被临时启动,处理完连接后一段时间内无新连接则会自动停止。这相当于给你的SSH门加了一个“感应灯”,没人时就熄灯,让端口扫描工具更难发现。
3. 为SSH连接设置“二次确认”(堡垒机思路)即使有了密钥,我们还可以增加一层“命令确认”。这可以通过authorized_keys文件的强制命令实现。
# 在服务器的 ~/.ssh/authorized_keys 文件中,在你的公钥前加上: command="/usr/local/bin/ssh_command_filter.sh" ssh-ed25519 AAAAC3Nz... your_key然后创建/usr/local/bin/ssh_command_filter.sh脚本:
#!/bin/bash # 记录所有SSH执行的命令 echo "$(date): $SSH_ORIGINAL_COMMAND from $SSH_CLIENT" >> /var/log/ssh_command.log # 这里可以加入你的逻辑,比如禁止某些危险命令,或者要求二次验证 # 例如,如果命令包含“rm -rf /”,则拒绝 case "$SSH_ORIGINAL_COMMAND" in *"rm -rf "*) echo "Dangerous command rejected!" exit 1 ;; *) # 执行原始命令 eval "$SSH_ORIGINAL_COMMAND" ;; esac这个脚本就像一个简单的内部堡垒机,可以审计和过滤所有通过SSH执行的命令。
4. 系统服务与权限收敛:关掉多余的后门
服务器默认会启动很多你可能永远用不到的服务,每一个都是潜在的攻击面。我们的原则是:不用的,坚决关掉。
4.1 服务精简与管控
使用systemctl进行服务管理
# 查看所有正在运行的服务 sudo systemctl list-units --type=service --state=running # 查看所有已启用的服务(开机自启) sudo systemctl list-unit-files --type=service --state=enabled # 禁用并停止一个明确不需要的服务,例如蓝牙(在服务器上通常无用) sudo systemctl disable bluetooth.service sudo systemctl stop bluetooth.service # 对于不确定的服务,先将其启动模式设为手动,而不是直接禁用 sudo systemctl disable avahi-daemon.service # 例如,禁用mDNS服务 sudo systemctl mask avahi-daemon.service # 更彻底:屏蔽,防止被其他服务意外拉起排查技巧:如何判断一个服务是否可以关闭?首先,
systemctl status service_name查看它的描述和功能。其次,用lsof -i -P -n | grep LISTEN查看它监听了哪些端口。如果这个端口你不知道是干什么的,用netstat -tulnp | grep :端口号或ss -ltnp | grep :端口号找出对应进程,再结合进程名判断。
4.2 文件与目录权限的“黄金标准”
Linux的权限体系(rwx)很强大,但用不好就是灾难。遵循以下原则:
1. 关键系统目录权限
# 检查并设置关键目录权限(脚本示例) sudo chmod 750 /boot /usr/src /lib/modules # 防止普通用户读取内核相关文件 sudo chmod 700 /root # root家目录必须只有root可访问 sudo chmod 755 /tmp # 确保/tmp有粘滞位(默认应有),防止用户删除他人文件 sudo chmod 644 /etc/passwd /etc/group # 保持可读,不可写 sudo chmod 600 /etc/shadow /etc/gshadow # 影子文件必须只有root可读写2. 查找并修复全局可写目录全局可写目录(权限为777或目录的组/其他用户有写权限)是攻击者最喜欢的地方,用于上传木马。
# 查找系统中所有全局可写的目录(排除/proc, /sys等虚拟文件系统) sudo find / -path /proc -prune -o -path /sys -prune -o -path /dev -prune -o -type d -perm -0002 -ls 2>/dev/null对于发现的非必要全局可写目录,立即修正权限。对于像/tmp、/var/tmp这样的必要可写目录,确保其设置了粘滞位(chmod +t),这样只有文件所有者才能删除自己的文件。
3. 使用访问控制列表(ACL)进行精细控制有时候,标准的ugo权限不够用。比如,你想让一个日志文件能被adm组读取,但又不希望改成全局可读。这时就用ACL。
# 安装ACL工具(通常已安装) # 为文件添加特定组的读权限 sudo setfacl -m g:adm:r /var/log/syslog # 查看文件的ACL getfacl /var/log/syslogACL非常强大,但也要谨慎使用,避免权限设置过于复杂难以管理。
5. 内核安全与系统调优:筑牢底层防线
Linux内核提供了大量和安全相关的参数,通过sysctl可以动态调整。正确的配置能有效缓解多种攻击。
5.1 关键内核安全参数配置
编辑/etc/sysctl.d/99-security-hardening.conf文件(独立文件便于管理),加入以下内容:
# 1. 网络堆栈安全加固 # 禁用ICMP重定向(防止中间人攻击) net.ipv4.conf.all.accept_redirects = 0 net.ipv6.conf.all.accept_redirects = 0 net.ipv4.conf.all.send_redirects = 0 # 启用源路由验证(防止IP欺骗) net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.default.rp_filter = 1 # 忽略ICMP广播请求,防止Smurf攻击 net.ipv4.icmp_echo_ignore_broadcasts = 1 # 2. 内存与栈保护 # 启用内存地址空间布局随机化(ASLR),增加攻击者预测地址的难度 kernel.randomize_va_space = 2 # 3. 进程与资源限制相关 # 禁止普通用户查看其他用户的进程(/proc限制) kernel.kptr_restrict = 2 kernel.perf_event_paranoid = 3 # 核心转储文件处理。生产环境通常禁用,或指向一个受控目录 # fs.suid_dumpable = 0 # 如果设置,需确保目录存在且权限正确使配置生效:sudo sysctl -p /etc/sysctl.d/99-security-hardening.conf。
注意事项:这些参数有些比较激进,比如
kernel.perf_event_paranoid = 3可能会影响一些性能监控工具。在应用前,最好在测试环境验证你的监控栈(如Prometheus Node Exporter)是否仍能正常工作。
5.2 利用Linux安全模块:AppArmor vs SELinux
这是两个最主流的强制访问控制(MAC)系统,能为进程划定严格的“活动范围”。很多人觉得它们太难而直接关闭,这是因噎废食。
对于大多数用户,我推荐AppArmor(Ubuntu/Debian默认),因为它基于路径配置,相对直观。
# 检查状态 sudo aa-status # 安装工具 sudo apt install apparmor-utils apparmor-profiles -y # 将某个进程置于“抱怨”模式,学习其正常行为 sudo aa-complain /usr/sbin/nginx # ...运行你的服务,进行各种正常操作... # 然后生成一个配置文件草案 sudo aa-genprof /usr/sbin/nginx # 根据提示,对程序的各种访问请求选择“Allow”(A)或“Deny”(D) # 最后,将模式切换为“强制”模式 sudo aa-enforce /usr/sbin/nginx现在,Nginx就被限制在了你定义的规则内,即使有漏洞,攻击者也无法读取/etc/shadow或执行/bin/bash。
对于RHEL/CentOS/Fedora,SELinux是默认且强大的。关键在于理解其“上下文”概念。
# 查看文件或进程的SELinux上下文 ls -Z /var/www/html ps -eZ | grep nginx # 如果因为SELinux导致服务异常,查看审计日志获取线索 sudo ausearch -m avc -ts recent # 根据日志提示,使用`audit2allow`生成临时规则或永久规则我的建议是:除非你非常熟悉,否则不要将SELinux设置为Permissive或Disabled。遇到权限问题,先看日志,再针对性解决,这能极大提升你的系统安全水位。
6. 审计、监控与入侵检测:让攻击行为无处遁形
安全加固不是一劳永逸的配置,而是一个持续的过程。你需要知道系统正在发生什么。
6.1 系统审计框架(auditd)配置
auditd是内核级别的审计工具,可以记录非常详细的事件,比如文件访问、系统调用、用户命令等。
# 安装 sudo apt install auditd audispd-plugins # Debian/Ubuntu sudo yum install audit audit-libs # RHEL/CentOS # 关键规则配置:编辑 /etc/audit/rules.d/audit.rules # 1. 审计所有对passwd文件的写访问 -w /etc/passwd -p wa -k identity # 2. 审计所有特权命令的执行(su, sudo) -a always,exit -F arch=b64 -S execve -C uid!=euid -F euid=0 -k priv_esc -a always,exit -F arch=b64 -S execve -C gid!=egid -F egid=0 -k priv_esc # 3. 审计所有失败的文件操作 -a always,exit -F arch=b64 -S open,openat,open_by_handle_at -F exit=-EACCES -k access -a always,exit -F arch=b64 -S open,openat,open_by_handle_at -F exit=-EPERM -k access # 重启服务 sudo systemctl restart auditd sudo systemctl enable auditd配置好后,你可以使用ausearch或aureport来查询日志。例如,sudo ausearch -k identity会查看所有和身份文件相关的审计事件。
6.2 文件完整性校验(AIDE)
AIDE(Advanced Intrusion Detection Environment)会为你的关键系统文件建立一个“指纹”数据库。定期运行校验,就能发现文件是否被篡改。
# 安装 sudo apt install aide -y # 初始化数据库(这可能需要一些时间,因为它要扫描大量文件) sudo aideinit sudo mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz # 创建一个每日运行的校验任务 sudo crontab -e # 加入一行,例如每天凌晨2点运行,并将报告邮件发送给你 0 2 * * * /usr/bin/aide --check | mail -s "AIDE Report for $(hostname)" your-email@example.com # 手动运行一次检查 sudo aide --check实操心得:AIDE的初始配置很关键。默认的配置文件
/etc/aide/aide.conf包含了很多规则。我建议你根据服务器角色精简它,重点关注/bin,/sbin,/usr/bin,/usr/sbin,/etc,/boot等目录,以及ssh_host_keys。忽略经常变化的目录,如/var/log,/home。每次系统合法更新(如apt upgrade)后,记得更新AIDE数据库:sudo aide --update。
6.3 日志集中管理与告警
本地日志容易被攻击者清理。将关键日志实时发送到远程的、受保护的日志服务器(如ELK Stack, Graylog, 或简单的rsyslog服务器)是最佳实践。
对于使用systemd的现代发行版,配置日志转发到远程rsyslog:
# 编辑 /etc/systemd/journald.conf [Journal] # 启用远程日志转发 ForwardToSyslog=yes # 保持较大的本地存储,以防网络中断 SystemMaxUse=1G然后在/etc/rsyslog.conf或/etc/rsyslog.d/下创建规则,将日志转发到远程服务器。
更重要的是设置日志监控告警。你可以用logwatch或fail2ban(针对SSH等服务的暴力破解)这样的工具,也可以自己写简单的脚本。例如,监控/var/log/auth.log中SSH的失败登录:
#!/bin/bash # 简单示例:检查过去5分钟内失败的SSH登录尝试,如果超过10次就发邮件 FAIL_COUNT=$(grep "Failed password" /var/log/auth.log | grep "$(date --date='5 minutes ago' '+%b %e %H:%M')" | wc -l) if [ "$FAIL_COUNT" -gt 10 ]; then echo "Warning: $FAIL_COUNT failed SSH attempts in last 5 minutes on $(hostname)" | mail -s "SSH Attack Alert" admin@example.com fi7. 常见问题与排查技巧实录
安全加固过程中,最怕的就是改完配置,服务起不来了,或者自己也被关在门外。这里记录几个我踩过的坑和解决方法。
7.1 问题:SSH加固后,密钥登录失败
症状:配置了PasswordAuthentication no并重启sshd后,使用密钥也无法登录,提示“Permission denied (publickey)”。
排查步骤:
- 检查服务状态:
sudo systemctl status sshd,确保服务在运行。 - 检查日志:立刻在服务器上(如果你还有别的会话)或通过控制台查看
/var/log/auth.log或/var/log/secure。关键错误信息通常在这里。 - 检查文件权限(最常见原因):SSH对
.ssh目录和authorized_keys文件的权限要求极其严格。- 用户家目录不能有写权限给组和其他用户:
chmod go-w ~ .ssh目录权限必须是700:chmod 700 ~/.sshauthorized_keys文件权限必须是600:chmod 600 ~/.ssh/authorized_keys.ssh目录的所有者必须是该用户本人。
- 用户家目录不能有写权限给组和其他用户:
- 检查sshd配置:确认
PubkeyAuthentication yes。有时配置文件可能有多个相同配置项,后面的会覆盖前面的,仔细检查。 - 使用详细模式调试:在客户端连接时加上
-vvv参数,会输出非常详细的连接过程,能精准定位在哪一步失败。ssh -vvv user@hostname。
7.2 问题:防火墙规则导致业务服务无法访问
症状:配置了firewalld或iptables后,外部无法访问Web服务(80/443端口)。
排查步骤:
- 列出当前所有规则:
sudo firewall-cmd --list-all(firewalld)或sudo iptables -L -n -v(iptables)。仔细检查你的服务端口是否在允许的规则中。 - 检查区域绑定:确认你的网卡绑定到了正确的防火墙区域。
sudo firewall-cmd --get-active-zones。 - 检查服务定义:
firewalld通过服务名(如http)来管理端口。确保服务定义包含了正确的端口:sudo firewall-cmd --info-service=http。 - 临时放行测试:在确保安全的前提下,可以临时添加一条规则测试:
sudo firewall-cmd --add-port=80/tcp。如果通了,说明是规则问题;如果还不通,可能是服务本身没监听或者被其他(如云服务商)安全组拦截了。 - 查看服务监听地址:
sudo ss -ltnp | grep :80。如果服务只监听在127.0.0.1(本地回环),那么外部自然无法访问。需要修改服务配置(如Nginx的listen指令)为0.0.0.0:80。
7.3 问题:SELinux/AppArmor导致服务异常
症状:服务配置正确,防火墙也放行了,但服务就是报“权限不足”(Permission Denied)错误,尤其是在访问非默认目录下的文件时。
排查步骤:
- 首先查看系统日志:
sudo dmesg | tail或sudo journalctl -xe,寻找带有“avc: denied”(SELinux)或“apparmor=“DENIED””(AppArmor)字样的错误信息。这是最直接的证据。 - 对于SELinux:
- 临时设置为宽容模式测试:
sudo setenforce 0。如果问题消失,则确认是SELinux问题。 - 根据日志,使用
audit2allow生成允许规则。切勿直接禁用SELinux。 - 示例:
sudo grep avc: /var/log/audit/audit.log | audit2allow -M mypolicy,然后sudo semodule -i mypolicy.pp。
- 临时设置为宽容模式测试:
- 对于AppArmor:
- 查看服务当前状态:
sudo aa-status。 - 将对应配置文件置于抱怨模式:
sudo aa-complain /path/to/binary,然后测试服务是否正常。如果正常,说明是AppArmor限制。 - 使用
aa-logprof或aa-genprof来生成新的、更宽松的配置文件。
- 查看服务当前状态:
7.4 问题:系统加固后性能下降
症状:应用响应变慢,系统负载升高。
排查思路:
- 审计规则过多:过多的
auditd规则会消耗CPU和I/O。使用sudo auditctl -l查看当前活动规则,优化规则,只审计最关键的事件。 - 文件完整性校验扫描:AIDE或类似工具的全盘扫描会占用大量I/O。将扫描任务安排在业务低峰期(如凌晨)。
- 过于严格的网络过滤:复杂的
iptables规则链会增加网络延迟。尽量使用state模块来建立有状态的规则,减少需要逐条匹配的规则数量。 - SELinux/AppArmor策略:过于宽泛的策略会增加内核开销。确保你的策略是精确的,只允许必要的访问路径。
安全加固是一个平衡的艺术,需要在安全性和可用性、性能之间找到最佳结合点。我的经验是,采用“白名单”思维,默认拒绝一切,只开放必要的。每次修改配置后,进行充分的功能测试和性能基准测试。将配置变更纳入版本控制系统(如Git),这样在出现问题时可以快速回滚。最后,保持警惕,安全不是一次性的工作,而是一个需要持续关注和更新的过程。