首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >使用Java读取文件或流的最健壮的方法(防止DoS攻击)

使用Java读取文件或流的最健壮的方法(防止DoS攻击)
EN

Stack Overflow用户
提问于 2013-06-13 18:22:31
回答 8查看 46.6K关注 1票数 24

目前,我有以下读取InputStream的代码。我将整个文件存储到一个StringBuilder变量中,然后处理这个字符串。

代码语言:javascript
复制
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();

}

该代码与安全团队一起进行了审查,并收到了以下意见:

  1. BufferedReader.readLine容易受到拒绝服务攻击(无限长的行、不包含换行符/回车符的大型文件) StringBuilder变量的return)
  2. Resource耗尽(当包含的数据大于可用内存时)

下面是我能想到的解决方案:

  1. 创建readLine方法(readLine(int limit))的替代实现,该方法检查no。读取的字节数,如果超过指定的限制,则在不加载整个文件的情况下逐行抛出自定义exception.
  2. Process。(纯非Java解决方案:) )

请建议是否有任何现有的库实现了上述解决方案。还建议任何替代解决方案,这些解决方案提供比所提出的解决方案更健壮或更方便实现的解决方案。虽然性能也是一个主要要求,但安全性是第一位的。

EN

回答 8

Stack Overflow用户

回答已采纳

发布于 2013-06-17 15:25:37

更新的答案

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

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

其次,你基本上想要做的是,你试图以块的形式读取数据。因此,您正在使用BufferedReader并逐行阅读它。但你想要做的,也是你最终真正想要的--是一种逐段读取文件的方法。与其一次读取一行,为什么不一次读取2KB呢?

BufferedReader --顾名思义--在它里面有一个缓冲区。您可以配置该缓冲区。假设您创建了一个缓冲区大小为2 KB的BufferedReader

代码语言:javascript
复制
BufferedReader reader = new BufferedReader(..., 2048);

现在,如果您传递给BufferedReaderInputStream包含100KB的数据,BufferedReader将一次自动读取2KB的数据。因此,它将读取流50次,每次2 KB (50x2KB = 100 KB)。类似地,如果使用10KB缓冲区大小创建BufferedReader,它将读取输入10次(10x10KB =100KB)。

BufferedReader已经完成了逐块读取文件的工作。所以你不会想要在它上面逐行添加额外的一层。只要关注最终的结果--如果你最后的文件太大了(>可用内存)--你怎么把它转换成String呢?

一种更好的方法是将事物作为CharSequence传递。这就是Android所做的。在整个Android中,您会看到它们到处都返回CharSequence。由于StringBuilder也是CharSequence的子类,安卓将根据输入的大小/性质在内部使用StringStringBuilder或其他一些优化的字符串类。因此,您可以在读取所有内容后直接返回StringBuilder对象本身,而不是将其转换为String。对于大数据,这样做会更安全。StringBuilder还在其内部维护了相同的缓冲区概念,并且它将在内部为大型字符串分配多个缓冲区,而不是一个长字符串。

所以总而言之:

  • 限制了整个文件的大小,因为您将在某个时候处理整个内容。忘记限制或拆分
  • Read in chunks

的行

使用Apache Commons IO,您将如何将数据从BoundedInputStream读取到StringBuilder中,将数据拆分为2 KB的块而不是行:

代码语言:javascript
复制
// 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;

原始答案

使用Apache Commons IO库中的BoundedInputStream。你的工作变得容易多了。

下面的代码将执行您想要的操作:

代码语言:javascript
复制
public static String getContentFromInputStream(InputStream inputStream) {
  inputStream = new BoundedInputStream(inputStream, <number-of-bytes>);
  // Rest code are all same

您只需简单地用BoundedInputStream包装您的InputStream并指定一个最大大小。BoundedInputStream将负责将读取限制到该最大大小。

或者您可以在创建阅读器时执行以下操作:

代码语言:javascript
复制
BufferedReader bufferedReader = new BufferedReader(
  new InputStreamReader(
    new BoundedInputStream(inputStream, <no-of-bytes>)
  )
);

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

编辑:添加脚注

编辑2:添加了基于评论的更新答案

票数 41
EN

Stack Overflow用户

发布于 2013-06-18 17:31:26

基本上有4种方式进行文件处理:

块处理模型(

  1. Stream-Based Processing )( java.io.InputStream模型):可选地在流周围放置一个块,迭代并读取流中的下一个可用文本(如果没有可用文本,则对块进行块处理,直到其中一些可用为止),在读取文本时独立处理每段文本(迎合大小变化很大的文本的非阻塞处理( java.nio.channels.Channel模型):创建一组固定大小的bufferedReader (表示要处理的“块”),),在没有阻塞的情况下依次读入每个缓冲区(nio委托给本机IO,使用快速O/S级线程),一旦缓冲区被填满,您的主处理线程将依次挑选每个缓冲区并处理固定大小的块,因为其他缓冲区继续异步loaded.
  2. Part文件处理(包括逐行处理)(可以利用(1)或(2)来隔离或构建每个“部分”):将文件格式分解为语义上有意义的子部分(如果可能!分成几行是可能的!),遍历流块或块并在内存中构建内容,直到下一部分完全构建完成,在built.
  3. Entire文件处理( java.nio.file.Files模型)中尽快处理每个部分:在一个操作中将整个文件读取到内存中,处理完整的内容

您应该使用哪一个?

这取决于您的文件内容和所需的处理类型。

从资源使用效率的角度来看(从最好到最差)是:1、2、3、4。

从处理速度和效率的角度来看(从最好到最差)是: 2,1,3,4。

从容易编程的角度来看(从最好到最差):4,3,1,2。

但是,某些类型的处理可能需要超过最小的文本部分(排除1,也许2),而某些文件格式可能没有内部部分(排除3)。

如果可以的话,我建议你切换到3(或更低)。

在4下,只有一种方法可以避免DOS --在读入内存之前限制大小(或者复制到你的文件系统)。一旦它被读入就太晚了。如果这不可能,请尝试3、2或1。

限制文件大小

通常,文件是通过HTML表单上传的。

如果使用Servlet @MultipartConfig注释和request.getPart().getInputStream()进行上传,则可以控制从流中读取的数据量。此外,request.getPart().getSize()提前返回文件大小,如果它足够小,您可以执行request.getPart().write(path)将文件写入磁盘。

如果使用JSF上传,那么JSF2.2(非常新)具有标准的html组件<h:inputFile> (javax.faces.component.html.InputFile),该组件具有maxLength属性;JSF2.2之前的实现具有类似的自定义组件(例如,Tomahawk具有带有maxLength属性的<t:InputFileUpload>;PrimeFaces具有带有sizeLimit属性的<p:FileUpload> )。

读取整个文件的替代方案

使用InputStreamStringBuilder等的代码是读取整个文件的高效方式,但不一定是最简单的方式(最少的代码行)。

在处理整个文件时,初级/普通开发人员可能会产生误解,认为您正在进行有效的基于流的处理-因此请包含适当的注释。

如果您想要更少的代码,可以尝试以下方法之一:

代码语言:javascript
复制
 List<String> stringList = java.nio.file.Files.readAllLines(path, charset);

 or 

 byte[] byteContents =  java.nio.file.Files.readAllBytes(path);

但它们需要小心,否则可能在资源使用方面效率低下。如果使用readAllLines,然后将List元素连接到单个String中,那么将消耗两倍的内存( List元素+连接的String)。类似地,如果使用readAllBytes,然后编码为String (new String(byteContents, charset)),那么再次使用“双倍”内存。因此,除非您将文件限制为足够小,否则最好直接针对List<String>byte[]进行处理。

票数 15
EN

Stack Overflow用户

发布于 2013-06-13 19:10:06

不使用readLine,而使用read,它读取给定数量的字符。

在每个循环中,检查读取了多少数据,如果读取的数据量超过一定数量,超过预期输入的最大值,则停止该循环并返回错误并记录该错误。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/17084657

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档