Самый надежный способ чтения файла или потока с помощью Java (для предотвращения DoS-атак)

В настоящее время у меня есть приведенный ниже код для чтения 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();

}

код пошел на обзор с командой безопасности, и были получены следующие комментарии:

  1. BufferedReader.readLine восприимчив к атакам DOS (отказ в обслуживании) (строка бесконечной длины, огромный файл, не содержащий linefeed/cariage retrun)

  2. ресурс исчерпание для переменной StringBuilder (случаи, когда файл, содержащий данные больше, чем avaialble памяти)

Ниже приведены решения, которые я мог бы придумать:

  1. создайте альтернативную реализацию метода readLine (readLine(int limit)), который проверяет нет. считанных байтов и если он превышает заданное ограничение, создайте пользовательское исключение.

  2. обработка файла строка за строкой без загрузки файла в целом. (чистое решение не java:))

пожалуйста, предложите, есть ли какие-либо существующие библиотеки, которые реализуют вышеуказанные решения. Также предлагаем любые альтернативные решения, которые предлагают большую надежность или более удобны в реализации, чем предлагаемые. Хотя производительность также является основным требованием, безопасность на первом месте.

спасибо заранее.

8 ответов


Обновил Ответ

вы хотите избежать всевозможных DOS-атак (на строках, по размеру файла и т. д.). Но в конце функции вы пытаетесь преобразовать весь файл в один String!!! Предположим, что вы ограничиваете строку 8 КБ, но что произойдет, если кто-то отправит вам файл с двумя строками 8 КБ? Часть чтения строки пройдет, но когда Вы наконец объедините все в одну строку, строка задушит всю доступную память.

так поскольку, наконец, вы конвертируете все в одну строку, ограничение размера строки не имеет значения и не безопасно. Вы должны ограничить весь размер файла.

во-вторых, то, что вы в основном пытаетесь сделать, это пытаться читать данные кусками. Итак, вы используете BufferedReader и читать его построчно. Но то, что вы пытаетесь сделать, и то, что вы действительно хотите в конце - это какой-то способ чтения файла по частям. Вместо того, чтобы читать по одной строке за раз, почему бы не прочитать 2 КБ за раз?

BufferedReader - по его имени-имеет буфер внутри него. Вы можете настроить этот буфер. Допустим, вы создаете BufferedReader С размером буфера 2 КБ:

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

теперь, если InputStream что вы передаете BufferedReader имеет 100 КБ данных, BufferedReader автоматически прочитает его 2 КБ одновременно. Таким образом, он будет читать поток 50 раз, по 2 КБ каждый (50x2KB = 100 KB). Аналогично, если вы создаете BufferedReader С размером буфера 10 КБ, он прочитает входной сигнал 10 раз (10кс10кб = 100 КБ.)

BufferedReader уже выполняет работу по чтению вашего файла chunk-by-chunk. Таким образом, вы не хотите добавлять дополнительный слой по строкам над ним. Просто сосредоточьтесь на конечном результате-если ваш файл в конце слишком большой (> доступный RAM) - как вы собираетесь его конвертировать в String в конце?

один лучший способ - просто передать вещи как CharSequence. Это то, что делает Android. На протяжении Android API, вы увидите, что они возвращаются CharSequence везде. С StringBuilder также является подклассом CharSequence, Android будет внутренне использовать либо String или StringBuilder или какой-либо другой оптимизированный класс строк, основанный на размере/характере ввода. Таким образом, вы могли бы напрямую вернуть StringBuilder объект сам по себе, как только вы прочитали все, а не преобразование его в String. Это было бы безопаснее против больших данных. StringBuilder также поддерживает ту же концепцию буферов внутри него, и он будет внутренне выделять несколько буферов для больших строк, а не один длинная строка.

в общем:

  • ограничить общий размер файла, так как вы собираетесь иметь дело со всем контентом в какой-то момент. Забудьте об ограничении или разделении строк
  • читать кусками

используя Apache Commons IO, вот как вы будете читать данные из BoundedInputStream на StringBuilder, разбиение на 2 КБ блоков вместо строк:

// 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

вы просто просто оберните свой InputStream С BoundedInputStream и вы указываете максимальный размер. BoundedInputStream позаботится об ограничении чтения до этого максимального размера.

или вы можете сделать это при создании читателя:

BufferedReader bufferedReader = new BufferedReader(
  new InputStreamReader(
    new BoundedInputStream(inputStream, <no-of-bytes>)
  )
);

в основном то, что мы делаем здесь is, мы ограничиваем размер чтения в InputStream слой сам, а не делать это при чтении строк. Таким образом, вы получаете многоразовый компонент, такой как BoundedInputStream что ограничивает чтение на слое InputStream, и вы можете использовать это везде, где хотите.

Edit: добавлена сноска

Edit 2: Добавлен обновленный ответ на основе комментариев


есть в основном 4 способа обработки файлов:

  1. Потоковая Обработка (the java.io.InputStream модель): при необходимости поместите bufferedReader вокруг потока, повторите и прочитайте следующий доступный текст из потока (если текст недоступен, блок до тех пор, пока некоторые не станут доступными), обрабатывайте каждый кусок текста независимо, как он читается (питание для широко варьирующихся размеров текста кусочки)

  2. Chunk На Основе Неблокирующей Обработки (the java.nio.channels.Channel модель): создайте набор буферов фиксированного размера (представляющих" куски " для обработки), считывайте в каждый из буферов по очереди без блокировки (NIO API делегирует собственный IO, используя быстрые потоки уровня O/S), ваш основной поток обработки выбирает каждый буфер по очереди после его заполнения и обрабатывает фиксированный размер куска, поскольку другие буферы продолжают асинхронно обрабатываться нагруженный.

  3. обработка файлов деталей (включая построчную обработку) (может использовать (1) или (2) для изоляции или создания каждой "части"): разбейте формат файла на семантически значимые подчасти (если это возможно! можно было бы прорваться в строй!), итерация через части потока или куски и наращивание контента в памяти unitl следующая часть полностью построена, обрабатывать каждую часть, как только это построенный.

  4. Вся Обработка Файла (the java.nio.file.Files модель): прочитайте весь файл в память за одну операцию, обработайте полное содержимое

какой из них следует использовать?
Это зависит от вашего содержимого файла и типа обработки, которую вы требуете.
С точки зрения эффективности использования ресурсов (от наилучшего к худшему): 1,2,3,4.
С точки зрения скорости и эффективности обработки (best к худшему) это: 2,1,3,4.
С точки зрения простоты программирования (от лучшего к худшему): 4,3,1,2.
Однако для некоторых типов обработки может потребоваться больше, чем самый маленький фрагмент текста (исключая 1 и, возможно, 2), а некоторые форматы файлов могут не иметь внутренних частей (исключая 3).

Вы делаете 4. Я предлагаю вам перейти на 3 (или ниже), если вы можете.

под 4, есть только один способ избежать DOS-ограничить размер, Прежде чем он будет считан в память, (или скопировать в файловую систему). Слишком поздно, когда все прочитано. Если это невозможно, попробуйте 3, 2 или 1.

Ограничение Размера Файла

часто файл загружается через HTML-форму.

при загрузке с помощью сервлета @MultipartConfig аннотации и request.getPart().getInputStream(), у вас есть контроль над тем, сколько данных читать из потока. Кроме того,request.getPart().getSize() возвращает размер файла заранее, и если он достаточно мал, вы можете сделать request.getPart().write(path) написать файл на диск.

если загрузка с помощью JSF, то JSF 2.2 (очень Новый) имеет стандартный html-компонент <h:inputFile> (javax.faces.component.html.InputFile), который имеет атрибут для maxLength; реализации pre-JSF 2.2 имеют аналогичные пользовательские компоненты (например, Tomahawk имеет <t:InputFileUpload> С maxLength атрибут; PrimeFaces имеет <p:FileUpload> С ).

Альтернативы для чтения всего файла

ваш код, который использует InputStream, StringBuilder и т. д., - Это эффективное способ прочитать весь файл, но не обязательно простой путь (наименьшие строки кода).

младшие / средние разработчики могут получить неправильное представление о том, что вы делаете эффективную потоковую обработку, когда вы обрабатываете весь файл-поэтому включите соответствующие комментарии.

если вы хотите меньше кода, Вы можете попробовать одно из следующих:

 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[], если вы не ограничиваете свои файлы до достаточно небольшого размера.


вместо readLine используйте read, который читает заданное количество символов.

в каждом цикле проверьте, сколько данных было прочитано, если это больше, чем определенное количество, больше, чем максимум ожидаемого ввода, остановите его и верните ошибку и зарегистрируйте ее.


дополнительно, я заметил, что вы не закрыли свой BufferedInputStream. Вы должны закрыть свой BufferedReader finally блок, поскольку это подвержено утечкам памяти.

...
} catch (IOException e) {
        // throw or handle the exception
    } finally{
       bufferedReader.close();
}

нет необходимости явно закрывать new InputStreamReader(inputStream) поскольку это будет автоматически закрыто при вызове для закрытия класса обертывания bufferedReader


я столкнулся с аналогичной проблемой при копировании огромного двоичного файла (который вообще не содержать символ новой строки). выполнение readline () приводит к чтению всего двоичного файла в одну строку, вызывающую OutOfMemory на кучу пространства.

вот простая альтернатива JDK:

public static void main(String[] args) throws Exception
{
    byte[] array = new byte[1024];
    FileInputStream fis = new FileInputStream(new File("<Path-to-input-file>"));
    FileOutputStream fos = new FileOutputStream(new File("<Path-to-output-file>"));
    int length = 0;
    while((length = fis.read(array)) != -1)
    {
        fos.write(array, 0, length);
    }
    fis.close();
    fos.close();
}

Примечание:

  • приведенный выше пример копирует файл, используя буфер байт 1K. Однако, если вы делаете эту копию по сети, вы можете настроить размер буфера.

  • Если вы хотите использовать FileChannel или библиотеки, такие как Commons IO, просто убедитесь, что реализация сводится к чему-то, как и выше


Я не могу придумать другого решения, кроме Apache Commons Io FileUtils. Его довольно просто с классом FileUtils, так как так называемая DOS-атака не будет поступать непосредственно с верхнего слоя. Чтение и запись файла очень просты, как вы можете сделать это только с одной строкой кода, как

String content =FileUtils.readFileToString(new File(filePath));

вы можете узнать больше об этом.


есть класс EntityUtils под Apache httpCore. Используйте метод getString () этого класса, чтобы получить строку из содержимого ответа.


это работает для меня без каких-либо проблем.

    char charArray[] = new char[ MAX_BUFFER_SIZE ];
    int i = 0;
    int c = 0;
    while((c = br.read()) != -1 && i < MAX_BUFFER_SIZE) {
        char character = (char) c;
        charArray[i++] = character;
   }
   return Arrays.copyOfRange(charArray,0,i);