发布时间:2026/9/6 4:47:10
基于图书管理系统的漏洞复现与代码审计(二):敏感信息泄露与文件上传 ⚠️ 免责声明本项目仅用于安全学习与教育目的严禁用于非法用途。如因使用本项目造成的任何法律后果使用者自行承担全部责任。本文是上一篇的续篇继续对该图书管理系统进行代码审计。上篇复现了 SQL 注入和水平越权两个漏洞本篇继续审计出敏感信息泄露和文件上传两个漏洞。一、敏感信息泄露漏洞1.1 漏洞原理后端接口直接返回完整的User实体对象Spring Boot 内置的 Jackson 序列化器会自动将所有 getter 方法转成 JSON 字段导致密码明文暴露在响应中。简单来说就是后端把整个 User 对象返回给前端了包括 password 字段。Spring Boot 自动把对象转成 JSON 的时候会调用所有 getter 方法getPassword()也被调用了密码就跟着 JSON 一起返回给前端了。1.2 漏洞代码位置UserController.javaPostMapping(/login)publicResultlogin(RequestBodyUseruser,HttpSessionsession){UserloginUseruserService.login(user.getUsername(),user.getPassword());if(loginUser!null){// 直接把完整的 User 对象返回了包括 passwordreturnResult.success(loginUser);}returnResult.error(用户名或密码错误);}问题loginUser是从数据库查出来的完整 User 对象里面有 password 字段。直接return Result.success(loginUser)Jackson 序列化器会把所有字段都转成 JSON包括 password。1.3 复现步骤步骤 1正常登录抓包用 Burp Suite 拦截登录请求正常登录用户A。注意配置的时候全部用 127.0.0.1别用局域网IP前端发给后端的登录请求所以8080是对的步骤 2查看响应发现密码明文放行请求后查看 Burp 的 Response可以看到返回的 JSON 里包含了 password 字段。密码123456直接明文返回给了前端。步骤 3浏览器控制台验证在浏览器 F12 控制台里也能看到响应内容任何能打开开发者工具的人都能看到密码。1.4 危害任何能抓包的人都能看到密码明文如果前端代码不规范password 可能直接渲染到页面上本项目密码是明文存储拖库后密码直接暴露SQL 注入 敏感信息泄露组合拳注:密码只能存在两个地方用户输入时用户自己知道数据库里应该是Hash不是明文1.5 修复方案方案一DTO 脱敏推荐新建一个不含 password 字段的 DTO 类返回给前端时只包含安全字段// UserDTO.java —— 专门用于返回给前端的类不含 passwordpublicclassUserDTO{privateIntegerid;privateStringusername;privateStringrole;// 没有 password 字段}Controller 里做转换// 【修复代码】使用 DTO 过滤敏感字段UserDTOuserDTOnewUserDTO();userDTO.setId(loginUser.getId());userDTO.setUsername(loginUser.getUsername());userDTO.setRole(loginUser.getRole());returnResult.success(userDTO);修复后返回的 JSONpassword 字段不再出现。方案二JsonIgnore 注解简单在实体类字段上加注解Jackson 序列化时跳过publicclassUser{privateIntegerid;privateStringusername;JsonIgnore// 序列化时跳过此字段privateStringpassword;privateStringrole;}两种方案对比DTO 脱敏JsonIgnore做法新建不含敏感字段的类实体类字段上加注解优点实体和返回数据彻底解耦改一行代码就行缺点多写一个类实体类和接口返回耦合推荐度正规项目推荐简单项目可以1.6 补充密码存储安全本项目密码是明文存储这本身也是安全问题。正确的做法是用 BCrypt 做 Hash 存储自带 Salt每次加密结果不同防彩虹表慢哈希可调计算成本抗暴力破解不可逆只能验证不能反推// 注册时加密存储user.setPassword(passwordEncoder.encode(123456));// 数据库存的是$2a$10$x7k9m2...Hash 值// 登录时验证passwordEncoder.matches(123456,$2a$10$x7k9m2...);// true/false二、文件上传漏洞2.1 漏洞原理后端上传接口没有做任何校验不校验文件类型、不校验文件内容、保留原始文件名、存到 Web 根目录下。攻击者可以直接上传后门文件通过 URL 访问执行任意系统命令实现 GetShell。2.2 漏洞代码位置FileController.javaPostMapping(/upload)publicResultupload(RequestParam(file)MultipartFilefile,HttpServletRequestrequest){try{// 第1步直接拿到用户上传的原始文件名StringfileNamefile.getOriginalFilename();// 如果用户上传 shell.jsp → fileName shell.jsp// 第2步存到 Web 根目录下的 uploads 目录StringuploadPathrequest.getServletContext().getRealPath(/uploads/);// 第3步拼接完整路径直接存FiledestnewFile(uploadPathFile.separatorfileName);if(!dest.getParentFile().exists()){dest.getParentFile().mkdirs();}file.transferTo(dest);// 第4步返回可访问的 URL 路径returnResult.success(/uploads/fileName);}catch(Exceptione){e.printStackTrace();returnResult.error(上传失败e.getMessage());}}问题总结问题说明无文件类型校验不检查扩展名可以上传 .jsp无 MIME 校验不检查 Content-Type无文件内容校验不检查文件头Magic Number保留原始文件名攻击者控制文件名知道访问路径存到 Web 根目录Tomcat 可直接解析执行 .jsp2.3 复现步骤步骤 2Burp 抓包对比上传成功文件被存到了服务器的 Web 目录下返回了可访问路径 /uploads/shell.jsp。后端没有做任何校验——不校验扩展名、不校验MIME类型、不校验文件内容、保留原始文件名、存到Web可访问目录。如果部署在传统 Tomcat 容器中非 Spring Boot 内嵌访问http://localhost:8080/uploads/shell.jsp?cmdwhoami就能执行系统命令实现 RCE。2.4 攻击链总结制作后门文件 ↓ 上传到服务器无校验直接存 ↓ 通过 URL 访问文件 ↓ Tomcat 把 .jsp 当代码执行 ↓ cmd 参数传命令 → RCE2.5 修复方案四层防御// 定义两个白名单privatestaticfinalListStringALLOWED_SUFFIXArrays.asList(jpg,jpeg,png,gif);privatestaticfinalListStringALLOWED_MIMEArrays.asList(image/jpeg,image/png,image/gif);PostMapping(/upload)publicResultuploadSafe(RequestParam(file)MultipartFilefile){try{StringoriginalNamefile.getOriginalFilename();// 第0层空值检查if(originalNamenull||!originalName.contains(.)){returnResult.error(文件名不能为空或无扩展名);}// 提取后缀StringsuffixoriginalName.substring(originalName.lastIndexOf(.)1).toLowerCase();// 第1层白名单校验扩展名 MIME 类型if(!ALLOWED_SUFFIX.contains(suffix)||!ALLOWED_MIME.contains(file.getContentType())){returnResult.error(只允许上传jpg/png/gif图片);}// 第2层Magic Number 文件头校验byte[]headerfile.getBytes();if(header.length4)returnResult.error(文件异常);StringmagicbytesToHex(Arrays.copyOfRange(header,0,4));if(!magic.startsWith(ffd8)!magic.startsWith(89504e47)!magic.startsWith(47494638)){returnResult.error(文件类型不匹配);}// 第3层UUID 重命名StringnewNameUUID.randomUUID().toString().suffix;// 第4层目录隔离存到 Web 根目录外StringuploadPathE:/Code/Library/uploads/images/;FiledestnewFile(uploadPathnewName);dest.getParentFile().mkdirs();file.transferTo(dest);returnResult.success(/static/images/newName);}catch(Exceptione){e.printStackTrace();returnResult.error(上传失败e.getMessage());}}// 辅助方法字节数组转十六进制字符串privatestaticStringbytesToHex(byte[]bytes){StringBuildersbnewStringBuilder();for(byteb:bytes){sb.append(String.format(%02x,b));}returnsb.toString();}2.6 四层防御详解第1层白名单校验扩展名 MIMEStringsuffixoriginalName.substring(originalName.lastIndexOf(.)1).toLowerCase();if(!ALLOWED_SUFFIX.contains(suffix)||!ALLOWED_MIME.contains(file.getContentType())){returnResult.error(只允许上传jpg/png/gif图片);}白名单 vs 黑名单白名单只允许 jpg/jpeg/png/gif → 新危险类型默认被拒绝 ✅黑名单拒绝 jsp/php/exe… → 新危险类型可能遗漏 ❌为什么要同时校验扩展名和 MIME因为攻击者可以同时改 filename 和 Content-Type两个都校验才安全为了挡住只改一个的攻击者。第2层Magic Number 文件头校验每种文件格式开头都有固定的字节标识文件类型Magic Numberhex说明JPEGFF D8 FFJPEG 文件固定开头PNG89 50 4E 47PNG 文件固定开头GIF47 49 46 38GIF 文件固定开头GIF8读取文件前 4 个字节转成十六进制检查是否匹配已知的图片文件头。作用防止攻击者把纯文本脚本代码改扩展名为 jpg 上传。局限拦不住图片马真 JPG 头 恶意代码因为图片马的文件头是真的 JPG 格式。第3层UUID 重命名StringnewNameUUID.randomUUID().toString().suffix;// 结果类似550e8400-e29b-41d4-a716-446655440000.jpg三个作用防路径穿越攻击者传文件名../../etc/passwd重命名后变成 UUID.jpg路径穿越失效防文件覆盖不同用户上传同名文件不会互相覆盖攻击者不知道文件名无法直接通过 URL 访问第4层目录隔离兜底StringuploadPathE:/Code/Library/uploads/images/;// 不在 Web 根目录下Tomcat 无法直接解析执行关键区别漏洞版存到 Web 根目录下 → Tomcat 能解析 .jsp → 执行代码 → RCE 修复版存到 Web 根目录外 → Tomcat 找不到这个目录 → 访问返回 404 → 无法执行这是最后的兜底防御——即使图片马通过了前面所有校验文件存到 Web 根目录外Tomcat 不会把它当作脚本解析执行攻击链断了。2.7 修复验证测试场景结果上传正常图片 cat.jpg✅ 成功能正常访问直接上传 shell.jsp❌ 被白名单拒绝改扩展名 shell.jpg纯文本内容❌ 被 Magic Number 拦住上传图片马真JPG头恶意代码❌ 通过前3层但被目录隔离拦住访问返回 404路径穿越 …/…/etc/passwd❌ UUID 重命名后失效所有攻击场景都被阻断。2.8 关于图片马什么是图片马把一个正常图片和一个后门文件拼在一起copy /b tree.jpg shell.jsp shell.jpg合并后的文件前面是正常的 JPG 文件头和图片数据后面藏着恶意代码。图片马为什么能通过前 3 层防御防御层校验内容图片马的表现结果白名单扩展名是 .jpg.jpg通过 ✅MIMEContent-Type 是 image/jpegimage/jpeg通过 ✅Magic Number文件头是 FF D8 FF真的 JPG 文件头通过 ✅最终靠目录隔离兜底文件存到 Web 根目录外Tomcat 不解析访问返回 404攻击链断了。三、总结漏洞原因危害修复方案敏感信息泄露直接返回 User 实体Jackson 自动序列化所有字段密码明文暴露DTO 脱敏不返回敏感字段文件上传无校验 保留原文件名 存到 Web 根目录GetShell → RCE → 完全控制服务器四层防御白名单 Magic Number UUID 重命名 目录隔离核心思想敏感信息泄露返回给前端的数据要做过滤不能直接把实体对象丢出去文件上传没有单一防御能拦住所有攻击需要多层防御纵深防御任何一层断了攻击链就成功了项目源码GitHub - library-security-lab

相关新闻

2026/9/6 4:42:10

广州 AI 搜索优化服务商:五家候选评估

标题:广州 AI 搜索优化服务商:五家候选评估 AI 搜索与智能问答正在成为用户获取信息的重要入口,企业能否被豆包、DeepSeek、文心一言等 AI 产品准确引用,直接影响品牌曝光与获客效率。广州已有多家服务商开展 AI 搜索优化相关业务…

2026/9/6 4:42:10

2026年什么是FDE服务?一文看懂企业合规降本的核心逻辑

本文从科普角度解析2026年热门的FDE服务,涵盖概念定义、核心逻辑、应用场景,为企业提供选型参考和落地建议。2026年什么是FDE服务?一文看懂企业合规降本的核心逻辑很多企业主对AI落地的印象还停留在“买个大模型、招个技术团队”的阶段&#…

2026/9/6 5:47:13

数据结构:非比较排序:计数排序

前面我们介绍的冒泡、选择、插入、归并、快排等排序算法,本质上都属于比较排序——它们通过元素之间的两两比较来决定先后顺序。本篇我将带大家认识一种另辟蹊径的非比较排序算法——计数排序(Counting Sort),它不靠比较,而是借助数组下标直接…

2026/9/6 5:47:13

关店前最后一晚,我把验证码全部过了一遍

关店前最后一晚,我把验证码全部过了一遍 一个关店卖家的告别: 「决定关店那天,我把店铺里最后的链接都下架了。下架也弹验证,我一个个过,过得很平静。旁边老婆问:都要关了还这么认真干嘛?我说&…

2026/9/6 5:47:13

数据库工程:索引策略落地避坑实战指南‌

数据库工程:索引策略落地避坑实战指南‌ 2026年4月安徽合肥轨道交通3号线南延线的运维管理系统,在早高峰运维数据上报时段突然出现了大面积的接口超时,全线12个站点的运维人员上报完巡检数据之后,系统提交按钮转了半分钟都没有反应…

2026/9/6 5:47:13

网络原理:初识网络的一些基本概念

IP地址网络通信的本质是计算机之间数据的传输。网络里有很多主机,因此需要知道数据从哪个主机发出,哪个主机接收,所以需要IP地址定位主机。IP地址也可以标识网络中的设备,如路由器。格式:32位二进制,一般分…

2026/9/6 5:47:13

Java PrintStream 详解:从基础用法到实战技巧

1. 引言在 Java 开发中,PrintStream 是一个极其常用的输出流类。无论是控制台打印、日志输出还是文件写入,PrintStream 都扮演着重要角色。本文将深入讲解 PrintStream 的核心概念、常用方法以及实战中的注意事项。2. 什么是 PrintStreamPrintStream 是 …

2026/9/6 5:42:13

FANUC机器人CC-Link从站与三菱PLC主站通信配置与故障排查指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/6 0:06:59

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/6 0:06:59

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/6 0:06:59

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/6 0:06:59

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/6 0:06:59

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/6 0:06:59

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/5 2:45:13

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/5 2:30:42

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/5 2:46:50

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…