Как безопасно анализировать строки?

#java #string #security #parsing

#java #строка #Безопасность #синтаксический анализ

Вопрос:

Мы знаем, что использование конкатенации строк для формирования SQL-запросов делает программу уязвимой для внедрения SQL. Обычно я обходил это, используя функции параметров, предоставляемые API любого программного обеспечения для баз данных, которое я использую.

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

 Scanner scanner = new Scanner(System.in);
String directoryName = "Bob";
String filePath = null;
String text = "some text";

System.out.print("Enter a file to write to: ");
filePath = scanner.nextLine();

// Write to the file in Bob's personal directory for this program (i.e. Bob/textfile.txt)
FileOutputStream file = new FileOutputStream(directoryName   "/"   filePath);
file.write(text.getBytes());
  

Является ли предпоследняя строка уязвимостью? Если да, то как можно сделать программу более безопасной (особенно в Java, C и C #)? Один из способов — проверить входные данные на наличие escape-символов. Что-нибудь еще?

Комментарии:

1. @HovercraftFullOfEels: я думаю, это тот термин, который я искал. Официальные руководства по Java, похоже, предполагают, что подготовленные операторы являются особыми для SQL. Могут ли они применяться в общем контексте?

Ответ №1:

Самое простое решение здесь — иметь белый список допустимых символов. Изменение исходного кода (для включения соглашений Java, поскольку вы сказали, что вы новичок …)

 package javawhitelist;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JavaWhiteListExample {

    public static void main(String[] args) throws IOException {

        Scanner scanner = new Scanner(System.in); 
        String directoryName = "Bob"; 
        String filePath = null; 
        FileWriter stream = null;
        String text = "some text";  
        System.out.print("Enter a file to write to: "); 
        filePath = scanner.nextLine();  
        String WHITELIST = "[^0-9A-Za-z] ";
        Pattern p = Pattern.compile(WHITELIST);
        Matcher m = p.matcher(filePath);

        //You need to do m.find() because m.matches() looks for COMPLETE match
        if(m.find()){ 
            //reject input.
            System.out.println("Invalid input.");
        }else{
            // Write to the file in Bob's home directory (i.e. Bob/textfile.txt) 
            try{
                File toWrite = new File(directoryName   File.separator   filePath);

                if(toWrite.canWrite()){
                    stream = new FileWriter(toWrite);
                    stream.write(text);
                }   
            }catch(FileNotFoundException e){
                e.printStackTrace();
            }catch(IOException e){
                e.printStackTrace();
            }finally{
                if(stream != null){
                    stream.close();
                }
            }

        }
    }
}
  

Реализация по умолчанию любой JVM выполняется со всеми правами доступа пользователя. Использование File.canWrite() метода поможет гарантировать, что пользователь не будет записывать поверх файла, на который у него нет разрешения. НАИБОЛЕЕ безопасным решением (с ТОЧНЫМ указанием, куда будет отправлен файл) будет использование
com.sun.security.auth.module.UnixSystem.getName()
и используйте это для построения
/home/$USER
часть имени каталога. Некоторые решения могут подсказать вам использовать
System.getProperty("user.home"):
или что-то подобное, но они зависят от легко изменяемых переменных среды.

Я старался быть тщательным, надеюсь, это поможет.

Комментарии:

1. Это было действительно довольно тщательно, спасибо за объяснение.

2. Я никогда раньше не использовал классы Pattern и Matcher, поэтому мне нужно это переварить. Будучи новичком в Java и видя, что почти для каждой ситуации есть класс, я надеялся, что будет стандартное решение для фильтрации пользовательского ввода.

3. На любом языке, с которым я когда-либо работал, проверка пользовательского ввода всегда выполнялась с использованием регулярных выражений. Решение 1 всегда находится в белом списке, решение 2 — в черном списке. owasp.org/index.php/Category:OWASP_Java_Project Для получения более подробного материала, относящегося к Java.

4. О, и если мой ответ лучший среди того, что вы видели здесь, не стесняйтесь нажать «принять». (флажок.)

Ответ №2:

Любой ввод от пользователя следует считать «подозрительным»

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

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

Так что да, строка:

 FileOutputStream file = new FileOutputStream(directoryName   "/"   filePath);
  

действительно ли это уязвимость

Эта концепция применима и к C

Комментарии:

1. Ну, концепция применима абсолютно к любому языку программирования 😉

2. Java обычно запускается с пользовательскими разрешениями, поэтому она должна иметь возможность писать только там, где разрешено пользователю. Однако существует очень популярная настольная ОС, которая до недавнего времени по умолчанию позволяла пользователям делать что угодно.

Ответ №3:

Этот вопрос сильно отличается от проблемы с внедрением SQL. В проблеме с внедрением SQL злоумышленно введенные параметры могут использоваться для выполнения команд в привилегированном контексте безопасности, поскольку пользователь базы данных, под которым выполняются команды, обычно имеет карт-бланш на запись в строки в базе данных.

В приведенном вами примере ключевым вопросом является «от имени какого пользователя будет выполняться Java-код?». Если вы выполняете этот код, например, как CGI-скрипт, то любой файл или каталог, в который пользователь процесса веб-сервера может записать, уязвим. Если вы просто запускаете это из командной строки, на самом деле защита файлов / каталогов, в которые пользователь не должен иметь возможности записывать, зависит от операционной системы (а не от кода Java).

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

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

PS — Обычно вы не хотите предполагать, что «/» является вашим разделителем каталогов. Java предоставляет константу File.separator для этой цели.

Комментарии:

1. File.separator это технически правильный способ указать разделитель файлов. Однако уровень ввода-вывода автоматически изменит / и , чтобы они были правильными для базовой платформы. Хотя это скорее проблема стиля, я тоже предпочитаю File.separator .

Ответ №4:

Если вы видите подобный код, запустите.

Некоторые проблемы:

Атаки обхода каталогов: традиционно файловые системы путают пользовательский интерфейс и API. У нас есть этот язык с путями к файлам, но нет способа четко указать конкретные имена. В типичных операционных системах .. это позволит перемещаться вверх по структуре каталогов (не обязательно в начале пути). Также обратите внимание, что в качестве разделителя каталогов может использоваться более одного символа.

Ссылки: ссылки на файловую систему в каталоге могут ссылаться на другие места.

Нулевые символы: если вы попытаетесь указать суффикс, например, в качестве расширения файла, нулевой байт сократит путь.

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

Существующие файлы: что происходит, если файл существует?

Использование диска: если данные предоставлены пользователем, вы проверяете, не слишком ли они велики?

Итак, старайтесь избегать использования имен файлов, созданных посторонними. Если вам действительно нужно, я предлагаю применить жесткий белый список символов.

Ответ №5:

Поскольку в имени файла есть несколько зарезервированных символов, вы можете выполнить поиск по пути, указанному пользователем. Вы также можете проверить, что строка не содержит ../ :/ и т.д., Что позволило бы пользователю изменять путь к «домашнему каталогу». Я бы рекомендовал использовать регулярное выражение для проверки заданной строки перед ее использованием. Если проверка не удалась, просто прервите операцию и сообщите пользователю, что что-то не так с вводом, вместо того, чтобы пытаться это исправить.

Структура файла может быть довольно сложной, если человек не знает, что он делает, и символы — не единственная проблема, как упоминалось в других ответах. Допустимые имена файлов различаются в разных файловых системах. Старые FAT-системы имеют ограничение не более 8 символов, в то время как новые NTFS, используемые Windows, допускают до 255 символов.

Обновленный ответ для большей ясности.

Комментарии:

1. Поэтому я полагаю, что полезной проверкой было бы проверить наличие косых и обратных косых черт и отклонить такой ввод. Будут ли экранирующие символы, такие как backspace, также представлять угрозу безопасности? Существует ли какой-то класс проверки ввода в Java и C #, который обрабатывает это элегантным способом?

2. И у нас уже есть ошибка безопасности в вашем коде. Потому что НАЧИНАТЬ с ../ недостаточно. foo/../../../privateStuff это совершенно правильный путь. Так и есть C:/Windows . И в зависимости от того, как вы это исправите (просто ../ замените ничем?) у вас возникают другие проблемы, например. ..././doh . Тогда в NTFS есть такие вещи, как реклама (не знаю, разрешает ли это java?) и так далее. Так что действительно лучшая идея — НЕ исправлять это самостоятельно.

3. На самом деле я не предлагал устранять проблему, а скорее сообщал пользователю, если проверка не удалась. И, конечно, проверки начала строки недостаточно; моя вина.

Ответ №6:

Вы можете получить каталог пользователя с System.getProperty("user.home") помощью . Если ваша программа выполняется под этим пользователем, и права пользователей управляются правильно, проблем не ожидается. Также вы можете получить символ разделителя пути с другим свойством — file.separator . И, наконец, есть методы File.canRead() и File.canWrite() .

Комментарии:

1. Спасибо, я этого не знал. Но на самом деле я не говорил о пользовательских каталогах, созданных ОС. Я создаю небольшую программу, в которой, если вы входите в систему как Bob, все данные, относящиеся к Bob, хранятся в C:SomeFolderBob поэтому я не могу пользоваться функциями управления правами, предоставляемыми операционной системой.

2. Затем используйте регулярные выражения для имен файлов. regexlib.com/Search.aspx?k=file name

3. Этот веб-сайт выглядит как ОЧЕНЬ полезный. В течение всех лет, когда мне приходилось иметь дело с регулярным выражением, я хотел бы, чтобы я столкнулся с этим. Спасибо.

4. Я был просто ленив, чтобы написать регулярное выражение для имени файла самостоятельно сейчас 🙂