Библиотека jsPDF тихо и спокойно живет в куче проектов, где сервер по кнопке собирает PDF-файлы: счета, акты, отчеты, сертификаты, резюме или пропуска на парковку. И именно поэтому история с CVE-2025-68428 - особенно неприятная. Тут не нужен "0-day цирк", не нужны сложные цепочки. Достаточно, чтобы в Node.js части вашего сервиса пользователь мог косвенно управлять путем к файлу, который jsPDF попробует подтянуть при сборке документа.
3 января 2026 года разработчики выпустили jsPDF 4.0.0 и закрыли дыру с оценкой CVSS 9.2. Это тот самый диапазон, где уже не обсуждают, критично или нет. Просто чинят. Уязвимость затрагивает именно Node.js сборки библиотеки (файлы dist/jspdf.node.js и dist/jspdf.node.min.js). В браузере такой номер не проходит, потому что браузер банально не дает JavaScript «лазить» по файловой системе. А вот на сервере все по-взрослому: процесс Node.js читает то, к чему у него есть права, и библиотека может затянуть это внутрь создаваемого PDF.
Как работает уязвимость
В формулировке уязвимости ключевая мысль очень простая: если первый аргумент функции loadFile контролируется пользователем, начинается локальная подгрузка файлов и обход директорий (Path Traversal). На практике риск шире, чем один метод. Он всплывает там, где разработчики вставляют в PDF изображения, шрифты или HTML, используя пути из запроса или данных пользователя. Методы, которые «фонят» чаще всего: loadFile, addImage, html, addFont.
Самый опасный момент - это выглядит как нормальная бизнес-логика. Пользователь загрузил логотип, указал путь к картинке, сервис собрал документ и отдал его обратно. Только вместо logo.png в запросе прилетает что-то вроде ../../../../etc/passwd или путь до .env вашего приложения. И если процесс Node.js имеет доступ к этому файлу, его содержимое аккуратно уезжает в PDF, который пользователь получит как результат. Без доступа к админке, без режима отладки, без паники в логах. Просто один запрос на генерацию документа.
Даже если вы в коде ожидаете картинку и передаете формат вроде PNG или JPEG, это не всегда спасает. В уязвимых версиях библиотека могла вести себя так, что фактически читала файл «как есть» и пыталась встроить данные. В итоге на выходе не картинка, а текстовый слив: конфиги, ключи, секреты, пароли, токены - все то, что обычно лежит в config.json, .env и файлах настроек базы. Иногда самое вкусное живет вообще не в папке проекта, а в системных директориях, и до них тоже можно дотянуться, если права процесса позволяют.
// пример уязвимого кода
const doc = new jsPDF();
doc.addImage(userProvidedPath, 'PNG', x, y);
doc.save('report.pdf');
Почему это опасно именно для обычных сервисов
Потому что генерация PDF почти всегда живет «где-то сбоку». Это не та часть кода, которую ревьюят с паранойей. Там много утилитарных функций, много склеек, и часто есть соблазн принимать пути к файлам как данные, а не как потенциальный ввод атакующего. В результате уязвимость превращается в утечку класса «прочитал и унес».
Типичные места, где ловится такой баг:
- Сервисы счетов и договоров, где клиент подставляет свой логотип или подпись.
- Платформы с резюме и сертификатами, где пользователь добавляет фото.
- Внутренние отчеты в корпорациях, где в параметрах передают пути к шаблонам или шрифтам.
- Пайплайны, где PDF строится автоматически по данным из веб-форм и интеграций.
Плюс отдельная неприятность характера цепи поставок. Вы могли написать свой код аккуратно, но библиотека по умолчанию позволяла опасное поведение. Это тот случай, когда популярность инструмента работает против экосистемы: одна ошибка в компоненте, и потенциально затронуты тысячи приложений, разработчики которых даже не подозревают, что путь к файлу у них под контролем пользователя. Похожая логика была в SolarWinds: там хакеры внедрили троян в официальное обновление доверенного софта. У jsPDF механика другая, но корень проблемы тот же, доверенные компоненты, которые используют миллионы разработчиков, становятся единой точкой отказа.
Реакция и исправление
Уязвимость нашел Квангвун Ким (Kwangwoon Kim, ник kilkat) из PerimeterX/HUMAN Security. Он сообщил о проблеме 23 ноября 2025 года через механизм согласованного раскрытия. Патч вышел 3 января 2026. Прошло 43 дня между отчётом и патчем. Это нормальная реальность: время на фикс, тесты и релиз. Но с точки зрения атакующего, это еще и комфортное окно возможностей, когда можно искать, где именно в проде jsPDF встроен в цепочку генерации.
Новая версия 4.0.0 не просто закрывает дыру, она меняет поведение по умолчанию. Чтение файловой системы теперь завязано на механизм разрешений Node.js (через флаг --permission и ограничение allow-fs-read на конкретные директории). Это правильная философия: не доверять всему диску и не надеяться, что разработчик сам везде все проверит. Потому что не проверит. Особенно в местах, которые кажутся второстепенными.
Наш вердикт: Техническая сложность низкая (path traversal через один параметр), но последствия критические. Если ваш Node.js-проект использует jsPDF версий ≤ 3.0.4 и передаёт в неё пользовательские пути, атакующий может прочитать системные файлы, конфиги и всё, до чего достаёт процесс. Обновляйтесь на 4.0.0 немедленно
Читайте также
Chrome 143: почему тихий патч WebView касается не только браузера
Google закрыла CVE-2026-0628 в Chrome 143.0.7499.192/.193. Это баг в WebView, компоненте, который живет не только в браузере, но и в …
В GNU Wget2 нашли критическую дыру: один файл может перезаписать вашу систему
В Wget2 обнаружили CVE-2025-69194 (CVSS 8.8): один подложный Metalink может заставить утилиту записать файл не туда и перезаписать важные данные. …
Китайские хакеры научились прятать бэкдоры в ядре Windows: новая тактика группировки Mustang Panda
Mustang Panda поднял скрытность на уровень ядра Windows. Руткит через драйвер ProjectConfiguration.sys обходит антивирусы, работает в памяти и внедряет TONESHELL …