Взлом сайта на PHP с помощью уязвимости XXE

Hack The Box

Продолжим проходжение заданий с площадки Hack The Box. На этот раз задание BountyHunter — взлом сайта на PHP с помощью уязвимости XXE.

Взлом сайта на PHP с помощью уязвимости XXE

Для начала до­бав­им IP-адрес машины в /etc/hosts:

Сканирование портов

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

Самый популярный сканер — это Nmap. Следующий скрипт улучшит резуль­таты сканирования:

Ре­зуль­тат скрип­та Nmap
Ре­зуль­тат скрип­та Nmap

На­ходим два откры­тых пор­та:

  • 80 (веб‑сер­вер Apache 2.4.41)
  • 22 (служ­ба SSH)

Справка: брутфорс учеток

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

 

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

Взлом сайта на PHP

Кноп­ка нерабо­чая, но ссыл­ка Portal открывает стра­ницу portal.php с тек­стом «Portal under development. Go here to test the bounty tracker» и еще какой-то ссыл­кой. При перехо­де по этой ссылке появляется фор­ма вво­да «Bounty Report System — Beta».

Точка входа

После заполнения дан­ных, мы видим сооб­щение, что если база дан­ных готова, то наши дан­ные будут добавлены в нее.

Фор­ма Bounty Report System
Bounty Report System

Пер­вым делом можно протестировать SQL-инъ­екцию в зап­росе Insert, но все же стоит взгля­нуть на весь путь зап­росов через Burp. Все зап­росы мож­ете уви­деть в Burp History.

Вклад­ка Burp History
Вклад­ка Burp History

Бро­силось в гла­за то, что я запол­нил четыре парамет­ра, а отправ­ился один, при­чем в кодиров­ке Base64. Хорошо, что в Burp Pro добавили плагин Inspector, который авто­матом раз­бира­ет и сни­мает извес­тные кодиров­ки.

Burp Inspector
Burp Inspector

Имен­но Inspector подсказал мене перей­ти с тес­тирова­ния SQL-инъ­екции на поиск XXE. А все потому, что парамет­ры отправ­ляют­ся в фор­мате XML.

Точка опоры

XXE-инъекция

XML eXternal Entity (XXE) инъ­екция — это уяз­вимость в сер­висах, которые под­держи­вают про­токо­лы SOAP и XML-RPC и при­нима­ют вход­ные дан­ные в виде XML-докумен­та. Стан­дарт XML под­держи­вает вклю­чение сек­ции DTD, а сек­ции эти, в свою оче­редь, могут под­клю­чать к докумен­ту допол­нитель­ные ком­понен­ты, так называ­емые внеш­ние сущ­ности (см. Эксплуатация уязвимости XXE).

Внеш­ние сущ­ности — это отдель­ные фай­лы, и добав­ляют­ся они с помощью клю­чево­го сло­ва SYSTEM и URI. Если XML-пар­сер невали­диру­ющий, то он может прос­то заг­рузить внеш­нюю сущ­ность и под­клю­чить к содер­жимому XML-докумен­та. У зло­умыш­ленни­ка есть воз­можность под­ста­вить в URI внеш­ней сущ­ности file URI, ука­зыва­ющий на уда­лен­ный или локаль­ный файл в сис­теме.

А теперь давайте перей­дем к нашему при­меру. Ниже пред­став­лен документ XML, который отправ­ляет­ся на сер­вер.

Что­бы поис­кать уяз­вимость, под­клю­чим сна­чала внут­реннюю сущ­ность. Для это­го опре­делим сек­цию DOCTYPE и сущ­ность (ENTITY) testXXE со зна­чени­ем RALF. В самом докумен­те можем обра­щать­ся к нашей сущ­ности testXXX как буд­то через ссыл­ку &testXXE;. При­чем вста­вим пос­ледова­тель­ность для тес­та абсо­лют­но во все парамет­ры, что­бы уве­личить шан­сы.

Зап­рос, тес­тиру­ющий XXE
Зап­рос, тес­тиру­ющий XXE

В ито­ге вмес­то &testXXE; под­став­ляет­ся зна­чение RALF, что сви­детель­ству­ет о наличии XXE. Давайте поп­робу­ем ее раз­вить: к при­меру, про­читать локаль­ный файл в сис­теме. Будем читать дос­тупный всег­да и для всех поль­зовате­лей файл /etc/passwd. Для это­го опре­делим сущ­ность, но уже не как локаль­ную стро­ку, а как внеш­ний файл в сис­теме:

До­кумент получил­ся сле­дующий:

Со­дер­жимое фай­ла /etc/passwd
Со­дер­жимое фай­ла /etc/passwd

Сис­тема вер­нула нам содер­жимое жела­емо­го фай­ла. В основном все поль­зовате­ли стан­дар­тные и отно­сят­ся к сис­теме, а вот логин development — нет. Инте­рес­но!

Сле­дующий шаг — поиск учет­ных дан­ных. Раз мы можем читать про­изволь­ные фай­лы, про­верим исходни­ки сай­та. Нач­нем с извес­тной нам стра­ницы tracker_diRbPr00f314.php. Но прос­то передать файл в сущ­ность теперь не вый­дет, так как при зап­росе содер­жимого кода на PHP веб‑сер­вер не отоб­разит, а выпол­нит его. Что­бы это­го не слу­чалось, будем исполь­зовать обер­тку PHP.

PHP wrappers

PHP пос­тавля­ется со мно­жес­твом встро­енных обер­ток для обра­щения к фун­кци­ям фай­ловой сис­темы, таким как fopen(), copy(), file_exists() и filesize(). В слу­чаях, подоб­ных нашему, чаще все­го исполь­зует­ся обер­тка, реали­зующая схе­му php://, которая пре­дос­тавля­ет дос­туп к потокам вво­да‑вывода.

В час­тнос­ти, как фай­лы в PHP мож­но открыть:

  • собс­твен­ные потоки вво­да‑вывода PHP;
  • дес­крип­торы стан­дар­тно­го вво­да, вывода и потока оши­бок;
  • вре­мен­ные фай­ловые потоки в памяти и на дис­ке;
  • филь­тры, которые могут манипу­лиро­вать дру­гими фай­ловыми ресур­сами по мере их счи­тыва­ния или записи.

Раз­берем­ся с филь­тра­ми. По адре­су php://filter дос­тупна мета­обер­тка, которая поз­воля­ет при­менять филь­тры к потоку во вре­мя откры­тия. Это полез­но для фун­кций вро­де readfile(), file() и file_get_contents(), где нет воз­можнос­ти при­менить филь­тр к потоку до того, как содер­жимое будет про­чита­но. А это наш слу­чай.

По­ток php://filter при­нима­ет как часть пути сле­дующие парамет­ры:

  • resource — ука­зыва­ет потоку, что его необ­ходимо отфиль­тро­вать;
  • read — одно имя или нес­коль­ко имен филь­тров для чте­ния, раз­делен­ных сим­волом |;
  • write — одно имя или нес­коль­ко имен филь­тров для записи, раз­делен­ных сим­волом |;
  • [список фильтров для применения к обеим цепочкам] — спи­сок филь­тров, который будет при­менен к обо­им потокам на чте­ние и на запись.

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

Это при­ведет к тому, что на сай­те будет отоб­ражен код тре­буемой стра­ницы, но сра­зу закоди­рован­ный в Base64. При­меним обер­тку в нашей XXE-наг­рузке:

От­прав­ка зап­росов с PHP-филь­тром
От­прав­ка зап­росов с PHP-филь­тром

Те­перь в Burp выделя­ем текст, закоди­рован­ный в Base64, и нажима­ем Ctrl-Shift-B. Откро­ется новое окно Burp с декоди­рован­ным тек­стом.

Де­коди­рован­ный код tracker_diRbPr00f314.php
Де­коди­рован­ный код tracker_diRbPr00f314.php

Ни­чего инте­рес­ного в этом фай­ле не находим, как и в коде осталь­ных извес­тных нам стра­ниц сай­та. В таком слу­чае прос­каниру­ем фай­лы PHP в кор­невом катало­ге сай­та, к при­меру с помощью ffuf.

Сканирование веба c fuff

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

Я пред­почитаю лег­кий и очень быс­трый ffuf. При запус­ке ука­зыва­ем сле­дующие парамет­ры:

  • -w — сло­варь (исполь­зуем directory-list-2.3-medium из набора SecLists);
  • -t — количес­тво потоков;
  • -u — URL;
  • -fc — исклю­чить из резуль­тата отве­ты с кодом 403.

Спи­сок популяр­ных фай­лов PHP мож­но най­ти в наборе сло­варей SecLists.

Ре­зуль­тат ска­ниро­вания фай­лов
Ре­зуль­тат ска­ниро­вания фай­лов

Там мы находим новый файл — db.php. Это файл, который обес­печива­ет под­клю­чение к базе дан­ных и дол­жен содер­жать необ­ходимые парамет­ры, в том чис­ле учет­ные дан­ные для дос­тупа к БД.

Со­дер­жимое фай­ла db.php
Со­дер­жимое фай­ла db.php

Най­ден­ный пароль под­ходит для обна­ружен­ного в фай­ле /etc/passwd юзе­ра development, и мы получа­ем флаг поль­зовате­ля.

Флаг поль­зовате­ля
Флаг поль­зовате­ля

Локальное повышение привилегий

Для повыше­ния при­виле­гий в Linux всег­да сто­ит про­верять нас­трой­ки sudoers и при­ложе­ния с выс­тавлен­ным битом SUID.

Справка: sudoers

Файл /etc/sudoers в Linux содер­жит спис­ки команд, которые раз­ные груп­пы поль­зовате­лей могут выпол­нять от име­ни адми­нис­тра­тора сис­темы. Мож­но прос­мотреть его как нап­рямую, так и при помощи коман­ды sudo -l.

При про­вер­ке sudoers (sudo -l) сра­зу находим зацеп­ку.

Нас­трой­ки судо­ера
Нас­трой­ки судо­ера

Та­ким обра­зом, мы можем без вво­да пароля выпол­нить скрипт /opt/skytrain_inc/ticketValidator.py в при­виле­гиро­ван­ном кон­тек­сте. Пос­мотрим, что в нем!

Со­дер­жимое фай­ла ticketValidator.py
Со­дер­жимое фай­ла ticketValidator.py

Нач­нем ана­лиз с фун­кции main. Пер­вым делом у нас зап­росят путь к какому‑то фай­лу и переда­дут то, что мы вве­ли, в фун­кцию load_file. В ней про­веря­ется рас­ширение фай­ла. Если это файл в фор­мате Markdown (.md), то он откры­вает­ся, а прог­рамма про­дол­жает работать.

За­тем дес­крип­тор откры­того фай­ла переда­ется в фун­кцию evaluate. Она пос­троч­но счи­тыва­ет и про­веря­ет файл. Так, пер­вой стро­кой дол­жно быть выраже­ние # Skytrain Inc, вто­рая стро­ка дол­жна начинать­ся с пос­ледова­тель­нос­ти ## Ticket to, а третья — __Ticket Code:__. Все осталь­ные стро­ки дол­жны начинать­ся сим­волами **.

Ес­ли все про­вер­ки прой­дены, то, начиная с чет­вертой стро­ки, в перемен­ную ticketCode будет счи­тывать­ся чис­ло меж­ду сим­волами ** и +. При этом оста­ток от деления это­го чис­ла на 7 дол­жен быть равен 4. В этом слу­чае вся стро­ка без сим­волов ** переда­ется в фун­кцию eval.

Мы доб­рались до вызова опас­ной фун­кции, которая дол­жна выпол­нять код Python, — тут и кро­ется уяз­вимость. Так как Python — это не сце­нар­ный язык вро­де bash, информа­цию об ошиб­ках мы получим толь­ко во вре­мя выпол­нения кода.

К при­меру, если я передам в фун­кцию eval стро­ку 4+sdffds, то прог­рамма сра­зу сиг­нализи­рует об ошиб­ке. Но если в ту же фун­кцию eval передать стро­ку (4+print('qwe')), то ошиб­ка будет выз­вана не сра­зу, так как Python сна­чала выпол­нит фун­кцию print, а потом eval.

Тес­товый запуск кода
Тес­товый запуск кода

Та­ким обра­зом, мы можем помес­тить в ито­говый файл код на Python пос­ле зна­ка +, и этот код будет выпол­нен. Можем перехо­дить к выбору наг­рузки.

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

где module — импорти­руемый модуль, method — фун­кция из это­го модуля, а param — парамет­ры фун­кции.

Тес­товый вызов фун­кции Base64
Тес­товый вызов фун­кции Base64

Под­клю­чать будем модуль pty, из которо­го исполь­зуем фун­кцию spawn для соз­дания коман­дной обо­лоч­ки в при­виле­гиро­ван­ном кон­тек­сте. Объ­еди­нив всё, получим сле­дующий файл Markdown:

Флаг рута
Флаг рута

Ма­шина зах­вачена, и мы получа­ем флаг рута.

Заключение

На этом все. Теперь вы знаете, как взломать сайт на PHP с помощью уязвимости XXE.

Еще по теме:

Дима (Kozhuh)

Эксперт в кибербезопасности. Работал в ведущих компаниях занимающихся аналитикой компьютерных угроз. Анонсы новых статей в Телеграме.

Добавить комментарий