给一个线上的 ThinkPHP 项目做了一轮安全检查,发现了 7 个安全隐患。好消息是每个都不难修,有的甚至就改一行配置的事。

1. 数据库 debug 模式没关

配置文件里:

'debug' => true,

线上开着 debug 模式,SQL 报错时会把完整的 SQL 语句、数据库表名、字段名全部暴露给用户。攻击者拿到这些信息就能针对性地搞你。

修复:改成 false,一个单词的事。

2. 全局输入过滤没开

ThinkPHP 的 app.php 里有个 default_filter 配置:

'default_filter' => '',

空的,意味着所有用户输入都不经过任何过滤就进来了。XSS 攻击随便搞。

修复:

'default_filter' => 'htmlspecialchars,strip_tags',

htmlspecialchars 转义 HTML 特殊字符,strip_tags 去掉 HTML 标签。两层防护。

3. 系统配置每次都查数据库

项目里有个 get_system_val() 函数,读系统配置值,每调用一次就查一次数据库。一个页面调用十几次,就是十几次重复查询。

虽然这个更像是性能问题,但频繁的数据库查询在高并发下也是安全风险(慢查询导致服务不可用)。

修复:加 static 变量缓存,同一请求内不重复查。

function get_system_val($code){
    static $cache = [];
    if (!isset($cache[$code])) {
        $cache[$code] = db('system_config')->where('code', $code)->value('val');
    }
    return $cache[$code];
}

4. 密码规则太弱

原来的密码验证:

preg_match('/[0-9a-zA-Z]{6,20}/', $pwd)

这个正则的问题:111111 能过,aaaaaa 也能过。没有要求必须同时包含字母和数字。

修复:分开检查,必须同时包含字母和数字:

if (!preg_match('/[a-zA-Z]/', $pwd)) return false;
if (!preg_match('/[0-9]/', $pwd)) return false;

5. 重名检查没排除已删除的记录

项目有个 son_repeat() 函数检查同级下是否有重名。但它没有加 is_del=0 的条件,导致已经软删除的记录也会被当成重复。

用户的感受就是:明明删掉了一个分类,再新建同名的时候提示「已存在」。

修复:加一行条件:

$where[] = ['is_del', '=', 0];

6. 自动部署脚本裸奔

项目根目录有个 deploy.php,接收 Gitee WebHook 触发自动部署。虽然有密码验证,但没有 IP 限制和频率限制。

如果密码泄露,任何人都能触发部署,往服务器上拉代码。

修复:

  • 加 IP 白名单,只允许 Gitee 服务器 IP
  • 加频率限制,60 秒内只允许一次
  • 记录请求来源 IP 到日志

7. 后台入口没绑定模块

admin.php 是后台入口,但没有绑定 admin 模块:

Container::get('app')->run()->send();

理论上可以通过 admin.php 访问到 index 模块的内容,虽然有权限验证挡着,但多一层防护总没坏处。

修复:

Container::get('app')->bind('admin')->run()->send();

总结

安全加固不一定要搞得很复杂,很多时候就是:

  • 关掉不该开的东西(debug 模式)
  • 打开该开的东西(输入过滤)
  • 加上缺失的检查(权限、白名单、频率限制)
  • 修正不严谨的逻辑(密码规则、重名检查)

这 7 个修复加起来改动不超过 50 行代码,但安全性提升了一个档次。如果你也在维护 PHP 项目,建议对照着检查一遍。