Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2026-34213 — Пользователь Docmost с низкими привилегиями может указать attachmentId жертвы в общей конечной точке загрузки и перезаписать сохраненное вложение другой страницы в пределах того же рабочего пространства. | Kitploit
Инструменты/GitHubGitHub/0xmrma/cve-2026-34213
Анализ уязвимостейЭксплуатация веб-приложенийТестирование на ПроникновениеСтатьи и ИсследованияОбучение и Образование
GitHub0xmrma/cve-2026-34213

CVE-2026-34213

Пользователь Docmost с низкими привилегиями может указать attachmentId жертвы в общей конечной точке загрузки и перезаписать сохраненное вложение другой страницы в пределах того же рабочего пространства.

Репозиторий
442 месяцев назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

CVE-2026-34213

Низкопривилегированный пользователь Docmost мог указать чужой attachmentId в общем эндпоинте загрузки и перезаписать сохранённое вложение другой страницы в том же рабочем пространстве.

Введение

Я выявил, ответственно раскрыл и воспроизвёл высокосерьёзную ошибку авторизации в Docmost — платформе для совместной работы с документацией с открытым исходным кодом.

Официальный сайт Docmost представляет её как корпоративную вики для локального развёртывания с 3M+ загрузок и утверждает, что ей доверяют команды таких организаций, как Vilnius City, Bechtle, Правительство Австралии, Красный Крест и ETS Quebec.

Ошибка находилась в общем пути загрузки файлов, который Docmost также использует для сохранения/обновления диаграмм.

Я изучал этот код, задавшись очень конкретным вопросом:

Что произойдёт, если эндпоинт загрузки подтвердит доступ на редактирование одной страницы, а цель перезаписи будет выбрана отдельным контролируемым пользователем идентификатором вложения?

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

Docmost позволял вызывающему коду отправлять:

  • pageId для страницы, которую ему разрешено редактировать, и
  • attachmentId, принадлежащий другой странице в том же рабочем пространстве

Сервер выполнял проверку согласованности перезаписи, но защита использовала неверную логическую операцию.

---

Это означало, что запрос мог пройти авторизацию и всё равно перезаписать чужое вложение.

Эта проблема получила номер CVE-2026-34213.

Docmost: docmost/docmost
Уведомление: GHSA-89fp-2hch-j9gp
CVE: CVE-2026-34213
Исправлено в: v0.71.0

photo0

Цепочка атаки

контролируемый атакующим pageId с доступом на редактирование -> контролируемый атакующим чужой attachmentId -> ошибочная защита перезаписи считает межстраничную перезапись допустимой -> путь к хранилищу перестраивается на основе чужого attachmentId -> байты атакующего заменяют файл жертвы -> страница жертвы продолжает отдавать изменённое вложение


Что делает эта часть Docmost

Docmost хранит загруженные вложения страниц как записи в базе данных, а также соответствующие файлы в хранилище.

При обычной загрузке сервер создаёт новый идентификатор вложения и записывает новый файл.

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

Такое поведение само по себе легитимно.

Проблема в том, что оно создаёт путь с высоким риском:

  • один входной параметр определяет страницу, для которой выполняется авторизация
  • другой входной параметр определяет вложение, которое перезаписывается

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

Docmost этого не сделал.


Почему эта поверхность заслуживала внимания

Смешанные эндпоинты создания/обновления — частое место ошибок авторизации.

Причина проста:

  • потоки создания обычно авторизуются на основе контейнера-объекта
  • потоки обновления обычно авторизуются на основе существующей записи
  • если один эндпоинт пытается делать и то, и другое, легко сначала проверить не то, что нужно, а второй идентификатор рассматривать как «просто метаданные»

Именно такой шаблон здесь и есть.

POST /api/files/upload проверял, может ли вызывающий редактировать страницу, указанную в pageId.

Но если также указывался attachmentId, сервер переключался на путь перезаписи и выбирал существующую запись вложения отдельно.

Это порождало критический вопрос безопасности:

доказывает ли путь перезаписи, что выбранное вложение действительно принадлежит авторизованной странице?

Ответ в уязвимых версиях был: нет.


Первопричина

Коренная причина — обход авторизации через контролируемый пользователем ключ в сочетании с ошибкой логической операции в защите перезаписи.

Уязвимый поток выглядел так:

  1. AttachmentController.uploadFile() считывал pageId из данных multipart-формы.
  2. Он загружал эту страницу и вызывал validateCanEdit(page, user).
  3. Он отдельно принимал опциональный attachmentId из того же запроса.
  4. AttachmentService.uploadFile() загружал существующее вложение по этому указанному атакующим ID.
  5. Защита перезаписи пыталась проверить, что существующее вложение соответствует авторизованной странице.
  6. Защита использовала && вместо того, чтобы отклонять при любом несовпадении.

Уязвимая защита была:

root@kitploit:~
if (
  existingAttachment.pageId !== pageId &&
  existingAttachment.fileExt !== preparedFile.fileExtension &&
  existingAttachment.workspaceId !== workspaceId
) {
  throw new BadRequestException("File attachment does not match");
}

Это условие отклоняло запрос только если:

  • идентификатор страницы не совпадал, и
  • расширение файла не совпадало, и
  • идентификатор рабочего пространства не совпадал

одновременно.

Это противоположно тому, что должна делать защита перезаписи.

Для реального случая атаки атакующий намеренно оставался в пределах одного рабочего пространства.

Итак:

  • existingAttachment.workspaceId !== workspaceId было false

Как только этот операнд становился ложным, всё условие && оценивалось как false, даже если вложение принадлежало другой странице.

Таким образом, сервер считал межстраничную перезапись допустимой.

Это была первая половина ошибки.

Вторая половина — то, что делало воздействие реальным.

После проверки сервис перестраивал путь к целевому хранилищу, используя указанный атакующим attachmentId и имя файла:

root@kitploit:~
const filePath =
  `${getAttachmentFolderPath(AttachmentType.File, workspaceId)}/` +
  `${attachmentId}/${preparedFile.fileName}`;

Затем на пути обновления Docmost обновлял только изменяемые метаданные, такие как:

  • fileSize
  • updatedAt

Он не привязывал повторно владельца к странице атакующего.

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

Вот почему это была не безобидная нестыковка.

Это была примитивная постоянная несанкционированная перезапись.


Почему это проблема безопасности, а не просто логическая ошибка

Это не была косметическая ошибка и не проблема коллизии имён файлов.

Атакующему не нужна гонка. Атакующему не нужно угадывать случайный путь. Атакующему не нужен доступ на запись к странице жертвы.

Ему нужно было только:

  • доступ на чтение, чтобы узнать чужую ссылку на вложение, и
  • доступ на запись к любой другой странице в том же рабочем пространстве

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

Это прямой сбой целостности.

На практике атакующий мог:

  • подделывать диаграммы
  • заменять вложения на вводящий в заблуждение контент
  • повреждать файлы, на которые есть ссылки
  • создавать запутанные следы аудита, поскольку вложение всё ещё казалось принадлежащим странице жертвы

Важный момент:

сервер принял цель перезаписи, выбранную атакующим, без привязки к странице, чьё разрешение на редактирование было фактически проверено.

Это сбой контроля доступа, а не просто плохая логическая гигиена.


Почему эксплуатация была практичной

Эксплойт был особенно практичен для вложений-диаграмм.

Клиент Docmost намеренно повторно использует attachmentId для сохранения диаграмм и использует детерминированные имена файлов:

  • diagram.excalidraw.svg
  • diagram.drawio.svg

Это важно, потому что снижает требования к атакующему.

Для обычных вложений атакующему нужны оба:

  • идентификатор вложения жертвы
  • имя файла жертвы

Для диаграмм имя файла уже предсказуемо.

Поэтому, если атакующий может прочитать содержимое страницы жертвы, он часто может восстановить единственный недостающий элемент:

  • attachmentId жертвы

В моей тестовой среде я использовал именно этот путь:

  • атакующий имел только доступ читателя к пространству жертвы
  • атакующий имел доступ писателя к другому контролируемому им пространству
  • оба пространства принадлежали одному рабочему пространству

Этого было достаточно.

Эксплойт пересекал границы страниц и границы пространств внутри одного рабочего пространства, всё ещё удовлетворяя ошибочной проверке рабочего пространства.


Подтверждение концепции

Я проверил проблему вживую на Docmost v0.70.3, используя одноразовую лабораторную среду, собранную из docmost/docmost:0.70.3, Postgres и Redis.

Поток PoC:

  1. Создать учётную запись владельца.
  2. Создать пространство жертвы и контролируемое атакующим пространство в одном рабочем пространстве.
  3. Пригласить второго пользователя как атакующего.
  4. Предоставить атакующему:
    • доступ читателя к пространству жертвы
    • доступ писателя к пространству атакующего
  5. В пространстве жертвы загрузить вложение-диаграмму на страницу жертвы.
  6. От имени атакующего получить информацию о странице жертвы и записать attachmentId жертвы.
  7. Отправить POST /api/files/upload с:
    • pageId = ID страницы атакующего
    • attachmentId = ID вложения жертвы
    • file = заменяющий файл атакующего с именем файла жертвы
  8. Загрузить вложение жертвы до и после перезаписи и сравнить хеши.

Минимальная форма запроса:

root@kitploit:~
POST /api/files/upload
Content-Type: multipart/form-data

pageId=<attackerPageId>
attachmentId=<victimAttachmentId>
[email protected];filename=diagram.excalidraw.svg

Наблюдаемый живой результат:

  • ID вложения жертвы, полученный атакующим: 019d18ae-b176-751c-8525-b5f3cede131d
  • ID страницы атакующего, использованный в запросе на перезапись: 019d18ae-b15b-70e9-ac67-64948e87cc5e
  • ID страницы владельца жертвы остался: 019d18ae-b12f-75ec-8c1c-5aff3ba6be9c
  • Ответ сервера на запрос перезаписи: 200 OK
  • SHA-256 файла жертвы до перезаписи:
root@kitploit:~
686a0a0ede90ece1cbb975bb29304a6c3a90373a9c3ab2496345cf7ca59cc8fa
  • SHA-256 файла жертвы после перезаписи:
root@kitploit:~
e0168298846cdaf75c4d880f4b721d7c0ef0ef310f75617bf2b833af34cdbeba
  • SHA-256 полезной нагрузки атакующего:
root@kitploit:~
e0168298846cdaf75c4d880f4b721d7c0ef0ef310f75617bf2b833af34cdbeba
  • подключённое хранилище подтвердило, что путь жертвы теперь содержит:
root@kitploit:~
Attacker replacement from another page

Это полное сквозное доказательство перезаписи, а не просто теоретический обзор кода.


Почему PoC был выбран именно так

В ходе триажа я использовал два стиля доказательств:

  • узкую автономную обвязку, которая имитировала уязвимую логику перезаписи, и
  • полноценный живой HTTP-эксплойт против одноразового экземпляра Docmost

Автономная обвязка была полезна для изоляции ошибки логической операции.

Живой HTTP PoC был более сильным артефактом, потому что он доказывал полную историю безопасности:

  • авторизация страницы прошла на странице атакующего
  • attachmentId жертвы был принят
  • запрос на перезапись вернул успех
  • страница жертвы осталась логическим владельцем
  • байты на диске изменились на контролируемое атакующим содержимое

Это различие имеет значение в ошибках контроля доступа.

«Условие неверно» само по себе недостаточно.

«Условие неверно, и приложение можно провести от начала до конца к постоянной несанкционированной перезаписи» — это полный случай.


Анализ исправления

Исправление было выпущено в v0.71.0 и изменило условие защиты перезаписи с && на ||:

root@kitploit:~
if (
  existingAttachment.pageId !== pageId ||
  existingAttachment.fileExt !== preparedFile.fileExtension ||
  existingAttachment.workspaceId !== workspaceId
) {
  throw new BadRequestException("File attachment does not match");
}

Этот патч минимален, прямолинеен и корректен для сообщённой ошибки.

Он восстанавливает правильное правило:

перезапись разрешена только тогда, когда существующее вложение точно соответствует авторизованной странице/рабочему пространству/типу.

Как только защита отклоняет запрос при любом несовпадении:

  • межстраничные перезаписи терпят неудачу
  • межпространственные перезаписи терпят неудачу
  • несовпадения типа/расширения терпят неудачу

Это был правильный вид исправления:

  • никакого редизайна
  • никакой расплывчатой логики совместимости
  • никакой попытки «по возможности» восстановить

Просто строгая привязка между авторизованной страницей и целью перезаписи.

Тем не менее, здесь есть более широкий инженерный урок:

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

Даже когда непосредственная ошибка исправлена, более надёжными долгосрочными решениями являются:

  • выделенные эндпоинты обновления для сохранения диаграмм
  • неизменяемые проверки привязки как имени файла, так и идентификатора вложения
  • регрессионное покрытие, которое явно моделирует попытки межстраничной перезаписи

Но для самой уязвимости опубликованный патч чисто закрыл основную проблему.


Важные регрессионные сценарии

Независимо от того, добавил ли проект свои собственные закрытые тесты для исправления, вот сценарии, которые важны для долгосрочного покрытия:

  • перезапись с тем же pageId и тем же attachmentId должна успешно выполняться
  • перезапись с другим pageId и тем же рабочим пространством должна завершаться неудачей
  • перезапись с другим рабочим пространством должна завершаться неудачей
  • перезапись с несовпадающим расширением файла должна завершаться неудачей
  • перезапись с известным именем файла диаграммы, но чужим attachmentId должна завершаться неудачей
  • перезапись никогда не должна молча сохранять владельца-жертву после записи байтов атакующего

Смысл этих тестов — не просто корректность.

Они нужны, чтобы закрепить привязку авторизации, чтобы будущие «полезные» рефакторинги загрузки не открыли тот же класс ошибок заново.


Серьёзность и классификация

Опубликованное уведомление классифицировало эту проблему как:

  • CWE-639: Обход авторизации через контролируемый пользователем ключ
  • CVSS v3.1:
root@kitploit:~
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L

Это даёт 7.1 / High, что является правильным выводом.

Важный показатель здесь — целостность.

Это не была малозначимая ошибка метаданных. Атакующий полностью контролировал байты замены, записанные в путь вложения другой страницы, и страница жертвы продолжала отдавать изменённый объект после этого.

Это именно тот вид сохранённого межзаписного вмешательства, который заслуживает Высокой целостности.

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


Раскрытие

Я сообщил о проблеме в частном порядке через GitHub Security Advisories, предоставив:

  • анализ первопричины
  • живой HTTP PoC
  • доказательства запроса/ответа
  • хеши файлов до/после
  • настраиваемую одноразовую лабораторную среду

Проблема была принята мейнтейнером, назначена CVE-2026-34213 и опубликована 14 апреля 2026 года.

Публичное уведомление содержит:

  • затронутые версии: >= v0.3.0
  • исправленная версия: v0.71.0

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


Чему на самом деле учит эта ошибка

Интересный урок здесь не просто «используйте || вместо &&».

Это симптом.

Глубинный урок:

если одно поле, контролируемое пользователем, подтверждает авторизацию, а другое поле, контролируемое пользователем, выбирает обновляемый объект, эти два поля должны быть явно и точно связаны вместе.

Это правило проявляется повсюду:

  • вложения документов
  • медиа профиля
  • ссылки на облачные объекты
  • редактирование задач/комментариев
  • повторная обработка фоновых заданий

Как только система говорит:

  • «вам разрешено редактировать страницу X»
  • «пожалуйста, также укажите, какую существующую запись обновить»

она создала границу безопасности, которая должна обеспечиваться инвариантами точного совпадения.

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

Эта проблема также подкрепляет второй момент, который легко недооценить:

небольшие логические ошибки в защитном коде могут иметь первостепенные последствия для безопасности.

Условие из трёх пунктов, которое «выглядит разумно» на первый взгляд, было достаточно, чтобы инвертировать модель защиты для пути перезаписи.

Вот почему эти поверхности заслуживают тщательного анализа, а не поверхностной уверенности.


Ключевые моменты

  • Docmost использовал один эндпоинт как для новых загрузок, так и для обновлений вложений на месте.
  • Авторизация проверялась на основе указанного вызывающим pageId, но выбор цели перезаписи использовал отдельный указанный вызывающим attachmentId.
  • Защита перезаписи отклоняла только тогда, когда все условия несовпадения были истинны одновременно.
  • В случае атаки в пределах одного рабочего пространства эта проверка не срабатывала.
  • Сервис перестраивал путь к хранилищу на основе ID вложения жертвы и записывал в него байты атакующего.
  • Запись вложения оставалась привязанной к странице жертвы после перезаписи.
  • Детерминированные имена файлов диаграмм делали эксплуатацию особенно практичной.
  • Исправление в v0.71.0 правильно изменило защиту на отклонение при любом несовпадении.

Заключительные слова

Эта уязвимость была не об экзотическом поведении хранилища.

Она была о том, что путь обновления доверял выбранному атакующим идентификатору объекта больше, чем следовало.

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

Вот почему она стала CVE-2026-34213.

Патч в v0.71.0 чисто исправил непосредственную проблему, но более широкий урок остаётся ценным:

когда авторизация и выбор объекта разделены по разным контролируемым пользователем полям, точная привязка является свойством безопасности.

Скачать инструмент