FlawOpen

flawopen.com/Teardowns/cve-2026-21589-atlassian-arbitrary-file-access

● CVE-2026-21589 · CVSS v4 9.3 · Критический
Исследования · FlawOpen

CVE-2026-21589: доступ к файлам Atlassian Data Center

Бюллетень Atlassian и сообщение MS-ISAC от 12 октября подтверждают риск и срочность, но не факт эксплуатации конкретной клиентской системы.[1], [4], [5], [10]

💡 Простыми словами (ELI5)

Представьте библиотеку, где посетитель называет адрес полки. Он не может осматривать все полки, но если уже знает место конкретной книги, ошибка проверки границ может заставить сотрудника достать её за пределами читального зала. Исправление удерживает каждый разрешённый путь внутри допустимой области и обновляет соответствующий продукт.[1], [4]

Ключевые понятия и термины

Произвольный доступ к файлам
Запрос получает доступ к файлам за пределами ожидаемой области. Atlassian описывает доступ к отдельным файлам в веб-каталоге, а не ко всей файловой системе.[1], [4]
Обход пути
Компоненты пути пересекают границу разрешённого каталога. Мера Atlassian блокирует маркеры обхода и их кодированные варианты в URL.[1], [4]
Data Center
Модель самостоятельного размещения продуктов Atlassian. В уведомлении указаны восемь продуктов; по данным Atlassian, Cloud-продукты исправлены.[1]
Известный путь
Злоумышленнику заранее нужны точное имя и путь к файлу; уведомление подчёркивает, что уязвимость не перечисляет каталоги.[1]
CVSS 4.0
Atlassian оценивает уязвимость как критическую: CVSS 4.0 — 9,3 по собственной оценке компании.[1]
Router.unescapeSlashes()
Общая библиотека преобразует закодированный разделитель :: обратно в / до обработки ресурса; эта граница декодирования важна для анализа watchTowr.[1], [4]
ResourceServingHelpers
Вспомогательная функция разрешает имя ресурса и передаёт оставшийся относительный путь в createResourceWithRelativePath().[1], [4]
Цепочка учётных данных Crowd
Условный следующий этап: доступный файл свойств Crowd может раскрыть пароль приложения; возможность его использования зависит от сети и прав.[4], [5]
CWE-22
Редакционная классификация FlawOpen для уязвимости обхода пути: путь недостаточно ограничен заданным каталогом. Atlassian не присваивает CWE в своем уведомлении.[6]

Связи со стандартами

  • Концептуальное соответствие FlawOpen для MITRE CWE; ни поставщик, ни запись CVE не присваивали этот идентификатор слабого места данной исследовательской демонстрации. CWE-22

Анализ первопричины

Анализ watchTowr общей библиотеки atlassian-plugins-webresource выявил границу декодирования путей. Router.unescapeSlashes() заменяет :: на /; маршруты ресурсов применяют функцию к компонентам пути из запроса до вызова обработчиков ресурсов. Последующий поиск разбивает имя ресурса и передаёт оставшийся путь в createResourceWithRelativePath(), где альтернативное представление разделителя может обойти проверку traversal. Исследователи продемонстрировали чтение файлов внутри контекста приложения Tomcat в Jira, Confluence и Bitbucket; Atlassian указывает восемь затронутых продуктов. В отчёте сказано, что проверенный traversal не выходил за контекст Tomcat. В условной цепочке доступный для чтения WEB-INF/classes/crowd.properties может раскрыть пароль приложения Crowd в открытом виде; последствия зависят от конфигурации и доступности Crowd. BleepingComputer сообщает, что датчики Previdian зафиксировали попытки через несколько часов после публикации технического разбора и PoC. Это попытки на honeypot-сенсорах, а не доказательство успешного доступа к системам клиентов. Уязвимые фрагменты Java ниже опубликованы watchTowr. Защитный пример — общий шаблон ограничения пути, а не полный исходный код исправления Atlassian; установите официальный патч каждого продукта.[1], [4], [5]

Пошаговый сценарий атаки

Шаг 1

Общий декодер преобразует путь

Маршрутизатор веб-ресурсов Atlassian заменяет альтернативный разделитель :: на / перед выдачей ресурса. Такое декодирование входит в обычную обработку ресурсов.[1], [4]

Шаг 2

Поиск ресурса получает декодированный путь

Вспомогательная функция разбирает компоненты и передаёт остаток относительного пути фабрике ресурсов. Альтернативный разделитель может обойти предположения проверки traversal.[4]

Шаг 3

Становятся доступны файлы внутри контекста приложения

watchTowr продемонстрировала это в Jira, Confluence и Bitbucket. Согласно отчёту, тестовый путь не выходил за контекст Tomcat; это не означает доступ к любым файлам хоста.[1], [4]

Шаг 4

Цепочка идентификации зависит от конфигурации

Если доступная конфигурация Crowd содержит пароль приложения, а Crowd принимает его по сети, могут стать доступны функции управления идентификацией. Важны сетевые списки доступа и настройки.[4], [5]

Шаг 5

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

12 октября в бюллетене MS-ISAC Центра интернет-безопасности сообщалось о случаях эксплуатации CVE-2026-21589 в реальных условиях. В бюллетене не названы пострадавшие и не опубликованы материалы инцидентов, поэтому это следует считать сообщением MS-ISAC об эксплуатации, а не подтверждением доступа к системе конкретного клиента. В бюллетене Atlassian от 5 октября говорилось, что расследование компании не выявило признаков эксплуатации в продуктах Cloud; компания также сообщила, что они исправлены. Этот вывод относился к Cloud и не противоречит сообщению MS-ISAC об эксплуатации уязвимости. Ни одно из заявлений не устанавливает, что произошло с конкретным экземпляром.[1], [10]

Исходный код: Уязвимый vs Защищённый вариант

Уязвимый фрагмент показывает опубликованное watchTowr преобразование пути в исходном коде затронутой библиотеки web-resource; это не запрос для эксплуатации. Нижний пример демонстрирует общий способ защиты на Java, а не патч Atlassian: обход от доверенного корня через дескрипторы SecureDirectoryStream, отказ от символических ссылок на каждом компоненте и ограничение объёма чтения. Вызывающий код должен передать относительный путь, декодированный ровно один раз, и SecureDirectoryStream, открытый от доверенного каталога. Некоторые файловые провайдеры не поддерживают SecureDirectoryStream; в этом случае безопасно откажитесь от операции. Установите исправление поставщика для конкретного продукта. Этот пример предполагает доверенное дерево ресурсов, каталоги и файлы которого не могут изменять недоверенные пользователи и которое содержит только обычные файлы; тип файла здесь отдельно не проверяется. Ограничение байтов и ограниченная обработка отсутствия прогресса сдерживают чтение, но это не универсальная процедура выдачи файлов. Вызывающий код должен передать относительный путь, декодированный ровно один раз, и сохранить открытый доверенный корневой поток. Поддержка со стороны файлового провайдера необязательна; безопасно откажитесь от операции, если SecureDirectoryStream недоступен.[4], [7]

УЯЗВИМАЯ РЕАЛИЗАЦИЯ
public static String unescapeSlashes(String string) {
    return string.replaceAll("::", "/");
}
БЕЗОПАСНЫЙ ИСПРАВЛЕННЫЙ ВАРИАНТ
static byte[] readUnder(SecureDirectoryStream<Path> root, Path relative, int maxBytes) throws IOException {
    if (relative.isAbsolute() || relative.getNameCount() == 0 || maxBytes < 0) {
        throw new SecurityException("Expected a relative path and non-negative limit");
    }
    List<SecureDirectoryStream<Path>> opened = new ArrayList<>();
    SecureDirectoryStream<Path> parent = root;
    try {
        for (int i = 0; i < relative.getNameCount() - 1; i++) {
            Path part = relative.getName(i);
            if (part.toString().isEmpty() || part.toString().equals(".") || part.toString().equals("..")) {
                throw new SecurityException("Invalid path component");
            }
            parent = parent.newDirectoryStream(part, LinkOption.NOFOLLOW_LINKS);
            opened.add(parent);
        }
        Path leaf = relative.getFileName();
        if (leaf.toString().isEmpty() || leaf.toString().equals(".") || leaf.toString().equals("..")) {
            throw new SecurityException("Invalid file name");
        }
        try (SeekableByteChannel channel = parent.newByteChannel(
                leaf, Set.of(StandardOpenOption.READ, LinkOption.NOFOLLOW_LINKS))) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ByteBuffer buffer = ByteBuffer.allocate(8192);
            int total = 0;
            int zeroReads = 0;
            while (true) {
                int count = channel.read(buffer);
                if (count == -1) break;
                if (count == 0) {
                    if (++zeroReads >= 3) throw new IOException("Resource read made no progress");
                    continue;
                }
                zeroReads = 0;
                if (count > maxBytes - total) throw new IOException("Resource exceeds size limit");
                out.write(buffer.array(), 0, count);
                total += count;
                buffer.clear();
            }
            return out.toByteArray();
        }
    } finally {
        for (int i = opened.size() - 1; i >= 0; i--) opened.get(i).close();
    }
}

Чек-лист по защите системы для инженеров

Источники