Одна библиотека, восемь продуктов, один запрос до ваших конфигов
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. Если вы выпускаете то, что превращает внешний ввод в доступ к файлам, запросы к базе или внутренние обращения, именно эту границу я бы проверил для вас. Напишите, что у вас работает, и мы определим объём.
Источники
Нужна такая проверка для вашего продукта?
Аудит безопасности кода