胖叔网络科技 网络科技 · 技术笔记
安全加固

服务器安全加固:从 SSH 到应用层的 18 项检查

按攻击面从外到内整理了一份可执行清单,每项都标注了宝塔面板中的对应操作路径,照着做一遍就能把常见风险堵住。

# SSH# 安全加固# 权限# 防火墙

#加固的基本思路

安全加固不是"装个安全软件",而是按攻击面从外到内逐层收窄。顺序错了会做很多无用功。

我的顺序是:

CODE
网络入口 → 登录凭证 → 文件权限 → 应用逻辑 → 日志监控 → 备份

#一、网络入口:先缩小暴露面

#1. 改掉 SSH 默认端口

扫描器几乎 100% 会扫 22 端口。改成高位端口能过滤掉绝大部分自动化攻击。

宝塔「安全」→ SSH 管理 → 端口。

改完记得同步云厂商安全组,否则会把自己关在门外。

#2. 面板端口不要用 8888

宝塔默认 8888,同样是扫描重点。「面板设置」→「面板端口」改掉。

#3. 数据库端口一律不对外

这是最容易被忽略的高危项

Bash
# 检查监听地址,正确结果应该是 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. 防火墙只放行必要端口

Bash
ufw status verbose

典型放行清单:

端口 用途
自定义 SSH 端口 远程管理
80 / 443 Web 服务
面板端口 宝塔管理

其他一律 deny。 特别是有没有意外放行的 3306、6379。

#5. 云安全组与系统防火墙保持一致

只改一边等于没改。两边都要确认,这是最常见的疏漏。

#二、登录凭证:让暴力破解变得不可行

#6. 禁用 root 密码直登

改用密钥登录。宝塔「安全」→ SSH 管理 → 密钥登录。

密钥的强度远高于密码,且不会被字典攻击。

#7. 开启登录失败封禁

宝塔「安全」→ 添加防爆破规则:

  • 5 次失败封 IP 1 小时
  • 触发后记录到日志便于事后分析

#8. 面板开启双因素认证

「面板设置」→「双因素认证」。多一层验证,成本很低。

#9. 应用后台的登录限流

面板之外的应用自己的后台也要做限流,且必须是服务端的:

JavaScript
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 运行意味着一旦应用被攻破就是整机沦陷

Bash
chown -R www:www /www/wwwroot/blog

#12. 上传目录禁止执行

Bash
chmod -R 0644 /www/wwwroot/blog/public/uploads
find /www/wwwroot/blog/public/uploads -type d -exec chmod 0755 {} \;

再在 Nginx 里显式 deny:

Nginx
location ^~ /uploads/ {
    location ~* \.(php|phtml|jsp|asp|aspx|cgi|pl|sh|py)$ {
        deny all;
    }
}

#13. 数据库文件权限 0600

Bash
chmod 600 /www/wwwroot/blog/data/*.db

#14. 上传文件类型白名单

黑名单永远不完整,用白名单。至少挡住服务端可执行扩展名:

JavaScript
const BLOCKED = new Set(['php', 'phtml', 'jsp', 'asp', 'aspx', 'cgi', 'pl', 'sh', 'htaccess']);

并且磁盘文件名要和用户提供的名字解耦(用时间戳+随机串),彻底消除路径穿越的可能。

#四、应用逻辑:几个必做项

#15. 所有写操作校验 CSRF

只靠 Cookie 鉴权的系统,没有 CSRF 保护就等于把操作接口暴露给了任意网页

JavaScript
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('会话校验失败');
}
JavaScript
cookie: {
  httpOnly: true,      // 禁止 JS 读取,防 XSS 窃取
  sameSite: 'lax',     // 防 CSRF
  secure: true,        // 仅 HTTPS 传输(上了证书后开启)
  maxAge: 14 * 24 * 3600 * 1000,
}

secure 要等 HTTPS 配好再开,否则登录会直接失效。

#17. 输出转义防 XSS

用户提交的内容渲染到页面时必须转义

JavaScript
function escapeHtml(s) {
  return String(s)
    .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}

模板引擎(EJS)的 <%= %> 默认转义,<%- %> 不转义。只有渲染自己生成的 HTML 时才用 <%- %>

#五、日志与监控

#18. 保留日志并关注异常

Bash
# 找出访问量异常的 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 长时间跑满。

#相关资源

以下资料可直接下载:

SH

数据自动备份脚本

每日定时备份站点数据库与附件目录,自动保留最近 14 天并输出清理日志,可直接配到宝塔「计划任务」。

auto-backup.sh 1.2 KB 26 次下载

#检查清单速查

CODE
[ ] 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 个文件,点击直接下载

MD

服务器安全加固清单

从 SSH 加固、防火墙策略、权限收敛到日志审计的 18 项检查清单,附宝塔面板对应的操作路径。

server-hardening-checklist.md 2.0 KB 55 次下载 来自:服务器安全加固:从 SSH 到应用层的 18 项检查