В янва­ре 2021 года вышел оче­ред­ной ре­лиз бра­узе­ра Chrome. В нем испра­вили 16 уяз­вимос­тей. Одну из них мы с тобой сегод­ня раз­берем, что­бы понять механизм воз­никно­вения таких багов и спо­собы экс­плу­ата­ции, с помощью которых зло­умыш­ленник может ата­ковать машину, оставшу­юся без обновле­ний.

Вер­сия Chrome, о которой пой­дет речь, — 87.0.4280.141. А инте­ресу­ющая нас запат­ченная уяз­вимость — CVE-2021-21112. Она каса­ется ком­понен­та потоков ком­прес­сии в бра­узер­ном движ­ке Blink и работа­ет по прин­ципу use after free. О баге сооб­щил иссле­дова­тель YoungJoo Lee (@ashuu_lee) из ком­пании RaonWhiteHat в нояб­ре 2020 года через сайт bugs.chromium.org, номер отче­та 1151298.

Blink — это бра­узер­ный дви­жок, на осно­ве которо­го работа­ет Chrome. А потоки сжа­тия — это те же веб‑потоки (web streams), но для удобс­тва веб‑раз­работ­чиков переда­ющиеся со сжа­тием. Что­бы не при­ходи­лось тянуть за про­ектом зависи­мос­ти типа zlib, соз­датели Chrome решили интегри­ровать фор­маты сжа­тия gzip и deflate в дви­жок Blink.

По сути, это удоб­ная обер­тка, тран­сфор­миру­ющий поток с алго­рит­мом тран­сфор­мации дан­ных по умол­чанию (или gzip, или deflate). Тран­сфор­миру­ющий поток — это объ­ект, содер­жащий два потока: чита­емый (readable) и записы­ваемый (writable). А меж­ду ними находит­ся тран­сфор­мер, который при­меня­ет задан­ный алго­ритм к про­ходя­щим меж­ду ними дан­ным.

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

 

Стенд

Для вос­про­изве­дения уяз­вимос­ти понадо­бит­ся стенд, сос­тоящий из вир­туаль­ной машины и уяз­вимой вер­сии Chrome. Готовую вир­туаль­ную машину мож­но заг­рузить с сай­та osboxes.org. Сайт пре­дос­тавля­ет обра­зы вир­туаль­ных машин как для VirtualBox, так и для VMware.

Я буду исполь­зовать образ Xubuntu 20 для VirtualBox. Читатель волен выбирать любой дис­три­бутив. Запус­каем машину, обновля­емся:

sudo apt update && sudo apt upgrade -y

Те­перь нам нуж­на уяз­вимая вер­сия бра­узе­ра.

Уяз­вимую вер­сию Chrome, ском­пилиро­ван­ную с ASan (AddressSanitizer), мож­но ска­чать с googleapis.com. В отче­те об уяз­вимос­ти ука­зано наз­вание нуж­ной сбор­ки, а имен­но билд asan-linux-release-812852. Рас­паковы­ваем архив:

unzip asan-linux-release-812852.zip

Го­товый билд сэконо­мит кучу вре­мени, так как сбор­ка бра­узе­ра тре­бует вре­мени, осо­бен­но если машина не очень мощ­ная.

AddressSanitizer — это детек­тор оши­бок памяти. Он пре­дос­тавля­ет инс­тру­мен­тацию во вре­мя ком­пиляции кода и биб­лиоте­ку вре­мени выпол­нения (runtime). Под­робнее о нем мож­но почитать на сай­те Clang.

Те­перь у нас готова вир­туаль­ная машина и ска­чан необ­ходимый билд Chrome. Помимо них, нам понадо­бит­ся Python 3 и LLVM. Обыч­но лог санитай­зера ASan выг­лядит нечита­емо, пос­коль­ку там ука­заны толь­ко адре­са и сме­щения. Разоб­рать­ся поможет ути­лита llvm-symbolizer, которая уста­нав­лива­ется вмес­те с LLVM. Она чита­ет эти адре­са и сме­щения и выводит соот­ветс­тву­ющие мес­та в исходном коде. Лог ASan будет выг­лядеть нам­ного понят­нее.

Ну а Python поможет нам готовить дан­ные для сжа­тия.

Все уста­нов­лено, теперь в бой!

 

Теория

Преж­де чем раз­бирать­ся в деталях уяз­вимос­ти, нам нуж­но нем­ного понимать пред­метную область.

Пре­дыс­тория все­го это­го такова. В кон­це 2019 года коман­да раз­работ­чиков Chromium реали­зова­ла новый JavaScript API, который называ­ется Compression Streams. Детали реали­зации при­веде­ны в от­чете.

Этот API осно­ван на спе­цифи­кации по­токов (спе­цифи­кация от 30 янва­ря 2020 года). Под­робно с его кон­цепци­ей можешь озна­комить­ся в ди­зайн‑докумен­те, допол­нитель­ные пояс­нения смот­ри на GitHub.

Я при­вожу более ста­рые вер­сии, так как уяз­вимость касалась имен­но их реали­зации. В даль­нейшем спе­цифи­кация потоков и их реали­зация в Chromium изме­нилась.

Те­перь раз­берем­ся в потоках пре­обра­зова­ния, потоках сжа­тия, объ­ектах promise и методе postMessage.

 

Потоки сжатия

По­токи сжа­тия осно­ваны на кон­цепции и реали­зации веб‑потоков. Отли­чие в том, что потоки ком­прес­сии могут сжи­мать и рас­паковы­вать дан­ные. На выбор — алго­рит­мы gzip и deflate, широко при­меня­емые в веб‑тех­нологи­ях. Потоки ком­прес­сии удов­летво­ряют спе­цифи­кации transform stream.

Ни­же при­веде­на схе­ма алго­рит­ма.

Гру­бо говоря, если дан­ные не кон­чились (счи­тан чанк), то вызыва­ется метод Transform, а тот вызовет метод ком­прес­сии или деком­прес­сии — в дан­ном слу­чае Inflate. В этом методе дан­ные обра­баты­вают­ся в цик­ле. Затем они помеща­ются в оче­редь потока. Для это­го вызыва­ется метод Enqueue.

То есть обра­баты­ваем кус­ки дан­ных и кла­дем их в оче­редь.

 

Promise

JavaScript час­то опи­сыва­ют как язык про­тотип­ного нас­ледова­ния. Каж­дый объ­ект име­ет объ­ект‑про­тотип — шаб­лон методов и свой­ств. Все объ­екты име­ют общий про­тотип Object.prototype и свой отдель­ный.

По­это­му при изме­нении каких‑то свой­ств или методов про­тоти­па новые объ­екты будут обла­дать изме­нен­ными свой­ства­ми или метода­ми.

Да­лее нас инте­ресу­ют асин­хрон­ное прог­рамми­рова­ние и «обе­щания» (promise). Рань­ше JavaScript исполнял­ся син­хрон­но, но это мешало веб‑стра­ницам быс­тро заг­ружать­ся и плав­но работать. Асин­хрон­ное прог­рамми­рова­ние поз­воля­ет обой­ти эту проб­лему. При ожи­дании какой‑то опе­рации (заг­рузки дан­ных по сети, чте­ния с дис­ка и тому подоб­ных) основной поток при­ложе­ния не бло­киру­ется, и оно не под­виса­ет.

Сна­чала в JavaScript внед­рили асин­хрон­ные кол­бэки (вызовы фун­кций по завер­шении опе­рации). Поз­днее при­дума­ли новый стиль написа­ния асин­хрон­ного кода — «обе­щания». Promise — это объ­ект, пред­став­ляющий асин­хрон­ную опе­рацию, выпол­ненную удач­но или неудач­но. На кар­тинке это наг­лядно изоб­ражено.

Источник — javascript.ru
Ис­точник — javascript.ru

Про­мис — это как бы про­межу­точ­ное сос­тояние: «Я обе­щаю вер­нуть­ся к вам с резуль­татом как мож­но ско­рее».

У объ­екта promise есть метод then. Он при­нима­ет два парамет­ра: фун­кции, которые нуж­но выз­вать в слу­чае раз­решения (resolve) или в слу­чае откло­нения (reject). В зависи­мос­ти от резуль­тата будет выз­вана соот­ветс­тву­ющая.

Осо­бен­ность JavaScript в том, что в этом язы­ке все явля­ется объ­ектом. По сути метод или фун­кция — это тоже объ­ект. И дос­туп к нему — это вызов объ­ектов get и set объ­екта — про­тоти­па объ­екта. Кра­сиво?

Осо­бен­ность объ­екта promise в том, что при его раз­решении (resolve) необ­ходимо выз­вать then. Дос­туп к это­му методу (get) мож­но изме­нить на поль­зователь­ский код, сме­нив общий для всех объ­ектов про­тотип:

Object.defineProperty(Object.prototype, "then", {
get() {
console.log("then getter executed");
}
});
 

postMessage

Как мы можем узнать из MDN Web Docs, этот метод поз­воля­ет обме­нивать­ся дан­ными меж­ду объ­екта­ми типа Window, нап­ример меж­ду стра­ницей и фрей­мом. Инте­рес­ная осо­бен­ность зак­люча­ется в том, как переда­ются дан­ные.

postMessage(message, targetOrigin, transfer);

Пос­ле вызова фун­кции вла­дение transfer переда­ется адре­сату, а на переда­ющей сто­роне прек­раща­ется.

Ес­ли вкрат­це, суть уяз­вимос­ти в том, что обра­бот­ка боль­шого мас­сива дан­ных про­исхо­дит в цик­ле и при добав­лении обра­ботан­ных чан­ков в оче­редь есть воз­можность выз­вать поль­зователь­ский код на JS. Это обес­печено тем, что объ­екту promise дано раз­решение на чте­ние из потока. Поль­зователь­ский код через postMessage может осво­бодить дан­ные, которые на тот момент обра­баты­вались в цик­ле.

Для более деталь­ного понима­ния всех кон­цепций мож­но обра­тить­ся к спе­цифи­кации. Мы же перехо­дим к прак­тике.

 

Запуск PoC

Пер­вым делом нуж­но уста­новить LLVM, так как с ним пос­тавля­ется сим­болиза­тор. Без него стек вызовов будет выг­лядеть непонят­но, пос­коль­ку не будет наз­ваний методов и имен фай­лов.

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии