【狄仁杰探案】修复Node.js后端图片上传接口漏洞
【版权声明】本文为百里飞洋原创博文,未经直接授权,禁止在任何平台以任何形式进行转载
【引用须知】本文仅接受适当合理地引用,并请附上本文博客的原文链接
【文章标题】由 OpenAI 人工智能大模型 GPT-3.5 拟定本文的章回体小标题,由作者百里飞洋审定修改
【文章插图】由通义万象绘制(人工智能艺术创作大模型 Powered by Alibaba Cloud)
温馨提示:本文涉及的所有数据已脱敏,包括但不限于文件名称、日期时间、邮箱地址、域名信息等,均为方便文章论述的仿造值,不代表真实情况。
00 本文目录
话说在那月黑风高的夜晚,寒鸦掠过孤月,飞向那怪爪乱生的阴森树枝,不时凄清的叫着。凉风拂过,卷着那黑云,遮住苍白的弯月。突然,树中传来一阵疾劲的风声,一道黑影闪过枝顶。一声轻响,黑影落在了屋檐上,同时屋中传来一阵疑惑的声音。原来,一位探案大侠名叫狄仁杰,在凌晨翻阅案文时,被电脑屏幕前显示的日志信息惊讶到了。
什么,古代没有电脑?我可没说故事发生在古代,我是说现在。
故事中“办案”的大侠就是我,这个凌晨加班备份系统数据的小小程序员。(流汗黄豆)
【第一回】深夜寻踪排日志 找迹寻源严调查
【第二回】极寒冰岛浮水面 狡兔三窟新域名
【第三回】防不胜防前端页 测试完毕迷难解
【第四回】逻辑欠缺后端码 修复完善尽开颜
话不多说,今天的故事咱们就按照上方的目录讲起。
01 日志追踪排查
第一回 “深夜寻踪排日志 找迹寻源严调查”
今日在浏览自己接手的一个项目数据库时,发现头像上传日志里有几条不太合规的记录,大概如表格后三条这样:
id | 文件名称 | 时间戳 |
---|---|---|
42 正常图片 | iMnGfF3KRD.jpg | 1694594376124 |
43 异常文件 | Y6ZM3L9sFV.jsp | 1698822596987 |
44 异常文件 | ptB5eUfodt.jsp | 1698822611332 |
45 异常文件 | Xs5JCB53IS.jsp | 1698822656218 |
可以看到,有用户在头像接口上传了 3 个 JSP 文件,时间分别为 2023 年 11 月 01 日的 15:09:56
、15:10:11
和 15:10:56
,其中第一次与第二次操作间隔了 15 秒,第二次与第三次操作间隔了 45 秒。
经过进一步的排查,发现该用户于当日 15:04:58
使用邮箱账号 aba@example.xyz
获取了注册验证码,稍后又使用邮箱账号 aaa@example.xyz
重新获取了注册验证码,并于 15:07:39
注册成功。
02 邮箱域名调研
第二回 “极寒冰岛浮水面 狡兔三窟新域名”
通过 Whois 查询,发现该邮箱所使用域名的注册信息如下(以下信息仅为论述需要,不代表真实情况):
查询信息 | 查询结果 |
---|---|
域名 | example.xyz |
注册商 | Namecheap |
whois-ser | whois.namecheap.com |
更新日 | 2023-10-25 17:17:06 UTC+8 |
注册日 | 2023-10-14 22:52:24 UTC+8 |
过期日 | 2024-10-15 07:59:59 UTC+8 |
IANA_ID | 1068 |
省/州 | Capital Region |
国别 | 冰岛 (IS) |
状态 | clientTransferProhibited (注册商禁止转移) |
DNS | dns1.registrar-servers.com dns2.registrar-servers.com |
DNSSEC | 未配置 |
呦呵,刚刚注册一周的,这就有趣了,看着不像是正经域名,而且也无法访问。
03 前端功能测试
第三回 “防不胜防前端页 测试完毕迷难解”
我先进行了前端页面的排查,测试是否能够直接上传 JSP 文件,看看我是否“错怪”了这位仁兄。
因为在选择文件时我已经做了 MIME 类型 校验[1](以下代码仅为演示需要,不保证功能完整性):
1 | // 选择的图片文件改变时 |
经过测试,系统前端页面的头像裁剪组件是不能选择除图片外的文件的。但是为了更加规范图片类型,我就再上点儿强度,直接定死可选择的文件后缀名,省的以后再有用户上传奇奇怪怪的东西作为头像(比如 gif 动图):
1 | // 选择的图片文件改变时 |
那既然前端功能没问题,就只能是后端没做好文件校验了,让人直接调用了接口上传了不该传的东西。
04 后端处理审查
第四回 “逻辑欠缺后端码 修复完善尽开颜”
在前端图片裁剪完毕后,会获取裁剪后的 blob 数据,并还原为 File 对象,然后通过 POST 请求以 form-data
表单的形式提交给后端。在后端我使用的是 Node.js 的中间件 Multer 来处理 multipart/form-data
类型的表单数据[2],并执行相关文件校验和存储逻辑。(生产环境中推荐使用第三方 OSS 等服务存储文件,本文为了方便复现代码逻辑,存储到了后端目录)
于是我又检查了一遍后端代码(以下代码仅为演示需要,不保证功能完整性):
1 | const express = require("express"); |
可以发现,在后端代码中似乎缺少文件后缀名校验的逻辑。查阅官方文档,发现可以传递给 Multer 的选项 multer(opts)
有:
Key | Description |
---|---|
dest or storage |
在哪里存储文件 |
fileFilter |
文件过滤器,控制哪些文件可以被接受 |
limits |
限制上传的数据 |
preservePath |
保存包含文件名的完整文件路径 |
其中 fileFilter
选项貌似可以满足我们的需求。它可以设置一个函数来控制什么文件可以上传以及什么文件应该跳过,这个函数应该看起来像这样:
1 | function fileFilter (req, file, cb) { |
所以我们就可以将上方 multer 实例那部分的代码改造为:
1 | // 创建 multer 实例 |
如此一来,后端也添加了文件扩展名校验,进一步规范了文件上传功能,补充了代码的逻辑缺失。
关于狄仁杰探案的故事,还有很多很多。欲知后事如何,且听下回分解!(本文完)
【参考内容】
一种表示文档、文件或一组数据的性质和格式的标准,也通常称为多用途互联网邮件扩展或 MIME 类型。
[2] expressjs/multer
一个 node.js 中间件,用于处理 multipart/form-data
类型的表单数据,它主要用于上传文件。