做技术这么多年,越来越觉得安全不是一次性的事情,而是需要持续投入的日常工作。这个博客系统从最早的几行代码慢慢迭代到现在,功能加了不少,但安全这块一直是"能跑就行"的状态。趁这周有空,我把整个代码库翻了个底朝天,做了一次比较系统的安全审计。
一、审计背景
这个博客是纯原生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,把内联脚本都去掉。但一看代码,内联样式和脚本到处都是,富文本编辑器也依赖内联。真要一步到位,全站都得改。所以先上宽松策略跑一段时间,收集违规报告,再逐步收紧。
五、后续计划
- 把已泄露的第三方密钥全部轮换一遍(AI、图片API、OAuth、SMTP)
- 引入富文本白名单过滤库
- Web服务器配置层加上传目录禁PHP执行
- 拆分几个过大的核心文件(最大的六十多KB)
- 修复循环内查库的N+1问题
- 逐步把API接口接入统一中间件
六、总结
这次审计最大的感受是:安全问题大多不是技术难度高,而是"没想到"和"懒得改"。默认密钥没改、异常信息直接输出、SSL校验被关掉,这些都是几行代码的事,但就是一直放在那里。
另外,建立一份检查清单真的很重要。这次审计完我整理了一份审计清单,以后每次改代码、每次上线前都对照着过一遍,比靠脑子记靠谱多了。
安全是一场持久战,这次只是个开始。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录