March 23, 2021

Редактируем подписанный файл PDF, не ломая подписи

Го­тов пос­порить: ты навер­няка не раз стал­кивал­ся с докумен­тами в фор­мате PDF, име­ющи­ми циф­ровую под­пись. Под­писан­ные PDF-фай­лы исполь­зуют­ся в кон­трак­тах и сче­тах‑фак­турах, еще циф­ровой под­писью снаб­жают­ся все­воз­можные выпис­ки и справ­ки на сай­те Госус­луг или в лич­ном кабине­те налогоп­латель­щика. Циф­ровая под­пись дол­жна гаран­тировать под­линность и целос­тность содер­жимого докумен­та. Если его изме­нят, поль­зователь, открыв файл, уви­дит соот­ветс­тву­ющее пре­дуп­режде­ние. Одна­ко это огра­ниче­ние мож­но обой­ти с помощью нес­коль­ких хит­рых трю­ков.

PDF и XML — очень гиб­кие фор­маты. Гиб­кость дает удобс­тво, ради которо­го при­ходит­ся жер­тво­вать безопас­ностью. В обо­их фор­матах каж­дый год находят мно­жес­тво уяз­вимос­тей, свя­зан­ных как с крип­тогра­фией, так и со струк­турой самого фай­ла. И вот в оче­ред­ной раз доказа­но: даже элек­трон­ная под­пись не дает абсо­лют­ной уве­рен­ности, что в нед­рах элек­трон­ного докумен­та кто‑то не покопал­ся шалов­ливыми руками.

WARNING

Эта пуб­ликация пред­став­лена в информа­цион­но‑озна­коми­тель­ных целях. Ни автор, ни редак­ция не несут ответс­твен­ности за любой воз­можный вред, при­чинен­ный матери­ала­ми дан­ной статьи.

ЭКСКУРС В PDF

PDF, или Portable Document Format, — это, как вид­но из наз­вания, перено­симый элек­трон­ный документ. Этим фор­матом поль­зуют­ся во мно­гих областях, преж­де все­го в биз­несе. PDF был раз­работан ком­пани­ей Adobe еще в далеком 2005 году. Вплоть до 2020 года фор­мат пре­тер­певал изме­нения четыре раза: в него добав­лялись встро­енные фай­лы, ссыл­ки, скрип­ты и раз­ного рода фун­кции. Тот фор­мат, который мы зна­ем на момент написа­ния этой статьи (PDF 2.0), вышел не так дав­но — в кон­це 2020 года вмес­те с его спе­цифи­каци­ей ISO 32000-2.

INFO

PDF вклю­чает в себя часть фун­кций язы­ка PostScript. Мож­но тран­сли­ровать PDF в PostScript. Есть прин­теры, которые уме­ют самос­тоятель­но интер­пре­тиро­вать PDF в PostScript.

В пер­вом приб­лижении струк­тура самого докумен­та PDF не очень слож­ная. Файл сос­тоит из сле­дующих эле­мен­тов:

  • за­голо­вок (header);
  • те­ло фай­ла (body);
  • таб­лица перек­рес­тных ссы­лок (cross-reference table);
  • блок поис­ка таб­лиц объ­ектов и ссы­лок (trailer).
Струк­тура PDF-фор­мата

За­голо­вок — это всег­да самая пер­вая стро­ка фай­ла, которая опре­деля­ет номер спе­цифи­кации.

За­голо­вок PDF

Те­ло содер­жит ссыл­ки на объ­екты. К ним отно­сят­ся стра­ницы, изоб­ражения, шриф­ты. Ком­мента­рии могут рас­полагать­ся во всем PDF-фай­ле. Их син­таксис сов­пада­ет с син­такси­сом ком­мента­риев в PostScript, начина­ются они с сим­вола % и закан­чива­ются сим­волом кон­ца стро­ки.

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

Чем даль­ше в лес, тем боль­ше дров: за струк­турой фор­мата «пря­чет­ся» так называ­емая струк­тура пуб­ликации.

Струк­тура пуб­ликации

Уг­лублять­ся в эти деб­ри мы не будем, дос­таточ­но знать, что текст внут­ри докумен­та сжат и во внут­реннос­тях фай­ла его уви­деть не получит­ся. Вот как, нап­ример, выг­лядит гла­ва из кни­ги в фор­мате PDF, если попытать­ся прос­мотреть ее как прос­той тек­сто­вый файл.

Со­дер­жание PDF с точ­ки зре­ния компь­юте­ра

АТАКА

Сна­чала давай раз­берем­ся с уяз­вимостью. Пред­ста­вим, что у нас есть Али­са (под­писыва­ет документ) и Боб (получа­ет под­писан­ный Али­сой документ) и, естес­твен­но, сам документ. Али­са сво­им сер­тифика­том под­писала документ и отпра­вила его Бобу. Боб уве­рен в том, что документ под­линный, так как он с под­писью. Но вот что будет, если документ готови­ла не Али­са (она толь­ко под­писала) и отправ­ляла тоже не она? Ата­ка с под­меной содер­жимого под­писан­ного докумен­та обыч­но про­исхо­дит в три эта­па.

  1. Ха­кер готовит документ, в котором при­сутс­тву­ет так называ­емое теневое содер­жимое. Это мож­но срав­нить с пус­тым лис­том в сши­том догово­ре, куда мож­но добавить фраг­мент пос­ле под­писания.
  2. Ха­кер отправ­ляет этот документ Али­се. Али­са под­писыва­ет его и отправ­ляет обратно хакеру. Хакер меня­ет содер­жимое докумен­та так, что­бы не сло­мать под­пись.
  3. Ха­кер отправ­ляет изме­нен­ный документ Бобу. Боб уве­рен в том, что документ нас­тоящий, и обра­баты­вает его.

О сущес­тво­вании этой уяз­вимос­ти упо­мина­лось еще в 2012 году, ког­да PDF соот­ветс­тво­вал спе­цифи­кации ISO 19005-3. Одна­ко пос­ле выхода новой вер­сии фор­мата хакеры про­вели оче­ред­ное иссле­дова­ние и прив­лекли вни­мание ком­паний, раз­рабаты­вающих прог­рам­мное обес­печение для работы с PDF.

Есть три спо­соба изме­нить содер­жимое уже под­писан­ного докумен­та: скры­тие, замена, а так­же скры­тие и замена.

Скрытие

Ре­зуль­татом этой ата­ки будет сок­рытие какого‑либо кон­тента за видимым содер­жимым фай­ла. Как толь­ко хакер получа­ет под­писан­ный Али­сой документ, он манипу­лиру­ет докумен­том таким обра­зом, что видимый слой боль­ше не отоб­ража­ется в при­ложе­нии прос­мотра. При­чем теперь уже ранее невиди­мые объ­екты ста­новят­ся вид­ны в PDF-фай­ле.

Замена

Ос­новная идея вари­анта «Замена» — добавить в под­писан­ный документ новые объ­екты, которые счи­тают­ся без­вред­ными, но нап­рямую вли­яют на пред­став­ление содер­жимого. Нап­ример, опре­деле­ние шриф­тов нап­рямую не меня­ет содер­жимое. Одна­ко это вли­яет на вид отоб­ража­емо­го кон­тента и дела­ет воз­можной замену цифр или сим­волов.

Эта ата­ка нацеле­на на инте­рак­тивные фор­мы. Фор­мы под­держи­вают раз­личные мас­ки вво­да (нап­ример, тек­сто­вые поля, тек­сто­вые области, перек­лючате­ли и кноп­ки выбора), где поль­зовате­ли динами­чес­ки вво­дят новое содер­жимое и сох­раня­ют его в докумен­те. Фор­мы так­же могут иметь зна­чения по умол­чанию, которые при необ­ходимос­ти мож­но изме­нить.

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

Скрытие и замена

В вари­анте ата­ки «Скры­тие и замена» PDF-файл содер­жит вто­рой скры­тый документ с дру­гим содер­жимым. Так как Али­са не может уви­деть скры­тое содер­жимое, она под­писыва­ет документ. Пос­ле под­писания хакер получа­ет файл и добав­ляет толь­ко новую таб­лицу внеш­них ссы­лок и трей­лер. В таб­лице внеш­них ссы­лок про­исхо­дит лишь одно изме­нение: ссыл­ка на опи­сание.

Дру­гими сло­вами, хакер соз­дает файл PDF, содер­жащий два объ­екта с одним и тем же иден­тифика­тором, но раз­ным содер­жани­ем. Пос­коль­ку вклю­чение таб­лицы xref, ука­зыва­ющей на уже опре­делен­ный объ­ект в под­писан­ной области, счи­тает­ся без­вред­ным, никаких пре­дуп­режде­ний о вне­сен­ных хакером изме­нени­ях не пос­тупа­ет, и про­вер­ка под­писи про­ходит успешно. Тем не менее Боб и Али­са видят раз­ный кон­тент в одном и том же фай­ле.

Этот вари­ант ата­ки наибо­лее мощ­ный, так как поз­воля­ет изме­нить все содер­жимое докумен­та. Хакер может соз­дать теневой документ, вли­яющий на отоб­ражение каж­дой стра­ницы или даже на общее количес­тво стра­ниц, а так­же на каж­дый содер­жащий­ся в фай­ле объ­ект.

В то же вре­мя этот вари­ант самый «шат­кий», так как при под­писании могут быть уда­лены неис­поль­зуемые объ­екты, в том чис­ле — теневые. Кро­ме того, ска­нер безопас­ности может обна­ружить боль­шое количес­тво лиш­них незадей­ство­ван­ных объ­ектов и выдать пре­дуп­режде­ние.

Реализация

Я рас­смот­рю пер­вый вид ата­ки «Скры­тие» с эле­мен­тами ата­ки «Скры­тие и замена», так как они по сво­ей сути похожи. Что­бы реали­зовать ата­ку, нуж­но пом­нить: таб­лица перек­рес­тных ссы­лок (cross-reference table) — это обыч­ная тек­сто­вая таб­лица. Она начина­ется со сло­ва xref и содер­жит в себе ссыл­ки на все объ­екты.

При­мер таб­лицы перек­рес­тных ссы­лок

Циф­ра 0 на иллюс­тра­ции выше обоз­нача­ет пер­вый номер объ­екта в таб­лице, 334 — количес­тво объ­ектов. Пер­вый эле­мент таб­лицы всег­да име­ет вид XXXXXXXXXX 65535 f. 65535 — это чис­ло по умол­чанию для пер­вого эле­мен­та в таб­лице. F (free) — объ­ект не исполь­зует­ся. Нап­ротив исполь­зуемых объ­ектов сто­ит n. Далее идет XXXXXXXXXX — это сме­щение от начала фай­ла до объ­екта. Таких таб­лиц в докумен­те будет столь­ко, сколь­ко в фай­ле добав­ленных объ­ектов.

Пос­ле таб­лицы идет trailer, затем зна­чение startxref, ука­зыва­ющее сме­щение от начала фай­ла до таб­лицы. Чте­ние докумен­та прог­раммой начина­ется с кон­ца. Для выпол­нения ата­ки нам понадо­бят­ся сле­дующие инс­тру­мен­ты:

  • Adobe Acrobat DC;
  • Крип­тоПро CSP для Windows;
  • Крип­тоПро CSP для Linux;
  • Крип­тоПро PDF;
  • сер­тификат, ключ;
  • Notepad++ (им я редак­тирую внут­реннос­ти PDF);
  • ImDisk.

Пе­ред тем как мы нач­нем пот­рошить PDF-файл, необ­ходимо получить ключ с сер­тифика­том, которые будут при­год­ны для Крип­тоПро. Сге­нери­ровать ключ в OpenSSL не получит­ся. Поэто­му запус­каем Linux-машину, идем на сайт Крип­тоПро и ска­чива­ем дис­три­бутив для сво­ей ОС (у меня он пред­став­лен в виде фай­ла .deb). Пред­варитель­но надо зарегис­три­ровать­ся.

Спи­сок фай­лов в дис­три­бути­ве

Уста­нав­лива­ем пакет:

./install.sh

Те­перь перехо­дим в пап­ку /opt/cprocsp/bin/amd64. Там дол­жны появить­ся сле­дующие бинар­ники: certmgr, cpverify, cryptcp, csptest, csptestf, curl, der2xer, genkpim, inittst, wipefile.

Ге­нери­руем сер­тификат:

cryptcp -creatcert -provtype 81 -dn "C=RU, L=Rostov-na-Donu, CN=srv, 1.2.643.100.5=305867501589415" -exprt -ex -cont "\\\\.\\HDIMAGE\\srv" -ku -certusage "1.3.6.1.5.5.7.3.1" -hashAlg "1.2.643.7.1.1.2.3" -ca http://www.cryptopro.ru/certsrv

В этой коман­де исполь­зуют­ся сле­дующие клю­чи:

  • certusage — иден­тифика­тор наз­начения. Раз­лича­ют про­вер­ку сер­вера и кли­ента. Для нашего слу­чая он неважен;
  • hashAlg — алго­ритм шиф­рования для сер­тифика­та. Что­бы пос­мотреть, какие они есть, мож­но выпол­нить коман­ду cpconfig -defprov –view_type;
  • СА — центр сер­тифика­ции, который под­пишет нам пару клю­ча с сер­тифика­том. Дан­ная пара будет валид­ной, но дей­ство­вать будет толь­ко три месяца;
  • сont — имя кон­тей­нера, в которое сох­ранит­ся ключ с сер­тифика­том.

Про­веря­ем соз­данный кон­тей­нер:

./certmgr –list

Дол­жен появить­ся кон­тей­нер как на скрин­шоте ниже.

Кон­тей­нер в Linux-сис­теме

Пе­рехо­дим в пап­ку /var/opt/cprocsp/keys/root, в которой дол­жна появить­ся дирек­тория с име­нем, ука­зан­ным в коман­де генера­ции это­го кон­тей­нера. В этой дирек­тории ты най­дешь шесть фай­лов с рас­ширени­ем .key — это и есть кон­тей­нер. Ска­чива­ем и переки­дыва­ем его на машину с Windows.

Это еще не все. Крип­тоПро с ее CSP счи­тает (да и пра­виль­но счи­тает, ско­рее все­го), что крип­токон­тей­нер не дол­жен хра­нить­ся на жес­тком дис­ке. Для это­го были при­дума­ны токены. Но так как под рукой у нас токена нет, а есть толь­ко жес­ткий диск, будем изоб­ретать велоси­пед. Для это­го нам понадо­бит­ся ImDisk. Ска­чива­ем и уста­нав­лива­ем его.

Эта прог­рамма уме­ет соз­давать сво­его рода зак­рытое прос­транс­тво на дис­ке. Да, нам при­дет­ся соору­дить допол­нитель­ный вир­туаль­ный HDD у себя на компь­юте­ре. Я соз­дал раз­дел раз­мером 60 Мбайт, в нем — пап­ку и вот в нее закинул фай­лы кон­тей­нера. Теперь Крип­тоПро видит и ключ, и сер­тификат.

Крип­тоПро CSP

Пе­рехо­дим к под­готов­ке самого фай­ла. Необ­ходимо иметь на машине редак­тор PDF — я выб­рал проб­ную вер­сию Adobe Acrobat DC, а так­же Крип­тоПро PDF для воз­можнос­ти под­писать файл.

От­кры­ваем Acrobat и соз­даем пус­той файл. Пишем посере­дине ][aker. Сох­раня­ем и зак­рыва­ем его. При­мер­но так выг­лядят внут­реннос­ти пус­того фай­ла с одним сло­вом, которое мы вве­ли:

%PDF-1.3

3 0 obj

<</Type /Page

/Parent 1 0 R

/Resources 2 0 R

/Contents 4 0 R>>

endobj

4 0 obj

<</Filter /FlateDecode /Length 86>>

stream

xњ3Rрв2Р35W(зr

QРw3T02С30PISp

кZ*›лY)„¤(hg¦з)д¦*”д+¤§–($*Ґ–'Ґиi*„dЃф ьM

endstream

endobj

1 0 obj

<</Type /Pages

/Kids [3 0 R ]

/Count 1

/MediaBox [0 0 595.28 841.89]

>>

endobj

5 0 obj

<</Type /Font

/BaseFont /Helvetica-Bold

/Subtype /Type1

/Encoding /WinAnsiEncoding

>>

endobj

2 0 obj

<<

/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]

/Font <<

/F1 5 0 R

>>

/XObject <<

>>

>>

endobj

6 0 obj

<<

/Producer (PyFPDF 1.7.2 http://pyfpdf.googlecode.com/)

/CreationDate (D:20201116165607)

>>

endobj

7 0 obj

<<

/Type /Catalog

/Pages 1 0 R

/OpenAction [3 0 R /FitH null]

/PageLayout /OneColumn

>>

endobj

xref

0 8

0000000000 65535 f

0000000242 00000 n

0000000430 00000 n

0000000009 00000 n

0000000087 00000 n

0000000329 00000 n

0000000534 00000 n

0000000643 00000 n

trailer

<<

/Size 8

/Root 7 0 R

/Info 6 0 R

>>

startxref

746

%%EOF

Те­перь встав­ляем кар­тинку поверх над­писи, нас­тра­иваем нашу под­пись и под­писыва­ем документ. Откры­ваем его в Notepad++ и находим xref с таб­лицей для нашей кар­тинки. Меня­ем необ­ходимые зна­чения n на f, делая их скры­тыми.

За­мена таб­лицы на скры­тие объ­екта

Сра­зу ска­жу, что мож­но так­же спря­тать кар­тинку, обоз­начив ее как метадан­ные. Их обновле­ние прог­рамма не будет счи­тать вме­шатель­ством в содер­жимое докумен­та. Син­таксис метадан­ных PDF (ста­рого образца, но он работа­ет до сих пор, зна­чит, вер­сион­ность фор­матов под­держи­вает­ся) мож­но изу­чить, к при­меру, тут. Заг­ружа­ем файл, меня­ем метадан­ные и видим их в разоб­ранном виде. Далее методом проб и оши­бок пыта­емся под­кинуть изме­нен­ные дан­ные в файл. Дол­жно получить­ся при­мер­но сле­дующее:

stream

<?xpacket begin="п»ї" id="W5M0MpCehiHzreSzNTczkc9d"?>

<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.4-c006 80.159825, 2016/09/16-03:31:08 ">

<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">

<rdf:Description rdf:about=""

xmlns:xmp="http://ns.adobe.com/xap/1.0/"

xmlns:dc="http://purl.org/dc/elements/1.1/"

xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">

<xmp:ModifyDate>2019-08-07T13:48:36+02:00</xmp:ModifyDate>

<xmp:MetadataDate>2019-08-07T13:48:36+02:00</xmp:MetadataDate>

<xmp:CreateDate>2019-08-07T11:14:22+02:00</xmp:CreateDate>

<dc:format>application/pdf</dc:format>

<xmpMM:DocumentID>uuid:d24ecc88-2a52-0b43-9272-3abe5e065c85</xmpMM:DocumentID>

<xmpMM:InstanceID>uuid:9d3cd24f-99fe-0849-a303-c9cfac889c31</xmpMM:InstanceID>

</rdf:Description>

</rdf:RDF>

</x:xmpmeta>

<?xpacket end="w"?>

endstream

Пос­ле про­делан­ной работы сох­раня­ем файл и зак­рыва­ем редак­тор. Наде­юсь, боль­ше он нам не понадо­бит­ся. Итак, мы под­писали файл с фото, а при откры­тии докумен­та видим абсо­лют­но дру­гой кон­тент. Это доказы­вает, что уяз­вимость име­ет мес­то быть, одна­ко работа­ет она далеко не вез­де. В раз­ных вер­сиях раз­ных прог­рамм для прос­мотра PDF (кро­ме бра­узе­ров) модифи­циро­ван­ный файл может отоб­ражать­ся по‑раз­ному.

КАК ЗАЩИТИТЬСЯ

Ком­пания Adobe испра­вила ошиб­ки CVE-2020-9592 и CVE-2020-9596 в об­новле­нии, выпущен­ном 12 мая 2020 года. Прог­рамма Foxit Reader тоже выда­ет пре­дуп­режде­ние о безопас­ности при попыт­ке открыть изме­нен­ный документ с циф­ровой под­писью.

Ошиб­ки под­писи

Бо­лее ста­рые вер­сии ПО, к при­меру Adobe Acrobat Reader DC v 2019.021.20061, Foxit Reader 3.4.0.1012 и LibreOffice Draw до 6-й вер­сии, уяз­вимы для такого рода атак. Наибо­лее акту­аль­ная на дан­ный момент защита — не исполь­зовать прог­рам­мное обес­печение уста­рев­ших вер­сий. Что каса­ется любите­лей пос­мотреть PDF через бра­узер, то ни один бра­узер не про­веря­ет под­пись в докумен­те, а посему для них этот трюк будет работать еще очень дол­го.

ЗАКЛЮЧЕНИЕ

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