Technical Article

Аудит рисков безопасности PDF с помощью PDFium в Delphi

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

В этой статье рассматривается проведение статического аудита безопасности документа в режиме «только чтение» с помощью компонента PDFium для Delphi и Lazarus. Аудит не выполняет отрисовку страниц. Он анализирует структуру документа, перечисляет все исполняемые элементы в файле и формирует простой отчет. Такой подход повышает защищенность систем при приеме внешних данных.

Что представляет собой аудит и для чего он нужен

Важно понимать границы применимости. Безопасный просмотр в песочнице отрисовывает файл с жесткими ограничениями, чтобы пользователь мог ознакомиться с ним без риска для операционной системы. Аудит же выполняется перед этим этапом. Это проверка без рендеринга, результатом которой является описание потенциальных угроз: наличие встроенных скриптов, действия при переходе по ссылкам, статус цифровой подписи и список вложений. Такой анализ запускается на этапе импорта документов из электронной почты, веб-форм или партнерских каналов передачи данных перед их открытием.

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

var
  Pdf: TPdf;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'Incoming_Invoice.pdf';
    Pdf.Active := True;          // parses structure, renders nothing
    // audit the loaded document here
  finally
    Pdf.Free;
  end;
end;

JavaScript уровня документа в дереве имен

Первое, что необходимо выявить, - это исполняемый код. Файл PDF может содержать JavaScript на уровне документа: скрипты, не привязанные к конкретным страницам или полям, а относящиеся к файлу в целом. Они хранятся в дереве имен /Names в записи /JavaScript. Стандартные программы просмотра выполняют их автоматически при открытии файла. Этот механизм часто используется злоумышленниками для автоматического запуска кода сразу после двойного клика по файлу пользователем.

При аудите необходимо выяснить два факта о таких скриптах: их наличие и содержимое. Компонент возвращает количество скриптов и позволяет прочитать каждое действие в виде записи, содержащей имя скрипта и его полный код. Анализ кода крайне важен. Имя скрипта вроде Doc.0 мало о чем говорит, но его текст может выполнять вызов app.launchURL или собирать данные для отправки во внешние системы. Извлечение исходного кода для анализа позволяет выявить небезопасные файлы на этапе загрузки.

var
  I: Integer;
  Action: TPdfJavaScriptAction;
begin
  if Pdf.JavaScriptActionCount > 0 then
    WriteLn('WARNING: document runs ', Pdf.JavaScriptActionCount,
            ' script(s) on open');
  for I := 0 to Pdf.JavaScriptActionCount - 1 do
  begin
    Action := Pdf.JavaScriptAction[I];
    WriteLn('  script "', Action.Name, '":');
    WriteLn(Action.Script);   // full body, for a human to read
  end;
end;

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

Действия запуска и вызова URI

Смежные угрозы могут быть связаны со ссылками и аннотациями. При аудите наиболее важны два типа действий. Действие запуска (Launch action) запускает внешнюю программу или открывает локальный файл при нажатии на ссылку. Действие URI (URI action) открывает веб-страницу. Специалист должен иметь возможность увидеть без кликов по документу, что кнопка на странице ссылается на запуск cmd.exe или ведет на подозрительный интернет-ресурс.

Компонент классифицирует найденные ссылки, возвращая тип действия и целевой путь для каждого элемента, что позволяет составить список всех действий запуска и вызова URI. Это чисто информационная операция: аудитор считывает данные о действиях и заносит их в отчет, не выполняя сами переходы.

Запуск действий происходит в компоненте отображения страниц, и его настройки по умолчанию довольно консервативны. Компонент TPdfView содержит свойство LinkOptions, определяющее типы ссылок, обрабатываемые автоматически при клике. По умолчанию этот набор равен [loAutoGoto, loAutoOpenURI], что разрешает переходы внутри документа и открытие веб-ссылок, но отключает loAutoLaunch для защиты от несанкционированного запуска программ. Для задач аудита рекомендуется полностью очистить этот набор, чтобы исключить любые автозапуски при анализе.

// Audit posture for the viewer: nothing auto-runs, nothing auto-opens.
View.LinkOptions := [];

// The shipped default already withholds launch:
//   default = [loAutoGoto, loAutoOpenURI]
//   loAutoLaunch is NOT in the default set, so external programs
//   are never started on a stray click out of the box.

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

Уровень прав MDP для цифровых подписей

Цифровые подписи требуют отдельного рассмотрения. Обычная подпись гарантирует неизменность байтов на момент подписания. Сертификационная подпись, создаваемая с использованием правил обнаружения и предотвращения изменений (MDP), идет дальше: она определяет рамки допустимых изменений в документе после сертификации, а программа просмотра выводит предупреждение при нарушении этих рамок. Чтение уровня прав позволяет аудитору узнать статус сертификации файла и уровень ограничений на его изменение.

Уровень прав MDP задается целым числом от 1 до 3. Уровень 1 означает полный запрет любых изменений; любое изменение аннулирует сертификацию. Уровень 2 разрешает заполнение форм и подписание (стандартный сценарий для договоров, которые необходимо заполнить и подписать без изменения текста). Уровень 3 дополнительно разрешает добавление аннотаций. Понимание этого уровня помогает выявить логические нестыковки: если документ сертифицирован по уровню 1, но содержит активные поля ввода или скрипты, это является поводом для ручной проверки.

Компонент считывает количество подписей и возвращает для каждой запись, в поле Permission которой хранится значение MDP, получаемое напрямую через вызов FPDFSignatureObj_GetDocMDPPermission. Нулевое значение означает, что подпись не является сертификационной (DocMDP), то есть ограничения уровня документа отсутствуют.

var
  I: Integer;
  Sig: TPdfSignature;
begin
  if Pdf.SignatureCount = 0 then
    WriteLn('document is not signed')
  else
    for I := 0 to Pdf.SignatureCount - 1 do
    begin
      Sig := Pdf.Signature[I];
      case Sig.Permission of
        1: WriteLn('certified: no changes allowed');
        2: WriteLn('certified: form fill and signing allowed');
        3: WriteLn('certified: form fill, signing and annotations allowed');
      else
        WriteLn('signed, but not a DocMDP certification');
      end;
    end;
end;

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

Прочие угрозы: встроенные файлы и XFA

Две дополнительные возможности дополняют общую картину. Встроенные файлы представляют собой полноценные документы, прикрепленные к PDF в качестве вложений. Они часто используются для скрытой доставки данных, так как обычный отчет может содержать исполняемый файл или второй вредоносный документ в списке вложений. Компонент возвращает количество вложений и имена файлов, что позволяет сформировать их список без извлечения и открытия.

Флаг наличия XFA служит еще одним важным индикатором. Форма XFA заменяет статичные поля AcroForm на динамическую структуру на базе XML со своим механизмом рендеринга и скриптов, что существенно расширяет спектр анализируемых элементов. Для фиксации присутствия XFA не нужно ее обрабатывать, сам этот факт указывает на наличие сложного интерактивного слоя, требующего дополнительного внимания. Компонент возвращает эту информацию в виде простого логического значения.

var
  I: Integer;
begin
  if Pdf.XFA then
    WriteLn('NOTE: document contains an XFA form layer');

  if Pdf.AttachmentCount > 0 then
  begin
    WriteLn('embedded files: ', Pdf.AttachmentCount);
    for I := 0 to Pdf.AttachmentCount - 1 do
      WriteLn('  - ', Pdf.AttachmentName[I]);
  end;
end;

Создание отчета в режиме «только чтение»

В результате процедура аудита сводится к последовательности действий: загрузка документа, перечисление скриптов с их кодом, формирование списка целей Launch и URI, чтение уровня прав подписи MDP, фиксация наличия вложений и XFA с записью всех результатов в лог. Отсутствие рендеринга экономит ресурсы системы и защищает ее от выполнения вредоносного кода. Итоговый отчет в текстовом виде передается специалисту или автоматизированной системе для принятия решений.

На практике удобно записывать каждую находку в отдельную строку, помечать критические угрозы специальными префиксами для их вывода в начало очереди проверки и сохранять отчет рядом с анализируемым файлом. Документ без скриптов, внешних запусков, вложений, XFA и без нарушений сертификации одобряется автоматически. Файлы с несколькими сработавшими флагами отправляются на ручную проверку. Аудит не принимает решение о безопасности за вас, он лишь предоставляет необходимые данные для осознанного выбора.

Если документ прошел проверку и его необходимо открыть, делайте это в безопасном изолированном режиме. Подход, описанный в нашем руководстве по созданию безопасного предпросмотра PDF в Delphi, показывает способы отключения автоматической обработки ссылок и активного содержимого. Интеграция этих проверок в конвейеры обработки данных рассматривается в статье о рабочем столе для анализа и импорта PDF. Обе технологии используют общую базу без рендеринга в режиме «только чтение» и поставляются в комплекте PDFium Component для Delphi и C++Builder.