
Публичная ссылка выглядела чистой в дереве страниц, но конечная точка поиска рассказала другую историю. В Docmost скрытые от просмотра по публичной ссылке дочерние страницы с ограниченным доступом могли всё ещё просачиваться через результаты поиска по публичной ссылке.
Публичная ссылка выглядела чистой в дереве страниц, но поисковая конечная точка рассказывала другую историю. В Docmost скрытые дочерние страницы, недоступные для просмотра через публичную ссылку, могли всё равно просачиваться через результаты поиска по публичной ссылке.
Я обнаружил эту проблему, изучая Docmost — платформу с открытым исходным кодом для совместных вики и документации, — задавшись очень простым вопросом:
Если страница намеренно скрыта от публичного зрителя, каждый ли публичный функционал соблюдает то же ограничение?
В данном случае ответ был отрицательным.
Ограниченная дочерняя страница могла оставаться скрытой в дереве публичной ссылки, но при этом просачиваться через конечную точку поиска по публичной ссылке.
Проблема была принята и получила идентификатор CVE-2026-33146.
Docmost: Docmost на GitHub
CVE: CVE-2026-33146
Официальный сайт Docmost представляет его как корпоративную вики, развёртываемую на месте, с более 3 миллионов загрузок, и утверждает, что ему доверяют команды таких организаций, как Vilnius City, Bechtle, Правительство Австралии, Красный Крест и ETS Quebec.
публичная родительская ссылка с включенными подстраницами → ограниченный потомок исключён из публичного дерева → злоумышленник запрашивает поиск по публичной ссылке → утекают заголовок и фрагмент ограниченного потомка
Docmost — это платформа для совместных вики и документации.
Она предоставляет:
Это означает, что её модель публичного доступа является реальной границей безопасности.
Важный вопрос заключался не в том, может ли Docmost публично делиться страницами.
Настоящий вопрос заключался в следующем:
Когда Docmost решает, что дочерняя страница является ограниченной и не должна отображаться посетителю по публичной ссылке, соблюдается ли это ограничение везде в потоке публичного доступа?
В данном случае — нет.
Многие проверки безопасности прекращаются слишком рано, когда видят страницу, скрытую в интерфейсе.
Этого недостаточно.
Более сильный вопрос звучит так:
Каждый ли серверный путь применяет одно и то же решение о видимости?
Это важно, потому что границы безопасности определяются не тем, как выглядит интерфейс.
Они определяются тем, что на самом деле возвращает сервер.
Здесь конечная точка дерева публичных ссылок вела себя безопасно:
Но путь поиска по публичной ссылке вёл себя иначе:
Это превратило проблему в реальную уязвимость авторизации и раскрытия информации, а не просто в несоответствие отображения.
Я не подходил к Docmost, случайно фаззя конечные точки в надежде, что появится что-то интересное.
Более сильный путь — сначала выбрать доверенную границу.
Для приложений, поддерживающих:
один из лучших вопросов:
Применяет ли уровень поиска точно такую же границу авторизации, как и уровень просмотра?
Этот вопрос становится особенно ценным, когда:
Именно здесь и проявилась эта проблема.
Ошибка была не в том, что Docmost не смог скрыть ограниченную страницу в обычном публичном дереве.
Ошибка заключалась в том, что публичный поиск не учитывал ту же логику ограничений.
Из анализа исходного кода: поток публичного дерева использовал обход потомков с учётом ограничений.
Соответствующая область:
apps/server/src/core/share/share.service.tsЭтот путь намеренно исключал ограниченных потомков, используя:
getPageAndDescendantsExcludingRestricted(...)Но поток поиска по публичной ссылке шёл по другому пути.
Соответствующие области:
apps/server/src/core/search/search.controller.tsapps/server/src/core/search/search.service.tsТам код собирал потомков, используя:
getPageAndDescendants(...)Это означало, что ограниченные потомки оставались в области поиска.
В контексте публичного доступа это имеет большое значение, потому что ветка поиска выполняется без обычного контекста разрешений аутентифицированного пользователя. Как только ограниченные потомки были включены в набор страниц, доступных для поиска, их метаданные могли просачиваться через ответ.
Потому что злоумышленнику не нужна аутентифицированная учётная запись.
Ему нужно только:
Как только это условие выполнено, публичный посетитель может запросить конечную точку поиска по общей ссылке и восстановить:
Этого достаточно для создания утечки конфиденциальности, даже если полное тело страницы не возвращается.
Важное различие в том, что приложение уже чётко сигнализирует о предполагаемой модели безопасности.
Конечная точка публичного дерева скрывает ограниченных потомков.
Поэтому настоящий вопрос не:
«Случайно ли поиск возвращает более широкий набор результатов?»
Настоящий вопрос:
«Нарушает ли поиск решение об авторизации, уже принятое в другом месте для той же границы публичного доступа?»
В Docmost — да.
Это превращает проблему из:
в:
Вот почему это настоящая уязвимость.
Я подтвердил проблему, сравнив две соответствующие публичные конечные точки бок о бок.
Сначала я проверил обычную конечную точку публичного дерева, используя ключ публичной ссылки.
Пример запроса:
POST /api/shares/tree HTTP/1.1
Host: 127.0.0.1:6752
Content-Type: application/json
{
"shareId": "public-share-key"
}
Ответ вернул только публичную дочернюю страницу в дереве страниц.
Показательный результат:
{
"pageTree": [
{
"id": "public-child",
"title": "Public roadmap"
}
]
}
Это установило ожидаемое поведение продукта:
Затем я запросил конечную точку поиска по публичной ссылке, используя термин, который встречался внутри ограниченного потомка.
Пример запроса:
POST /api/search/share-search HTTP/1.1
Host: 127.0.0.1:6752
Content-Type: application/json
{
"shareId": "public-share-key",
"query": "salary"
}
Ответ всё равно включил ограниченного потомка:
{
"items": [
{
"id": "public-child",
"title": "Public roadmap",
"highlight": "release plan and milestones"
},
{
"id": "restricted-child",
"title": "Payroll Q4",
"highlight": "salary bands and bonus targets"
}
]
}
Это доказало основное утверждение: