当前位置:首页 > 探索

网络安全漏洞排查报告

上个月帮一家做跨境电商的网络朋友看服务器,刚连上就发现他们后台登录接口返回的安全报错信息里,直接把数据库版本号写在了页面上。漏洞MySQL 5.7.29。排查这个版本有个已知的报告CVE-2021-2307,虽然不算致命,网络但等于给攻击者递了把钥匙。安全朋友还一脸茫然:“这也能看出来?漏洞”

排查漏洞这事,很多时候不是排查靠扫描器跑一遍就完事。工具能报出一堆中危低危,报告真正要命的网络往往藏在业务逻辑里。我习惯先看三个地方:登录、安全上传、漏洞查询接口。排查这三个位置出问题,报告基本等于门没锁。

登录接口:别让报错信息变成说明书

那家电商的登录接口,输错密码返回“密码错误”,输对账号但密码为空返回“密码不能为空”,输一个不存在的账号返回“用户不存在”。这等于告诉别人:这个账号是存在的,你只需要猜密码。正确的做法是统一返回“账号或密码错误”。

更离谱的是,他们有个“忘记密码”功能,输入手机号后,页面直接显示“验证码已发送至138****1234”。手机号前三位后四位都露了。攻击者拿个手机号字典跑一遍,能筛出大量真实用户。这种信息泄露在《个人信息安全规范》里明确算违规,但开发的时候没人注意。

上传接口:文件类型校验不能只看后缀

有个做在线教育的客户,允许老师上传课件。前端限制只能选 .ppt 和 .pdf,后端拿到文件后只检查了 Content-Type。我把一个 .jsp 文件改名为 .pdf,再用 Burp Suite 把 Content-Type 改成 application/pdf,直接上传成功。访问路径,服务器解析了 JSP,拿到了 webshell。

修复方案不复杂:后端读取文件头魔数,PDF 必须是 %PDF-,PPT 必须是 D0 CF 11 E0。同时把上传目录的脚本执行权限禁掉。Nginx 里加一行 location ~* /uploads/.*\.(jsp|php|asp)$ { deny all; } 就能拦住大部分。

查询接口:SQL注入没死,只是变懒了

很多人觉得用了 ORM 就安全了。我见过一个用 MyBatis 的项目,Mapper XML 里写的是 ${ userId} 而不是 #{ userId}。区别在于 #{ } 会预编译,${ } 直接拼接。攻击者传个 1' OR '1'='1,整个用户表就出来了。

还有个更隐蔽的:排序参数。前端传 sortField=create_time,后端直接拼进 ORDER BY。我试了 sortField=create_time; DROP TABLE users--,虽然 MySQL 默认不支持多语句,但换成 sortField=(CASE WHEN (SELECT SUBSTRING(password,1,1) FROM users LIMIT 1)='a' THEN create_time ELSE id END) 就能盲注出管理员密码。这种基于布尔的条件注入,WAF 很难拦。

我的排查清单

每次接活,我会按这个顺序过一遍:

  • 抓登录、注册、找回密码的请求,看返回信息是否泄露用户存在性
  • 找所有文件上传点,传一个改后缀的 jsp/php,看能不能访问
  • 翻 JS 文件,搜 api/、/v1/、/v2/ 这些路径,往往能发现没在前端暴露的接口
  • 对每个查询参数加单引号、双引号、反斜杠,看报错
  • 检查 Cookie 里的 sessionId 是否 HttpOnly、Secure
  • 看服务器响应头有没有 X-Powered-By、Server 版本号

最后说个事。那家电商修完漏洞后,我建议他们加个 WAF。运维说预算不够,我说那你至少把 Nginx 的日志打开,每天 grep 一下 union select、../、%00 这些关键词。三天后他发我截图,有人一直在试 /admin.php?cmd=whoami。你看,攻击从来不会等你准备好。

分享到: