java - 使用 Java 读取文件或流的最可靠方法(防止 DoS 攻击)

标签 java bufferedreader readline denial-of-service

目前我有以下代码用于阅读 InputStream .我将整个文件存储到 StringBuilder变量并在之后处理此字符串。

public static String getContentFromInputStream(InputStream inputStream)
// public static String getContentFromInputStream(InputStream inputStream,
// int maxLineSize, int maxFileSize)
{

    StringBuilder stringBuilder = new StringBuilder();
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
    String lineSeparator = System.getProperty("line.separator");
    String fileLine;

    boolean firstLine = true;
    try {
        // Expect some function which checks for line size limit.
        // eg: reading character by character to an char array and checking for
        // linesize in a loop until line feed is encountered.
        // if max line size limit is passed then throw an exception
        // if a line feed is encountered append the char array to a StringBuilder
        // after appending check the size of the StringBuilder
        // if file size exceeds the max file limit then throw an exception

        fileLine = bufferedReader.readLine();

        while (fileLine != null) {
            if (!firstLine) stringBuilder.append(lineSeparator);
            stringBuilder.append(fileLine);
            fileLine = bufferedReader.readLine();
            firstLine = false;
        }
    } catch (IOException e) {
        //TODO : throw or handle the exception
    }
    //TODO : close the stream

    return stringBuilder.toString();

}

安全团队对代码进行了审查,并收到了以下评论:
  • BufferedReader.readLine容易受到 DOS(拒绝服务)攻击(无限长的行,不包含换行/回车的巨大文件)
  • StringBuilder 的资源耗尽变量(文件包含的数据大于可用内存的情况)

  • 以下是我能想到的解决方案:
  • 创建 readLine 的替代实现方法( readLine(int limit) ),它检查编号。读取的字节数,如果超过指定的限制,则抛出自定义异常。
  • 逐行处理文件而不完全加载文件。 (纯非 Java 解决方案 :) )

  • 请建议是否有任何现有的库可以实现上述解决方案。
    还建议任何替代解决方案,这些解决方案比提议的解决方案更健壮或更便于实现。尽管性能也是一项主要要求,但安全性是第一位的。

    最佳答案

    更新答案

    您想避免各种 DOS 攻击(在线、文件大小等)。但是在函数的最后,您试图将整个文件转换为一个 String !!!假设您将行限制为 8 KB,但是如果有人向您发送一个包含两个 8 KB 行的文件,会发生什么情况?行读取部分将通过,但是当您最终将所有内容组合成一个字符串时,该字符串将阻塞所有可用内存。

    因此,由于最终您将所有内容都转换为一个字符串,因此限制行大小并不重要,也不安全。您必须限制文件的整个大小。

    其次,您基本上要做的是,您正在尝试分块读取数据。所以你正在使用 BufferedReader并逐行阅读。但是你想要做的,以及你最终真正想要的 - 是一种逐个阅读文件的方式。与其一次读取一行,不如一次读取 2 KB?
    BufferedReader - 顾名思义 - 里面有一个缓冲区。您可以配置该缓冲区。假设您创建了一个 BufferedReader缓冲区大小为 2 KB:

    BufferedReader reader = new BufferedReader(..., 2048);
    

    现在如果InputStream您传递给 BufferedReader有 100 KB 的数据,BufferedReader一次将自动读取 2 KB。因此它将读取流 50 次,每次 2 KB (50x2KB = 100 KB)。同样,如果您创建 BufferedReader缓冲区大小为 10 KB 时,它将读取输入 10 次 (10x10KB = 100 KB)。
    BufferedReader已经完成了逐块读取文件的工作。所以你不想在它上面逐行添加额外的一层。只关注最终结果 - 如果最后的文件太大(> 可用 RAM) - 你将如何将其转换为 String在末尾?

    一种更好的方法是将东西作为 CharSequence 传递。 .这就是安卓所做的。在整个 Android API 中,您会看到它们返回 CharSequence到处。自 StringBuilder也是 CharSequence 的子类, Android 将在内部使用 String ,或 StringBuilder或其他一些基于输入大小/性质的优化字符串类。所以你可以直接返回 StringBuilder阅读完所有内容后,对象本身,而不是将其转换为 String .这对于大数据来说会更安全。 StringBuilder里面也保持着相同的缓冲区概念,它会在内部为大字符串分配多个缓冲区,而不是一个长字符串。

    所以总的来说:
  • 限制整体文件大小,因为您将在某个时候处理整个内容。忘记限制或分割线
  • 分块阅读

  • 使用 Apache Commons IO,这里是您从 BoundedInputStream 读取数据的方法成StringBuilder , 拆分为 2 KB 块而不是行:
    // import org.apache.commons.io.output.StringBuilderWriter;
    // import org.apache.commons.io.input.BoundedInputStream;
    // import org.apache.commons.io.IOUtils;
    
    BoundedInputStream boundedInput = new BoundedInputStream(originalInput, <max-file-size>);
    BufferedReader reader = new BufferedReader(new InputStreamReader(boundedInput), 2048);
    
    StringBuilder output = new StringBuilder();
    StringBuilderWriter writer = new StringBuilderWriter(output);
    
    IOUtils.copy(reader, writer); // copies data from "reader" => "writer"
    return output;
    

    原答案

    使用 BoundedInputStream来自 Apache Commons IO图书馆。您的工作变得更加轻松。

    以下代码将执行您想要的操作:
    public static String getContentFromInputStream(InputStream inputStream) {
      inputStream = new BoundedInputStream(inputStream, <number-of-bytes>);
      // Rest code are all same
    

    您只需简单地包裹您的 InputStreamBoundedInputStream并指定最大大小。 BoundedInputStream将负责将读取限制到该最大大小。

    或者您可以在创建阅读器时执行此操作:
    BufferedReader bufferedReader = new BufferedReader(
      new InputStreamReader(
        new BoundedInputStream(inputStream, <no-of-bytes>)
      )
    );
    

    基本上我们在这里做的是,我们将读取大小限制在 InputStream层本身,而不是在阅读行时这样做。所以你最终会得到一个可重用的组件,比如 BoundedInputStream这限制了 InputStream 层的读取,您可以在任何地方使用它。

    编辑:添加脚注

    编辑 2:根据评论添加了更新的答案

    关于java - 使用 Java 读取文件或流的最可靠方法(防止 DoS 攻击),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17084657/

    相关文章:

    java - 如何正确从一对多列表中删除实体?

    java - 如何在android mysql中获取单个列

    python - 是否存在用于读取行然后拆分它们的快速 Python 内置方法?

    macos - 是否可以将多个命令添加到 readline .inputrc 文件中?

    java - 如何返回列表中元素的列表迭代器

    java - Basic Java - 一个内部(嵌套?)类可以访问另一个吗?

    java - 无法存储 ReadLine (bufferedReader) 中的值

    java - 为什么 BufferedReader readLine 读取超过 EOF

    java - 在 Java 中多次读取 BufferedReader

    php - 使用带有 readline 的 PHP 读取用户命令行输入,但 bash 不是默认 shell