服务器安全加固:从 SSH 到应用层的 18 项检查
按攻击面从外到内整理了一份可执行清单,每项都标注了宝塔面板中的对应操作路径,照着做一遍就能把常见风险堵住。
#加固的基本思路
安全加固不是"装个安全软件",而是按攻击面从外到内逐层收窄。顺序错了会做很多无用功。
我的顺序是:
网络入口 → 登录凭证 → 文件权限 → 应用逻辑 → 日志监控 → 备份
#一、网络入口:先缩小暴露面
#1. 改掉 SSH 默认端口
扫描器几乎 100% 会扫 22 端口。改成高位端口能过滤掉绝大部分自动化攻击。
宝塔「安全」→ SSH 管理 → 端口。
改完记得同步云厂商安全组,否则会把自己关在门外。
#2. 面板端口不要用 8888
宝塔默认 8888,同样是扫描重点。「面板设置」→「面板端口」改掉。
#3. 数据库端口一律不对外
这是最容易被忽略的高危项:
# 检查监听地址,正确结果应该是 127.0.0.1:3306 而不是 0.0.0.0:3306
ss -ltnp | grep -E '3306|6379|27017'
MySQL 和 Redis 如果监听 0.0.0.0,等于把数据库直接放到公网上。Redis 未授权访问是服务器被挖矿的头号原因。
#4. 防火墙只放行必要端口
ufw status verbose
典型放行清单:
| 端口 | 用途 |
|---|---|
| 自定义 SSH 端口 | 远程管理 |
| 80 / 443 | Web 服务 |
| 面板端口 | 宝塔管理 |
其他一律 deny。 特别是有没有意外放行的 3306、6379。
#5. 云安全组与系统防火墙保持一致
只改一边等于没改。两边都要确认,这是最常见的疏漏。
#二、登录凭证:让暴力破解变得不可行
#6. 禁用 root 密码直登
改用密钥登录。宝塔「安全」→ SSH 管理 → 密钥登录。
密钥的强度远高于密码,且不会被字典攻击。
#7. 开启登录失败封禁
宝塔「安全」→ 添加防爆破规则:
- 5 次失败封 IP 1 小时
- 触发后记录到日志便于事后分析
#8. 面板开启双因素认证
「面板设置」→「双因素认证」。多一层验证,成本很低。
#9. 应用后台的登录限流
面板之外的应用自己的后台也要做限流,且必须是服务端的:
const attempts = new Map();
const MAX_TRY = 6;
const WINDOW = 10 * 60 * 1000;
function checkThrottle(ip) {
const rec = attempts.get(ip);
if (!rec) return { blocked: false };
if (Date.now() - rec.first > WINDOW) {
attempts.delete(ip);
return { blocked: false };
}
if (rec.count >= MAX_TRY) {
return { blocked: true, wait: Math.ceil((WINDOW - (Date.now() - rec.first)) / 60000) };
}
return { blocked: false };
}
注意必须按 IP 计数,且要有时间窗口重置,否则正常用户输错几次也会被永久锁死。
#10. 默认密码首次登录必须改
.env 里的初始密码只是引导用。应用应该在首次登录后强制跳转到改密页,或者在后台显著提示。
#三、文件权限:最小权限原则
#11. 站点目录属主不要用 root
Web 进程以 root 运行意味着一旦应用被攻破就是整机沦陷。
chown -R www:www /www/wwwroot/blog
#12. 上传目录禁止执行
chmod -R 0644 /www/wwwroot/blog/public/uploads
find /www/wwwroot/blog/public/uploads -type d -exec chmod 0755 {} \;
再在 Nginx 里显式 deny:
location ^~ /uploads/ {
location ~* \.(php|phtml|jsp|asp|aspx|cgi|pl|sh|py)$ {
deny all;
}
}
#13. 数据库文件权限 0600
chmod 600 /www/wwwroot/blog/data/*.db
#14. 上传文件类型白名单
黑名单永远不完整,用白名单。至少挡住服务端可执行扩展名:
const BLOCKED = new Set(['php', 'phtml', 'jsp', 'asp', 'aspx', 'cgi', 'pl', 'sh', 'htaccess']);
并且磁盘文件名要和用户提供的名字解耦(用时间戳+随机串),彻底消除路径穿越的可能。
#四、应用逻辑:几个必做项
#15. 所有写操作校验 CSRF
只靠 Cookie 鉴权的系统,没有 CSRF 保护就等于把操作接口暴露给了任意网页。
function csrfGuard(req, res, next) {
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) return next();
const sent = req.body._csrf || req.get('x-csrf-token');
if (sent && sent === req.session.csrf) return next();
return res.status(403).send('会话校验失败');
}
#16. Cookie 安全属性
cookie: {
httpOnly: true, // 禁止 JS 读取,防 XSS 窃取
sameSite: 'lax', // 防 CSRF
secure: true, // 仅 HTTPS 传输(上了证书后开启)
maxAge: 14 * 24 * 3600 * 1000,
}
secure 要等 HTTPS 配好再开,否则登录会直接失效。
#17. 输出转义防 XSS
用户提交的内容渲染到页面时必须转义:
function escapeHtml(s) {
return String(s)
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
.replace(/"/g, '"').replace(/'/g, ''');
}
模板引擎(EJS)的 <%= %> 默认转义,<%- %> 不转义。只有渲染自己生成的 HTML 时才用 <%- %>。
#五、日志与监控
#18. 保留日志并关注异常
# 找出访问量异常的 IP
awk '{print $1}' /www/wwwlogs/blog.log | sort | uniq -c | sort -rn | head -20
# 找出返回 4xx/5xx 最多的路径
awk '$9 ~ /^[45]/ {print $9, $7}' /www/wwwlogs/blog.log | sort | uniq -c | sort -rn | head -20
同时开启宝塔「监控」,关注 CPU / 内存 / 磁盘的突增。挖矿木马最典型的表现就是 CPU 长时间跑满。
#相关资源
以下资料可直接下载:
数据自动备份脚本
每日定时备份站点数据库与附件目录,自动保留最近 14 天并输出清理日志,可直接配到宝塔「计划任务」。
#检查清单速查
[ ] 01 SSH 端口已改
[ ] 02 root 密码直登已禁用
[ ] 03 登录失败封禁已开启
[ ] 04 面板端口非默认
[ ] 05 面板双因素已开启
[ ] 06 数据库端口未对外监听
[ ] 07 防火墙仅放行必要端口
[ ] 08 云安全组与系统防火墙一致
[ ] 09 站点目录属主非 root
[ ] 10 上传目录无执行权限
[ ] 11 Nginx 已 deny 上传目录脚本
[ ] 12 数据库文件权限 0600
[ ] 13 上传类型白名单已启用
[ ] 14 磁盘文件名与原名解耦
[ ] 15 CSRF 校验已覆盖所有写操作
[ ] 16 Cookie 安全属性已配置
[ ] 17 输出转义已覆盖用户内容
[ ] 18 日志保留 + 监控告警已开启
完整清单和配套脚本可在文末下载。
附件下载 共 1 个文件,点击直接下载
服务器安全加固清单
从 SSH 加固、防火墙策略、权限收敛到日志审计的 18 项检查清单,附宝塔面板对应的操作路径。