Блог / Разборы CVE

CVE-2026-21589: чтение файлов Atlassian, спрятанное за «::»

CVE-2026-21589 — чтение произвольных файлов без аутентификации в восьми продуктах Atlassian Data Center. Разбираю ошибку порядка проверки и как найти её у себя.

Аспирантура по информационной безопасности завершенаВнутривузовская защита пройдена · Итоговая защита диссертации впереди

Одна библиотека, восемь продуктов, один запрос до ваших конфигов

5 октября 2026 года Atlassian выпустила внеплановое исправление CVE-2026-21589 — чтение произвольных файлов без аутентификации с оценкой CVSS 4.0 9.3. [1] Один дефект затрагивает восемь продуктов Data Center и Server: Jira Software, Jira Service Management, Confluence, Bitbucket, Bamboo, Crowd, Crucible и Fisheye. Они ломаются не случайно вместе. У них общая библиотека atlassian-plugins-webresource, и ошибка живёт внутри неё. Бюллетень перечисляет исправленную сборку для каждого продукта; сама библиотека исправлена в 6.0.8. [1][2]

Atlassian аккуратно описывает границы. Атакующий может прочитать «определённые файлы в корневом каталоге веб-приложения» и только если уже знает точный путь, без перечисления содержимого каталогов. [1] Звучит узко. Это не так, и почему именно — суть этой статьи. Я не воспроизводил уязвимость на живом стенде; дальше идёт разбор бюллетеня Atlassian и анализа первопричины от watchTowr, прочитанных 8 октября 2026 года. [1][2]

// atlassian-plugins-webresource, before 6.0.8
escapeSlashes(String string)   { return string.replaceAll("/", "::"); }
unescapeSlashes(String string) { return string.replaceAll("::", "/"); }

// How a web-resource name is handled on the way in:
resourceName                       // attacker-controlled
  -> check for ".." / "/" traversal   // sees "..::", no slash -> looks safe
  -> unescapeSlashes(resourceName)    // "..::"  becomes  "../"
  -> load file relative to web root   // traversal now active

Сервер считал «::» безопасным. Атакующий решал, чем это станет

Вот замысел, которому доверяли разработчики. URL веб-ресурса несёт ключ ресурса и его имя. Прямой слэш в имени запутал бы маршрутизатор, поэтому библиотека экранирует его: каждый «/» на входе превращается в «::», а каждый «::» позже возвращается в «/». [2] Такое кодирование разумно. Ошибка не в кодировании. Ошибка в порядке.

Код сначала проверяет входную строку на обход каталога, пока слэши записаны как «::». Для этой проверки последовательность вида «..::» не содержит слэша, выглядит обычным именем файла и проходит. И только затем библиотека снимает экранирование, и «..::» становится «../». [2] Компонент, признавший запрос безопасным, и компонент, идущий по файловой системе, смотрели на две разные строки. Разрыв между ними принадлежит атакующему.

Почему чтение файла — это захват вашего слоя аутентификации

Пройдём шаг за шагом. Экранированное имя, похожее на обход, проходит проверку, декодируется обратно в настоящие сегменты «../» и попадает в загрузчик, читающий из classpath и корня веб-приложения. [2] По данным watchTowr чтение остаётся внутри контекста Tomcat, выбраться к /etc/passwd нельзя. Для атакующего эта граница почти не важна, потому что нужные файлы уже внутри неё.

Важнее всего WEB-INF/classes/crowd.properties. В продуктах, связанных с Atlassian Crowd, этот файл хранит имя приложения и пароль приложения для обращения к серверу аутентификации. [2] Неаутентифицированное «чтение файла» теперь выдаёт учётные данные к системе, управляющей вашими пользователями. watchTowr проходит ровно этот шаг: читаешь файл, берёшь пароль приложения Crowd — и ты уже не читаешь файлы, ты обращаешься к слою аутентификации как доверенное приложение. [2]

Этот ход и пропустили заголовки. «Читает определённые файлы» и «отдаёт атакующему секреты аутентификации» — одна и та же ошибка, разделённая лишь знанием, какой файл запросить. Предусловие бюллетеня, что нужно знать точный путь, реально, но crowd.properties не секретное место. Это место, где файл лежит всегда. [1][2]

# WEB-INF/classes/crowd.properties  (illustrative keys, not real values)
application.name       = my-confluence
application.password   = <shared secret to the Crowd identity server>
crowd.server.url       = https://id.example.internal/crowd/services/
crowd.base.url         = https://id.example.internal/crowd/

Патч меняет порядок двух шагов. Проверьте, что развернули именно его

Исправление в atlassian-plugins-webresource 6.0.8 закрывает разрыв, заставляя проверку и файловую систему видеть одну строку: имя декодируется в настоящий вид до проверки на обход, а не после. [2] Принцип стоит запомнить по имени. Сначала канонизация, потом валидация. Проверка безопасности что-то значит, только если смотрит на то самое значение, что доходит до опасного приёмника. Схема ниже — этот исправленный порядок как подсказка для ревью, а не копия диффа Atlassian.

Для владельца продукта ловушка в том, чтобы верить номеру версии вместо артефакта. У нескольких продуктов несколько исправленных веток, и образ для отката, стенд или забытый сервер Crucible может по-прежнему грузить библиотеку 6.0.7, пока в заметках написано «пропатчено». [1] Проверяйте jar, который реально загружен, на экземпляре, который реально доступен. Исправленная рабочая копия на ноутбуке разработчика ничего не доказывает о машине в интернете.

// The fix, as a principle (not Atlassian's exact diff):
// decode to the real value FIRST, then validate THAT value.
resourceName
  -> unescapeSlashes(resourceName)    // "..::"  becomes  "../"  up front
  -> check the decoded value for ".." traversal   // now the check sees it
  -> reject, or load file relative to web root

Как я ищу эту форму в коде, не имеющем отношения к Atlassian

Эта ошибка не про Atlassian. Это класс, и класс называется «проверить, затем преобразовать». Как только код декодирует, снимает экранирование, нормализует или канонизирует значение после того, как проверил его на опасность, появляется та же дыра — будь приёмником чтение файла, SSRF или строка SQL. Когда я делаю ревью кода, первое, что я ищу через grep, — шаг декодирования ниже по потоку, чем шаг валидации, над той же переменной.

Шаблон ниже — то, с чего я начинаю. Он находит вызовы декодирования и приёмника; дальше работа ручная. Для каждого совпадения я задаю один вопрос: та ли строка, что смотрела проверка на «..», побайтово доходит до файловой системы? Если между ними стоит преобразование, проверка смотрит на значение, которого к нужному моменту уже нет. Этот вопрос находит ошибку быстрее любой сигнатуры сканера, потому что сканер сверяет имена, а я сверяю порядок.

# Smell to hunt: a decode/normalise step that runs AFTER the path check,
# on the same variable. Start broad, then read each hit by hand.
rg -n "replaceAll|URLDecoder|decode|unescape|normalize|canonical" \
   --glob '!**/test/**'

# The one question that confirms the bug:
#   is the string that reaches the file sink byte-for-byte the string
#   the ".." check inspected? If a transform sits between them, it is vulnerable.

Это ваш пожар? Для части из вас — да, сегодня

Честно о масштабе. Затронуты только самостоятельно размещённые Data Center и Server. Atlassian сообщает, что экземпляры Cloud исправлены и признаков эксплуатации там нет. [1] Если вы целиком в Atlassian Cloud, это не ваш инцидент. Если вы сами хостите любой из восьми продуктов и открыли его в интернет — ваш, и отсчёт уже пошёл.

watchTowr опубликовала первопричину и proof of concept 6 октября и оценивает число уязвимых экземпляров «в диапазоне шести-семи знаков», причём только Confluence — почти 700 000 по их подсчёту. [2] Относите это число к ним, не ко мне. Что не оспаривается — скорость. The Hacker News и BleepingComputer сообщают, что ловушки увидели попытки эксплуатации примерно через два часа после публикации деталей, пока в основном снятие отпечатков и поиск конфигов. [3][4] На 8 октября уязвимость не в каталоге известных эксплуатируемых CISA (KEV) — это говорит, что рано, а не что тихо. [3]

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

Что сделать, прежде чем закрыть эту вкладку

Список ниже — в том порядке, в каком работал бы я. Сначала патч, потому что окно для смягчения здесь измеряется часами, не неделями. [3] Но если вы хостите сами и были открыты, считайте, что пароль приложения Crowd и всё остальное под корнем веб-приложения могли прочитать, и смените их. Патч останавливает следующее чтение. Он не отменяет утечку секрета, который уже ушёл.

Журналы доступа покажут, пытался ли кто-то. Запросы до патча к маршрутам веб-ресурсов с «::», «%3a%3a» или «..» в имени ресурса — вот сигнатура для поиска. Совпадение не доказывает успешного чтения, но говорит, какие экземпляры считать подозрительными и какие секреты менять первыми.

Patch and verify (in this order):
  [ ] List every self-hosted install: Jira, JSM, Confluence, Bitbucket,
      Bamboo, Crowd, Crucible, Fisheye.
  [ ] Confirm the build that is RUNNING, not the one in your notes.
  [ ] Patch each product to its fixed version from the advisory [1].
  [ ] Cannot patch yet? Apply Atlassian's WAF / RewriteValve mitigation [1].

If you were exposed and unpatched, assume read, then rotate:
  [ ] The Crowd application password in crowd.properties.
  [ ] Any other secret stored under the web application root.

Hunt the access logs for pre-patch probing:
  grep -E "/(s|resources|sources)/[^ ]*(::|%3a%3a|\.\.)" access.log

То, чего не скажет вам смена номера версии

CVE-2026-21589 — чистый пример ошибки, которую не видно по номеру версии. Исправление меняет порядок двух внутренних шагов, и единственный способ узнать, что ваше развёртывание безопасно, — посмотреть на работающий артефакт и путь кода, доходящий до файловой системы. Это разрыв между «мы применили патч» и «мы проверили, что важное для нас закрыто».

Проследить запрос от края, через фреймворк, до точной строки, решающей, что читать, — суть аудита безопасности кода, и запах «проверить, затем преобразовать» из этой CVE я ищу в коде, не имеющем отношения к Atlassian. Если вы выпускаете то, что превращает внешний ввод в доступ к файлам, запросы к базе или внутренние обращения, именно эту границу я бы проверил для вас. Напишите, что у вас работает, и мы определим объём.

Источники

Нужна такая проверка для вашего продукта?

Аудит безопасности кода
Все статьи

08Контакт

Выпускаете продукт с данными пользователей?

Пришлите короткое описание. Я отвечу в течение трёх дней вопросами или наброском предложения, а стоимость согласуем письменно до начала работ.