
A Documentation of CVE-2025-68116
Автор: @x0root
Уязвимость: Межсайтовый скриптинг (XSS) с сохранением через загружаемые файлы, рендерящиеся в браузере (SVG / HTML)
Затронутое ПО: FileRise (< 2.7.1)
Патч-версия: 2.7.1
Официальный CVE (запрошен через GHSA): CVE-2025-68116 (отслеживание/уведомление: GHSA-35pp-ggh6-c59c)
Предыдущее связанное уведомление (исходная нейтрализация, которая была обойдена): GHSA-qrcv-vjvf-fr29
Оценка CVSS:
Оценка репортера оценивает необходимые привилегии (PR) в момент эксплуатации, а не в момент внедрения уязвимости.
Эксплуатация происходит, когда жертва переходит по сгенерированной публичной ссылке общего доступа, что не требует аутентификации или привилегий (PR:N).
Оценка CNA оценивает PR на основе возможности загрузки вредоносного файла. Однако CVSS v3.1 определяет необходимые привилегии (PR) как привилегии, которыми должен обладать атакующий на момент эксплуатации уязвимости, а не привилегии, необходимые для размещения или подготовки уязвимого состояния.
Таким образом, PR:N более точно отражает реальные условия эксплуатации, что приводит к классификации серьезности как Критический (9.6).
Примечание: GHSA-qrcv-vjvf-fr29 ввел нейтрализацию, которая предотвращала рендеринг SVG в веб-интерфейсе FileRise (панель предпросмотра). В данном отчете документируется обход этой нейтрализации — в частности, серверные конечные точки загрузки/скачивания, — который отслеживается как GHSA-35pp-ggh6-c59c / CVE-2025-68116.
Этот документ представляет собой полную техническую запись CVE-2025-68116: хранимый XSS в FileRise, который сохранился после более ранней нейтрализации и в конечном итоге был исправлен в версии v2.7.1. Он включает обнаружение, доказательства эксплуатации, многократные неудачные исправления, точный анализ корневой причины с точки зрения потока управления (с доказательствами), окончательную проверку патча и анализ характеристик эксплуатации, актуальных для оценки CVSS. Все содержимое ниже основано на воспроизведенных тестах, проверке контроллера и публичной теме уведомления.
Предыдущее уведомление, GHSA-qrcv-vjvf-fr29, касалось хранимого XSS через загрузку SVG путем блокировки встроенного рендеринга в веб-интерфейсе FileRise. Эта нейтрализация не затрагивала то, как SVG-файлы обрабатывались следующими серверными конечными точками:
/api/file/download.php/api/file/share.phpCVE-2025-68116 (отслеживается как GHSA-35pp-ggh6-c59c) документирует обход нейтрализации GHSA-qrcv-vjvf-fr29: атакующий может сохранить сфабрикованный SVG и доставить его жертвам через публичные ссылки общего доступа или определенные варианты скачивания, что приводит к выполнению скрипта в контексте origin FileRise.
Чтобы проверить, по-прежнему ли серверная часть предоставляет SVG в рендерящемся виде, я загрузил простой SVG PoC:
Доступ к файлу через:
/api/file/download.php?…/api/file/share.php?token=…привел к выполнению alert(). Исходная нейтрализация GHSA-qrcv-vjvf-fr29 (блокировка предпросмотра в интерфейсе) была обойдена прямым доступом к этим конечным точкам.
alert() — это PoC; я проверил значимое воздействие, заставив полезную нагрузку взаимодействовать с внутренними API.
Использованная тестовая нагрузка:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<script type="text/javascript">
fetch('/api/upload/upload.php')
.then(response => response.text())
.then(data => alert('API Response: ' + data));
</script>
</svg>
Когда вошедший в систему администратор открывал ссылку общего доступа, содержащую этот SVG, скрипт выполнялся и делал аутентифицированные запросы к API. Наблюдаемые эффекты включали:
{"csrf_expired":true,"csrf_token":"..."})Классификация воздействия, продемонстрированная во время тестирования:
Я сообщил о проблеме в частном порядке. Сопровождающий выпустил несколько инкрементальных исправлений:
На протяжении v2.6.0 → v2.7.0 конечная точка ссылки общего доступа продолжала обслуживать SVG таким образом, что допускался встроенный рендеринг и выполнение скриптов. Анализ корневой причины ниже объясняет, почему более ранние исправления не смогли полностью закрыть вектор.
Основной причиной был не один отсутствующий заголовок, а порядок потока управления и вывода внутри shareFile() (контроллер), который препятствовал применению заголовков безопасности во многих путях выполнения. Присутствовали два класса проблем:
exit;, которые прерывали функцию до того, как были установлены заголовки безопасности.Я использовал сканирование awk для вывода вхождений header() и exit; внутри shareFile() до вызова readfile():
Команда: awk '/function shareFile(/ {flag=1} /readfile(/ {flag=0} flag && /(header|exit;)/ {printf "%4d | %s\n", NR, $0}' src/controllers/FileController.php
Наблюдаемый вывод (сокращен из моего запуска):
1649 | header('Content-Type: application/json; charset=utf-8'); 1651 | exit; 1657 | header('Content-Type: application/json; charset=utf-8'); 1659 | exit; 1664 | header('Content-Type: application/json; charset=utf-8'); 1666 | exit; 1670 | header("Content-Type: text/html; charset=utf-8"); 1693 | exit; 1699 | header('Content-Type: application/json; charset=utf-8'); 1701 | exit; 1719 | header('Content-Type: application/json; charset=utf-8'); 1721 | exit; 1725 | header('Content-Type: application/json; charset=utf-8'); 1727 | exit;
Заголовки безопасности (логика усиления защиты) начинаются примерно со строки 1743:
1743 | header('X-Content-Type-Options: nosniff'); ... 1770 | header("Content-Disposition: attachment; ...");
Поскольку функция выдает заголовки + exit; раньше во многих путях, эти запросы никогда не достигали кода усиления защиты, который устанавливает Content-Disposition, nosniff или ограничительный тип.
В потоке общего доступа, защищенного паролем, функция рано выводила HTML-приглашение для ввода пароля:
if (!empty($record['password']) && empty($providedPass)) { header("Content-Type: text/html; charset=utf-8"); ... exit; }
Этот путь отправляет Content-Type: text/html и завершается до логики усиления защиты SVG, что вызывает встроенный рендеринг в браузерах для защищенных паролем ресурсов, где пароль не был предоставлен.
Уязвимость не ограничивалась потоками, защищенными паролем. Запрос к ресурсу общего доступа без пароля также возвращал text/html в моих тестах:
Команда: curl -svI "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" 2>&1 | grep -iE "content-type"
Наблюдалось: < Content-Type: text/html; charset=UTF-8
Это подтверждает, что даже в общем случае (без пароля) ответ был text/html, и SVG рендерился встроенно.
Я захватил сырой ответ конечной точки общего доступа, который включал предупреждения PHP, выведенные до усиления заголовков. Снимок (сокращен):
Команда: curl -s "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" | head -n 30
Наблюдаемый сырой вывод (сокращен):
Deprecated: Constant FILTER_SANITIZE_STRING is deprecated in /data/data/com.termux/files/home/FileRise/src/controllers/FileController.php on line 1644
Эти предупреждения показывают, что вывод был произведен (уведомления об устаревании) до усиления заголовков, что делало невозможным применение последующих вызовов header() в этих запусках.
nosniff существовал, но не достигался на многих путях кода из-за ранних выходов и вывода.После отчетов о корневой причине сопровождающий применил изменения, которые решили проблемы с потоком управления и порядком вывода. В v2.7.1:
exit;, которые обходили усиление защиты, были исправлены/обработаны.Финальная проверка (мой тест на v2.7.1):
Команда: curl -svI "http://127.0.0.1:8080/api/file/share.php?token=fc911e48b0a30e9417a9020ef959784d" 2>&1 | grep -iE "content-type|content-disposition"
Наблюдалось: < Content-Type: application/octet-stream < Content-Disposition: attachment; filename="xss-image.svg"; filename*=UTF-8''xss-image.svg
Результат: Браузер принудительно скачал файл; SVG не рендерился встроенно, и XSS-нагрузка не выполнилась. Я считаю, что v2.7.1 устранила проблему в моем окружении.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:L — Оценка: 9.6 (Критический)
Я задокументировал это обоснование в теме уведомления и запросил использование PR:N.
Вектор CVSS от CNA отражает ограниченную модель угроз.
Эмпирическое тестирование демонстрирует более серьезный и воспроизводимый путь эксплуатации, что соответствует более высокому базовому баллу CVSS 3.1 в соответствии со стандартными правилами оценки.
Независимым оценщикам рекомендуется оценивать серьезность, используя наблюдаемые условия эксплуатации, описанные в этом документе, чтобы публичная оценка серьезности отражала реальное воздействие, а не узко ограниченный базовый уровень.
1649 | header('Content-Type: application/json; charset=utf-8'); 1651 | exit; 1657 | header('Content-Type: application/json; charset=utf-8'); 1659 | exit; 1664 | header('Content-Type: application/json; charset=utf-8'); 1666 | exit; 1670 | header("Content-Type: text/html; charset=utf-8"); 1693 | exit; 1699 | header('Content-Type: application/json; charset=utf-8'); 1701 | exit; 1719 | header('Content-Type: application/json; charset=utf-8'); 1721 | exit; 1725 | header('Content-Type: application/json; charset=utf-8'); 1727 | exit;
Заголовки безопасности начинаются примерно с 1743: 1743 | header('X-Content-Type-Options: nosniff'); ... 1770 | header("Content-Disposition: attachment; ...");
~/FileRise $ curl -svI "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" 2>&1 | grep -iE "content-type" < Content-Type: text/html; charset=UTF-8
Deprecated: Constant FILTER_SANITIZE_STRING is deprecated in /data/data/com.termux/files/home/FileRise/src/controllers/FileController.php on line 1644
...затем следовала выгрузка SVG-нагрузки, которая выводилась и рендерилась встроенно.
~/FileRise $ curl -svI "http://127.0.0.1:8080/api/file/share.php?token=fc911e48b0a30e9417a9020ef959784d" 2>&1 | grep -iE "content-type|content-disposition" < Content-Type: application/octet-stream < Content-Disposition: attachment; filename="xss-image.svg"; filename*=UTF-8''xss-image.svg