做技术这么多年,越来越觉得安全不是一次性的事情,而是需要持续投入的日常工作。这个博客系统从最早的几行代码慢慢迭代到现在,功能加了不少,但安全这块一直是"能跑就行"的状态。趁这周有空,我把整个代码库翻了个底朝天,做了一次比较系统的安全审计。

一、审计背景

这个博客是纯原生PHP写的,没有用任何框架,前后台加起来大概一百多个PHP文件。功能包括文章发布、会员系统、评论、音乐播放器、第三方登录、AI优化等。代码量不大,但因为是一步步加功能堆出来的,有些地方的安全措施确实不到位。

这次审计的目标很明确:不是要搞成什么银行级安全,而是把明显的、已知的漏洞先堵上,同时建立一套可复用的检查清单,以后每次改代码都能对照着过一遍。

二、审计范围和方法

审计范围排除了自动发布脚本、缓存日志、上传文件、第三方解析接口这些目录,重点看核心业务代码。

检查维度主要包括:

  • SQL注入(虽然用了PDO预处理,但还是要确认有没有遗漏的拼接)
  • XSS跨站脚本(输出转义、富文本过滤)
  • CSRF跨站请求伪造(表单和API的令牌校验)
  • 鉴权与越权(后台和会员中心的登录态校验)
  • 文件上传(类型校验、路径穿越、上传目录执行权限)
  • 密钥管理(默认密钥、明文存储)
  • 异常信息泄露(错误信息是否回显给用户)
  • SSRF服务端请求伪造(远程URL下载功能)
  • SSL证书校验(出网请求是否验证证书)
  • N+1查询(性能问题顺带看了)

三、发现的问题

审计下来,问题比想象中多。按严重程度分级:

P0 立即修复(3项)

1. 默认密钥没改

配置文件里的核心密钥还是初始化时的出厂默认值。这个key是用来做API远程发布鉴权的,任何人知道这个默认值就能直接调用发文接口往博客里灌垃圾文章。这个是最危险的,第一时间就换成了强随机值。

2. 回调密钥没改

第三方解析接口的回调密钥也是默认值,虽然影响面小一些,但同样可能被伪造调用。

3. 密钥明文存储在版本库

配置文件里明文存了数据库密码、AI接口密钥、图片API密钥、各个OAuth平台的密钥、SMTP密码。这个暂时没动,因为涉及到要去各个平台重新签发密钥,工作量比较大,列在了后续计划里。

P1 本周修复(5项)

1. IP地址可伪造

原来获取客户端IP的函数优先读取代理转发头(比如X-Forwarded-For)。问题是这些头是客户端可以随便伪造的,导致后台的IP白名单、登录限流、API频率限制全部可以被绕过。

修复方案:默认只信任直接连接的远程地址,只有配置了可信代理网段时才解析代理转发头。

2. API接口缺统一鉴权

二十来个API接口里,大部分缺CSRF校验,有几个完全没有鉴权。比如点赞接口、好友申请接口,任何人都可以伪造请求批量刷。

修复方案:新建了一个统一的API安全中间件,提供登录校验、Token校验、CSRF校验、频率限制等公共函数,后续接口逐步接入。

3. 异常信息直接回显

多处异常捕获块直接把异常详情输出给用户。异常信息里可能包含SQL片段、文件路径、内部结构,等于给攻击者送地图。

修复方案:统一改成"操作失败,请稍后重试"这种通用文案,详情写到日志里。

4. SSRF漏洞

上传接口支持通过参数下载远程图片,但完全没有做URL校验,而且禁用了SSL证书验证、允许无限跟随重定向。攻击者可以用这个功能请求内网服务,比如本地回环地址、云服务器元数据地址等。

修复方案:协议白名单(仅http/https)、主机IP私网/保留地址拦截、元数据地址拦截、恢复SSL校验、限制跳转次数且跳转后重新校验目标。

5. 出网请求禁用SSL校验

多处curl请求把证书校验和主机名校验都关掉了。这等于HTTPS白用了,中间人可以随便篡改响应内容。

修复方案:全部恢复证书校验,HTTP请求封装的默认值也改成开启校验。

P2 双周修复(3项)

1. XSS过滤可绕过

原来的XSS过滤用的是黑名单方式,只过滤了javascript协议,没过滤data URI。通过data URI构造的HTML是可以执行脚本的。

修复方案:增加了data URI和vbscript协议的拦截。长远来看应该换成白名单过滤方案,但那是个大工程,先把明显的洞补上。

2. 缺CSP头

只有点击劫持防护、MIME类型嗅探防护这些基础安全头,没有内容安全策略(CSP)。CSP是防XSS的重要纵深防御手段。

修复方案:加了一个相对宽松的CSP策略,允许内联样式和脚本(兼容现有系统),后续观察无违规后再逐步收紧。

3. 上传目录可能执行PHP

这个需要在Web服务器配置层处理,代码层面管不了,列在了待办里。

四、修复过程中的一些思考

安全和便利性的平衡

比如IP伪造这个问题,原来的写法是为了兼容反向代理环境(Nginx后面挂PHP-FPM,远程地址是本地回环)。但不能为了便利就把所有代理头都信任,正确的做法是配置可信代理网段,只信任来自这些IP的代理头。

SSL校验的"历史遗留"

问了下自己,当时为什么要禁用SSL校验?大概率是本地测试环境证书有问题,图方便就关了,然后上线的时候忘了开回来。这种"临时方案"最容易变成永久漏洞。以后加配置项,本地可以关,线上默认开。

CSP不能一步到位

一开始想直接上严格的CSP,把内联脚本都去掉。但一看代码,内联样式和脚本到处都是,富文本编辑器也依赖内联。真要一步到位,全站都得改。所以先上宽松策略跑一段时间,收集违规报告,再逐步收紧。

五、后续计划

  1. 把已泄露的第三方密钥全部轮换一遍(AI、图片API、OAuth、SMTP)
  2. 引入富文本白名单过滤库
  3. Web服务器配置层加上传目录禁PHP执行
  4. 拆分几个过大的核心文件(最大的六十多KB)
  5. 修复循环内查库的N+1问题
  6. 逐步把API接口接入统一中间件

六、总结

这次审计最大的感受是:安全问题大多不是技术难度高,而是"没想到"和"懒得改"。默认密钥没改、异常信息直接输出、SSL校验被关掉,这些都是几行代码的事,但就是一直放在那里。

另外,建立一份检查清单真的很重要。这次审计完我整理了一份审计清单,以后每次改代码、每次上线前都对照着过一遍,比靠脑子记靠谱多了。

安全是一场持久战,这次只是个开始。