Перейти к содержимому
getnextpdf.com

Санация недоверенных PDF: обезвреживание и реконструкция содержимого

Spec: ISO 32000-2, §12.6.4

PDF, приходящий из внешнего мира, — это код в той же мере, в какой документ. Он может нести JavaScript, действие Launch, встроенный исполняемый файл и структуру, сделанную так, чтобы сбить парсер. Обезвреживание и реконструкция содержимого (Content Disarm & Reconstruction, CDR) трактует такой файл как недоверенный, оставляет только то, что безопасно, и пересобирает чистый PDF из выживших.

На этой странице объясняется, как CdrEngine в NextPDF Enterprise это делает, — и та единственная честная вещь, которую каждый поставщик CDR должен сказать вслух: пересобранный файл — это проекция безопасности оригинала, а не его верная копия.

Опасные части PDF не экзотичны. Это стандартные возможности. Спецификация определяет целый каталог действий — что происходит, когда документ открывается, когда показывается страница, когда меняется поле, — и этот каталог включает действия JavaScript и Launch (Spec: ISO 32000-2, §12.6.4). Программа просмотра, которая уважает формат, с радостью их выполнит. Это и есть опора атакующего: файл, одновременно совершенно корректный и совершенно вредоносный.

Фильтрация по расширению файла здесь не делает ничего. Угроза внутри правильно сформированного PDF, так что единственная настоящая защита — открыть его, понять его и снять активную машинерию, прежде чем она вообще достигнет средства отрисовки. Это та позиция проверки ввода, которую описывает руководство OWASP по загрузке файлов и проверке ввода: никогда не доверяйте байтам и предпочитайте пересборку известно-хорошего артефакта сканированию враждебного на известно-плохие сигнатуры.

  • CDR предполагает, что вход враждебен, и производит новый файл, а не латает старый.
  • CdrEngine::sanitize() выполняет шесть фаз: разбор, контроль допуска, обнаружение угроз, фильтрация, очистка ссылок, пересборка.
  • Он возвращает CdrResult, который сообщает вам, что было удалено, был ли документ вообще допущен и — если нет — почему он был отклонён.
  • Вывод — это проекция безопасности. Это явно не доказательственная копия, не совпадение хеша и не архивный артефакт. Это контракт, а не оговорка.
  • Это только Enterprise. Ядро NextPDF не выполняет CDR.

Собственная документация движка формулирует проектную красную линию в трёх словах: Security Projection Layer. sanitize() — это разрушающее, необратимое преобразование. Ему разрешено выбрасывать байты. Чего ему не разрешено — это притворяться, что результат — тот же документ.

Конвейер намеренно упорядочен. Каждая фаза сужает доверие, прежде чем следующая фаза на нём действует.

  1. ParsePdfReader builds the object graph from the raw bytes. A parse failure is a rejection, not a best-effort guess.
  2. Admission controlResource limits are checked first — object count, page count, decoded-stream size, per-stream inflation ratio. Over-budget input is rejected, never sanitised.
  3. DetectThreatDetector walks the graph and records each dangerous feature as a DetectedThreat with its object number and type.
  4. FilterObjects are partitioned into safe and removed. Catalog-level keys are stripped in place so the document catalog itself survives.
  5. Reference scrubPointers to removed objects are cleaned so the rebuilt file has no dangling references.
  6. RebuildCdrRebuilder serialises only the safe objects into a new PDF. The output is fresh structure, not an edited original.
Шесть фаз CdrEngine::sanitize(), по порядку. Документ разбирается, затем допускается или отклоняется против пределов ресурсов, затем сканируется на угрозы; опасные объекты отфильтровываются, висячие ссылки очищаются, и только выжившие безопасные объекты сериализуются в совершенно новый PDF. Исходные байты никогда не проходят насквозь.

Допуск перед обезвреживанием. Прежде чем удалена хоть одна угроза, движок спрашивает, стоит ли документ вообще обрабатывать. CdrPolicy несёт пределы — maxObjects, maxPageCount, maxDecodedStreamBytes и maxInflationRatio, который защищает от бомб декомпрессии. Документ, который пробивает их насквозь, отклоняется, и CdrResult говорит об этом с admitted: false и rejectionReason. Это различие несущее: «мы его очистили» и «мы его отказали» — разные исходы, и тип результата держит их порознь, чтобы и ваша отчётность об ошибках могла так же.

Угрозы названы, а не угаданы. ThreatDetector::detect() сопоставляет каждую опасную конструкцию ThreatTypeJavaScript, LaunchAction, OpenAction, AdditionalActions, RemoteGoTo, SubmitForm, ImportData, EmbeddedFiles, RichMedia, NamedJavaScript и другим, — каждый привязан к конкретной возможности PDF. Объект, который вообще нельзя разобрать, становится собственным типом угрозы, UnparseableObject, потому что объект, который санитайзер не может прочитать, — это объект, за который он не может поручиться. Каждая находка — это DetectedThreat, несущий номер нарушающего объекта, так что удаление точно.

Удаление защищает документ, а не только нагрузку. Некоторые опасные ключи живут на каталоге документа (Root) — /OpenAction, который срабатывает при открытии, например. Удаление всего объекта Root, чтобы убить один ключ, уничтожило бы каталог и по-тихому сломало бы файл. Движок обрабатывает их как снятие ключей на месте на каталоге, а в качестве защиты со сбоем закрыто отказывается выдавать пересобранный документ, который потерял свой /Root вообще. Санитайзер, который производит структурно сломанный файл, отчитываясь об успехе, — это ровно тот режим сбоя, для предотвращения которого существует эта защита.

Приведённая ниже форма — настоящая точка входа. Вы вручаете движку сырые байты и политику; вы получаете обратно CdrResult, который честен о том, что произошло.

<?php
declare(strict_types=1);
use NextPDF\Enterprise\Security\Cdr\CdrEngine;
use NextPDF\Enterprise\Security\Cdr\CdrPolicy;
$engine = new CdrEngine();
// Standard policy removes the known active threats — JavaScript, Launch
// actions, remote go-to, form submit/import, and the rest — while leaving
// the lossy opt-in "Strip*" cases off by default.
$result = $engine->sanitize($untrustedPdfBytes, CdrPolicy::standard());
if (!$result->admitted) {
// Rejected by admission control (e.g. object/page limit, zip bomb).
// This is NOT a sanitised document. Do not serve it; report the reason.
throw new \RuntimeException($result->rejectionReason);
}
if ($result->hadThreats()) {
// The disarmed bytes are safe to render. Each removed threat carries its
// type and object number for your audit log — never silently.
foreach ($result->removedThreats as $threat) {
error_log(\sprintf(
'CDR removed %s in object %d',
$threat->type->value,
$threat->objectNumber,
));
}
}
$cleanBytes = $result->sanitizedPdf; // The security projection. Not the original.

Нет пути, где этот код по-тихому возвращает всё ещё опасный файл. Либо документ допущен и обезврежен, либо он отклонён с заявленной причиной. Список removedThreats означает, что обезвреживание поддаётся аудиту, а не волшебно.

«CDR — это просто редактирование с дополнительными шагами».

Это не так, и смешивание этих двух опасно. Редактирование удаляет информацию — имена, номера счетов, содержимое, которое человек не должен видеть. CDR удаляет возможность — JavaScript, действие Launch, встроенную нагрузку, которую машина не должна запускать. У них противоположные критерии успеха. Редактирование верно, когда конфиденциальное содержимое ушло, а остальное сохранено дословно. Обезвреживание верно, когда угроза ушла, и оно совершенно готово изменить безобидную структуру, чтобы этого достичь. Используйте инструмент, который соответствует вашему намерению; не тянитесь за одним, ожидая гарантий другого.

Второе заблуждение — что чистая пересборка доказывает, что оригинал был чист. Она не доказывает об оригинале ничего. Она доказывает лишь, что вывод не содержит обнаруженной угрозы. Вход мог быть оружием; задача CDR — убедиться, что то, что вы передаёте дальше по потоку, оружием не является.

Это та часть, которую брошюры пропускают, так что мы скажем её прямо.

  • Вывод — это проекция безопасности, а не доказательственная копия. Исходный код CdrEngine несёт это как архитектурную красную линию. Санированный PDF не должен использоваться для сохранения юридических доказательств, для сравнения хеша с оригиналом или как архивная копия. Преобразование по своей природе разрушающее и необратимое.
  • У обнаружения есть область. CDR удаляет угрозы, которые умеет называть. Это сильный, поддающийся аудиту слой в стеке эшелонированной защиты — не гарантия, что файл свободен от всякой возможной будущей техники. Держите его за той же проверкой загрузки, проверками типа содержимого и обработкой с наименьшими привилегиями, которые описывает руководство OWASP по загрузке файлов.
  • Некоторые политики намеренно с потерями. Включаемые случаи Strip* удаляют встроенные файлы, подписи, поля форм, слои и 3D-медиа. Они мощны и будут удалять легитимное содержимое — скажем, нагрузку счёта ZUGFeRD/Factur-X. Они выключены по умолчанию ровно по этой причине. Включайте их осознанно.
  • Подписи не переживают пересборку. Реконструкция файла меняет его байты, так что любая исходная цифровая подпись больше не соответствует своему диапазону байтов. Обезвреженный документ не подписан в отношении источника. Если вам нужен подписанный артефакт, подпишите чистый вывод как новый акт.
Content Disarm & Reconstruction (CDR) — edition availability
EditionAvailability
Core

Недоступно. Ядро NextPDF не выполняет CDR. Оно разбирает, отрисовывает и записывает PDF; оно не обнаруживает угрозы и не пересобирает недоверенный ввод.

Pro

Недоступно в издании Pro.

Enterprise

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

  • CDR (Content Disarm & Reconstruction) — стратегия санации, которая разбирает недоверенный файл, удаляет активные или опасные компоненты и пересобирает чистый файл из безопасного остатка, а не сканирует на известно-плохие сигнатуры.
  • Проекция безопасности — санированный вывод, который сохраняет достаточно источника, чтобы быть полезным, гарантируя при этом удаление угроз. Он намеренно не верен по байтам и не пригоден для доказательств, хеширования или архивации.
  • Контроль допуска — предсанационный шлюз, который отклоняет документы, превышающие пределы ресурсов (число объектов, число страниц, размер декодированного потока, коэффициент инфляции), прежде чем начнётся какая-либо работа по обезвреживанию.
  • Действие — конструкция PDF, которая заставляет что-то произойти по триггеру, такому как открытие документа или изменение поля; каталог типов действий (Spec: ISO 32000-2, §12.6.4) включает действия JavaScript и Launch, каноническую поверхность угроз CDR.
  • Висячая ссылка — указатель на объект, который больше не существует после фильтрации. Фаза очистки ссылок удаляет их, чтобы пересобранный файл оставался структурно согласованным.