DeepCode предлагает анализ статических программ на основе ИИ для Java, JavaScript и TypeScript, а также Python. Как вы, возможно, знаете, DeepCode использует тысячи репозиториев с открытым исходным кодом для обучения нашего движка. Мы попросили команду разработчиков предоставить некоторую статистику по результатам. Что касается основных предложений нашего движка, мы хотим представить и дать некоторую предысторию в этой серии статей в блоге.

Язык: Java
Дефект: неанитизированный ввод (категория безопасности 1)
Диагностика: необработанный ввод поступает из $ Source $ и используется как путь для записи файлов в java.io.File. Это может привести к уязвимости обхода пути.

Сегодняшний пример спонсируется openjdk / jdk14. Если вы хотите продолжить, перейдите на deepcode.ai, на своей панели инструментов, загрузите проект и следуйте инструкциям.

Фон:

Как внешний злоумышленник, какие векторы вы можете использовать для создания системы? Самым очевидным из них являются входные данные, которые вы ему представляете. Это основная идея целого семейства атак, начиная от внедрения SQL, кросс-сторонних сценариев и заканчивая обходом пути и т. Д. Некоторые из них, такие как SQL-инъекция или XSS, настолько популярны в наши дни, я думаю, что все мы осведомлены и разумны. Тем не менее, мы видим, как они происходят (и не только сохраняются в старом коде). Тем не менее, это верхушка айсберга. Действительно сложно понять (1) различные способы проникновения внешних данных и (2) иногда сложные пути, по которым данные проходят. В Top Finding # 5 мы рассказали об атаке под названием ReDoS. Здесь у нас есть обход пути. Итак, давайте посмотрим:

private boolean extract() throws IOException {
        Path dir = options.extractDir != null ? options.extractDir : CWD;
        try (JmodFile jf = new JmodFile(options.jmodFile)) {
            jf.stream().forEach(e -> {
                try {
                    ZipEntry entry = e.zipEntry();
                    String name = entry.getName();
                    int index = name.lastIndexOf("/");
                    if (index != -1) {
                        Path p = dir.resolve(name.substring(0, index));
                        if (Files.notExists(p))
                            Files.createDirectories(p);
                    }
                    try (OutputStream os = Files.newOutputStream(dir.resolve(name))) {
                        jf.getInputStream(e).transferTo(os);
                    }
                } catch (IOException x) {
                    throw new UncheckedIOException(x);
                }
            });
            return true;
        }
    }

Код, который мы рассматриваем, является частью обработчика Jmod. Jmod - это файлы, очень похожие на jar-файлы, поскольку они являются файлами ZIP. Jmod не исполняются напрямую, а скорее используются во время компиляции или компоновки.
Давайте проследим за потоком приведенного выше кода: В первой строке экземпляр класса Path, который считывается из глобальной переменной. Затем он открывает файл Jmod и начинает чтение из его записей. Как упоминалось выше, Jmod на самом деле представляет собой ZIP-архив файлов и подкаталогов. Итак, наша процедура выполняет одну за другой, проверяет, существует ли путь или каталоги должны быть сгенерированы, и при необходимости генерирует. Затем resolve() используется для генерации окончательного имени файла, и мы копируем данные из источника в приемник.

А теперь подумайте, что произойдет, если имя ZIP-файла включает относительные пути? Что ж, с этим мы можем фактически выйти из данного каталога и буквально перезаписать все, на что мы имеем право. Атака получила название ZIP slip по понятным причинам. Хорошо, теперь вы можете возразить: «Jmods очень близки нашему сердцу. Почему мы не должны им доверять? " и ответ должен быть - по крайней мере, в моем понимании - почему мы должны?

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

String canonicalDirAllowed = ...
File os = new File(destinationDir, e.getName());
String canonicalOSPath = os.getCanonicalPath();
if (!canonicalOSPath.startsWith(canonicalDirAllowed + File.separator)) {
  throw new UncheckedIOException("Entry is outside of the target dir: " + e.getName());
}

DeepCode узнал об этом из других репозиториев. В нарисованном примере используется функция normalize(), которая делает то же самое. Давайте проверим предложение DeepCode.

Вы можете увидеть в двух нижних выделенных красным, как исходный код делал то же самое, что и наш код Jmod. Путь к файлу был взят из ZIP-файла и использовался напрямую. Если вы посмотрите на соответствующие зеленые части, вы можете увидеть, как код был изменен для нормализации, проверить путь и, соответственно, зарегистрировать ошибку. Это хороший рецепт, которому нужно следовать и делать все правильно.

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