Unn*_*ris 22 java denial-of-service readline bufferedreader
目前我有以下代码供阅读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();
}
Run Code Online (Sandbox Code Playgroud)
该代码与安全团队进行了审核,收到了以下评论:
BufferedReader.readLine 易受DOS(拒绝服务)攻击(无限长度的线,含有巨大的文件不换行/回车)
StringBuilder变量的资源耗尽(包含大于可用内存的数据的文件的情况)
以下是我能想到的解决方案:
创建readLinemethod(readLine(int limit))的备用实现,它检查no.读取的字节数,如果超过指定的限制,则抛出自定义异常.
逐行处理文件而不完整加载文件.(纯非Java解决方案:))
请建议是否有任何现有的库实现上述解决方案.还建议任何替代解决方案,其提供比建议的更稳健或更方便实施.虽然性能也是一项主要要求,但安全性是第一位的.
Sub*_*has 40
您希望避免各种DOS攻击(在线路上,文件大小等).但是在函数的最后,你试图将整个文件转换成一个String!假设您将行限制为8 KB,但如果某人向您发送包含两个8 KB行的文件会发生什么?行读取部分将通过,但是当最终将所有内容组合成一个字符串时,String将阻塞所有可用内存.
因此,自从最终将所有内容转换为单个字符串后,限制行大小无关紧要,也不安全.您必须限制文件的整个大小.
其次,你基本上要做的是,你正试图以块的形式读取数据.所以你正在BufferedReader逐行使用和阅读它.但是你要做的是什么,以及你最终想要的是什么 - 是一种逐段阅读文件的方式.为什么不一次读取2 KB,而不是一次读取一行?
BufferedReader - 按名称 - 里面有一个缓冲区.您可以配置该缓冲区.假设您创建的BufferedReader缓冲区大小为2 KB:
BufferedReader reader = new BufferedReader(..., 2048);
Run Code Online (Sandbox Code Playgroud)
现在,如果InputStream您传递BufferedReader的数据有100 KB的数据,BufferedReader则会自动将其读取为2 KB.因此它将读取流50次,每次2 KB(50x2KB = 100 KB).同样,如果BufferedReader使用10 KB的缓冲区大小创建,它将读取输入10次(10x10KB = 100 KB).
BufferedReader已经完成了以块为单位读取文件的工作.因此,您不希望在其上方添加额外的逐行图层.只关注最终结果 - 如果你的文件结尾太大(>可用内存) - 你最后怎么把它转换成一个String?
一种更好的方法是将事物作为一种传递方式CharSequence.这就是Android的功能.在整个Android API中,您将看到它们CharSequence随处返回.由于StringBuilder也是它的子类CharSequence,因此Android将在内部使用基于输入的大小/性质的a String或a StringBuilder或其他一些优化的字符串类.因此,StringBuilder一旦您阅读了所有内容,您就可以直接返回该对象,而不是将其转换为String.这对大数据更安全.StringBuilder还在其中维护相同的缓冲区概念,并在内部为大字符串分配多个缓冲区,而不是一个长字符串.
整体而言:
使用Apache Commons IO,以下是如何将数据从a读取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;
Run Code Online (Sandbox Code Playgroud)
使用BoundedInputStream从Apache的百科全书IO库.你的工作变得容易多了.
以下代码将执行您想要的操作:
public static String getContentFromInputStream(InputStream inputStream) {
inputStream = new BoundedInputStream(inputStream, <number-of-bytes>);
// Rest code are all same
Run Code Online (Sandbox Code Playgroud)
您只需InputStream用a 包装即可BoundedInputStream指定最大尺寸.BoundedInputStream将负责限制读取到最大尺寸.
或者,您可以在创建阅读器时执行此操作:
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(
new BoundedInputStream(inputStream, <no-of-bytes>)
)
);
Run Code Online (Sandbox Code Playgroud)
基本上我们在这里做的是,我们限制了InputStream图层本身的读取大小,而不是在读取行时这样做.因此,最终得到一个可重用的组件BoundedInputStream,可以限制在InputStream层读取,并且您可以在任何地方使用它.
编辑:添加脚注
编辑2:根据评论添加更新的答案
Gle*_*est 14
基本上有4种方法可以进行文件处理:
基于流的处理(java.io.InputStream模型):可选择在流周围放置一个bufferedReader,迭代并读取流中的下一个可用文本(如果没有文本可用,阻止直到某些文件可用),在阅读时单独处理每段文本(适应各种尺寸的文字)
基于块的非阻塞处理(java.nio.channels.Channel模型):创建一组固定大小的缓冲区(表示要处理的"块"),依次读入每个缓冲区而不会阻塞(nio API委托给本机IO,使用快速O/S级线程),主处理线程在填充后依次选取每个缓冲区并处理固定大小的块,因为其他缓冲区继续异步加载.
零件文件处理(包括逐行处理)(可以利用(1)或(2)隔离或构建每个"零件"):将文件格式分解为具有语义意义的子零件(如果可能的话!行是可能的!),遍历流片段或块并在内存中构建内容,直到下一部分完全构建,一旦构建完成就处理每个部分.
整个文件处理(java.nio.file.Files模型):在一次操作中将整个文件读入内存,处理完整的内容
你应该使用哪一个?
这取决于您的文件内容和您需要的处理类型.
从资源利用效率的角度(从最好到最差)是:1,2,3,4.
从处理速度和效率的角度来看(从最好到最差)是:2,1,3,4.
从易于编程的角度来看(从最好到最差):4,3,1,2.
但是,某些类型的处理可能需要的不仅仅是最小的文本(排除1,可能是2),而某些文件格式可能没有内部部分(排除3).
你正在做4.我建议你转到3(或更低),如果可以的话.
在4以下,只有一种方法可以避免DOS - 在读入内存之前限制大小(或者复制到文件系统).一旦它被读入就太晚了.如果这是不可能的,那么尝试3,2或1.
限制文件大小
通常,文件是通过HTML表单上传的.
如果使用Servlet @MultipartConfig注释上传request.getPart().getInputStream(),则可以控制从流中读取的数据量.此外,request.getPart().getSize()提前返回文件大小,如果它足够小,您可以request.getPart().write(path)将文件写入磁盘.
如果使用JSF上传,那么JSF 2.2(非常新的)有标准的html组件<h:inputFile>(javax.faces.component.html.InputFile),它有一个属性maxLength; 预JSF 2.2实现方式中也有类似的定制的组件(例如战斧具有<t:InputFileUpload>与maxLength属性; PrimeFaces具有<p:FileUpload>与sizeLimit属性).
读取整个文件的替代方法
你的代码使用InputStream,StringBuilder等等,是一种高效的读取整个文件的方法,但不一定是最简单的方式(至少代码行).
当您处理整个文件时,初级/普通开发人员可能会误以为您正在进行有效的基于流的处理 - 因此请包含适当的注释.
如果您想要更少的代码,可以尝试以下方法之一:
List<String> stringList = java.nio.file.Files.readAllLines(path, charset);
or
byte[] byteContents = java.nio.file.Files.readAllBytes(path);
Run Code Online (Sandbox Code Playgroud)
但它们需要小心,否则它们在资源使用方面效率低下.如果你使用readAllLines然后将List元素连接成一个单元String,那么你将消耗双倍的内存(对于List元素+连接String).同样,如果你使用readAllBytes,然后编码为String(new String(byteContents, charset)),那么你再次使用"双"内存.因此,最好直接针对List<String>或处理byte[],除非您将文件限制为足够小的大小.
| 归档时间: |
|
| 查看次数: |
38255 次 |
| 最近记录: |