当前位置: 首页 > article >正文

【Java代码审计 | 第八篇】文件操作漏洞成因及防范

未经许可,不得转载。

文章目录

  • 文件操作漏洞
    • 文件读取漏洞
      • 基于 InputStream 的读取
      • 基于 FileReader 的读取
    • 文件下载漏洞
    • 文件删除漏洞
    • 防范

在这里插入图片描述

文件操作漏洞

分为文件读取漏洞、文件下载漏洞与文件删除漏洞。

文件读取漏洞

在Java中,文件读取通常有两种常见方式:一种是基于InputStream,另一种是基于FileReader

漏洞成因:未对用户输入做过滤,导致读取敏感文件并返回至客户端。

以下两种代码形式都存在路径遍历问题。

基于 InputStream 的读取

String filename = request.getParameter("filename");  // 假设这是用户输入的文件名
File file = new File(filename);  // 创建文件对象,未进行任何路径验证
InputStream inputStream = new FileInputStream(file);  // 创建输入流
int len;
while (-1 != (len = inputStream.read())) {  // 循环读取文件内容
    outputStream.write(len);  // 将读取的字节写入输出流
}

基于 FileReader 的读取

String filename = request.getParameter("filename");  // 假设这是用户输入的文件名
String fileContent = "";  // 存储文件内容
FileReader fileReader = new FileReader(filename);  // 创建FileReader对象,未进行任何路径验证
BufferedReader bufferedReader = new BufferedReader(fileReader);  // 包装为BufferedReader
String line;
while (null != (line = bufferedReader.readLine())) {  // 逐行读取文件
    fileContent += (line + "\n");  // 拼接每一行内容
}

文件下载漏洞

漏洞成因:未对用户输入做过滤,导致用户端可下载敏感文件。

filename 参数未经过任何验证或过滤,攻击者可以通过构造恶意路径下载系统敏感文件。

String filename = request.getParameter("filename");  // 获取文件名
File file = new File(filename);  // 创建文件对象
response.reset();  // 重置响应
response.addHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes("utf-8")));  // 设置下载文件名
response.addHeader("Content-Length", "" + file.length());  // 设置文件大小
response.setContentType("application/octet-stream; charset=utf-8");  // 设置响应内容类型为二进制流

InputStream inputStream = new FileInputStream(file);  // 创建输入流读取文件
OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());  // 获取输出流
int len;
while (-1 != (len = inputStream.read())) {  // 读取文件并写入响应
    outputStream.write(len);
}
inputStream.close();  // 关闭输入流
outputStream.close();  // 关闭输出流

文件删除漏洞

漏洞成因:未对用户输入做过滤,导致敏感文件被删除。

filename 参数未经过任何验证或过滤,攻击者可以通过构造恶意路径删除系统关键文件。

String filename = request.getParameter("filename");  // 获取文件名
File file = new File(filename);  // 创建文件对象
if (file != null && file.exists() && file.delete()) {  // 检查文件存在并删除
    response.getWriter().println("Delete success");  // 删除成功
} else {
    response.getWriter().println("Delete failed");  // 删除失败
}

防范

1、使用 getCanonicalPath() 方法获取文件的规范路径,并与预期的基路径进行比较,确保文件路径在允许的范围内。

String basePath = "/allowed/directory/";  // 允许访问的基路径
File file = new File(basePath, filename);  // 拼接文件路径
if (!file.getCanonicalPath().startsWith(new File(basePath).getCanonicalPath())) {
    throw new SecurityException("非法文件路径");  // 路径不在允许范围内,抛出异常
}

对以下语句进行解读:

if (!file.getCanonicalPath().startsWith(new File(basePath).getCanonicalPath())) {

file.getCanonicalPath():这个方法返回文件的规范化路径,即将路径中的 .(当前目录)和 …(上级目录)等符号解析成实际的绝对路径。

1、假设:basePath = “/allowed/directory/”
2、用户输入 filename = “…/…/etc/passwd”
3、new File(basePath):创建 /allowed/directory/ 的 File 对象。
4、file = new File(basePath, filename):用户提供的路径是 …/…/etc/passwd,所以拼接后的 file 实际路径为 /allowed/directory/…/…/etc/passwd,这是一个潜在的路径遍历。
5、file.getCanonicalPath():获取文件的规范化路径,经过解析后变为 /etc/passwd(因为路径 …/…/ 会使路径跳转到根目录)。
6、new File(basePath).getCanonicalPath():获取 /allowed/directory/ 的规范化路径,即 /allowed/directory/。
7、在这种情况下,file.getCanonicalPath()(即 /etc/passwd)不会以 /allowed/directory/ 开头,因此会进入 if 语句块,阻止访问该文件。

其次是检查文件后缀是否在允许的后缀白名单中,举个例子:

// 定义允许的文件后缀白名单
Set<String> allowedExtensions = new HashSet<>(Arrays.asList(
    "txt", "pdf", "jpg", "png", "doc", "xls"
));

String filename = request.getParameter("filename");  // 获取文件名
String fileExtension = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();  // 提取后缀并转为小写

// 检查后缀是否在白名单中
if (!allowedExtensions.contains(fileExtension)) {
    throw new SecurityException("非法文件类型");  // 文件类型不在白名单中,抛出异常
}

File file = new File(filename);  // 创建文件对象

// 使用 try-with-resources 读取文件
try (InputStream inputStream = new FileInputStream(file);
     OutputStream outputStream = new BufferedOutputStream(response.getOutputStream())) {
    int len;
    while (-1 != (len = inputStream.read())) {
        outputStream.write(len);
    }
} catch (IOException e) {
    e.printStackTrace();
}

http://www.kler.cn/a/578927.html

相关文章:

  • IntelliJ IDEA 2021版创建springboot项目的五种方式
  • javaEE初阶————多线程进阶(1)
  • 大整数加法(信息学奥赛一本通-1168)
  • NoSQL数据库系统Cassandra学习笔记
  • Prompt engineering设计原则
  • 力扣HOT100之哈希:128. 最长连续序列
  • 学习LED驱动知识(二)
  • 神经网络中梯度计算求和公式求导问题
  • 【MySQL】索引(页目录、B+树)
  • Go学习笔记:go 操作mysql和Redis
  • OkHttp:工作原理 拦截器链深度解析
  • Git基础之基础概念
  • 消息队列MQ(RabbitMQ)
  • 2025-3-9哈弗曼树
  • OPENGLPG第九版学习 -颜色、像素和片元 PART1
  • python flask
  • 跨越时空的对话:图灵与GPT-4聊AI的前世今生
  • 在MATLAB环境中,对矩阵拼接(Matrix Concatenation)的测试
  • 【 <一> 炼丹初探:JavaWeb 的起源与基础】之 JSP 中的内置对象:request、response、session 的使用示例
  • JVM 的OOM问题