Ядро WordPress — слепой SSRF без аутентификации
WordPress — самая популярная в мире система управления контентом, которую используют более 40% всех веб-сайтов . Такое широкое распространение делает его главной целью для злоумышленников и исследователей безопасности, которым платят за сообщения о проблемах безопасности в рамках их общедоступной программы поощрения ошибок.
Брокеры уязвимостей также очень заинтересованы в приобретении непропатченных уязвимостей, позволяющих им захватить WordPress, иногда предлагая до 300 000 долларов за критические уязвимости. Таким образом, WordPress имеет хорошо изученную кодовую базу, в которой исследователи уже не смогут найти низко висящие плоды. Наши предыдущие исследования этой цели требовали большого опыта и усилий для выявления проблем безопасности.
В этом сообщении блога описывается удивительно простая уязвимость в реализации пингбэков в WordPress. Хотя в случае с WordPress влияние этой уязвимости для большинства пользователей невелико, связанный с ней уязвимый шаблон кода довольно интересен для документирования, поскольку он также, вероятно, присутствует в большинстве веб-приложений. Цель этой записи в блоге - рассказать об этом шаблоне и повысить осведомленность.
Раскрытие информации
Мы не смогли определить общие способы использования этого поведения для захвата уязвимых экземпляров без использования других уязвимых сервисов.
Это может облегчить эксплуатацию других уязвимостей во внутренней сети пострадавшей организации, например, используя одну из недавних инъекций OGNL в Confluence, эпическое удаленное выполнение кода в Jenkins, найденное @orange_8361, или одну из других цепочек, задокументированных AssetNote.
Технические подробности
Pingbacks - это способ для авторов блогов получать уведомления и показывать, когда другие "дружественные" блоги ссылаются на данную статью: они отображаются рядом с комментариями и могут быть свободно приняты или отклонены. Под капотом блоги должны выполнять HTTP-запросы друг к другу, чтобы определить наличие ссылок. Посетители также могут запустить этот механизм. Эта функция подверглась широкой критике, поскольку она позволяет злоумышленникам осуществлять распределенные атаки типа "отказ в обслуживании", злонамеренно запрашивая тысячи блогов проверить наличие обратных ссылок на одном сервере-жертве. Pingbacks все еще включены по умолчанию на экземплярах WordPress из-за важности социальных и общественных функций, когда речь идет о личном блоггинге. Однако не предполагается, что эти запросы могут быть отправлены другим внутренним службам, расположенным на том же сервере или в том же сегменте локальной сети. Функциональность pingback реализована в API XML-RPC WordPress. Напомним, что это конечная точка API, ожидающая XML-документы, в которых клиент может выбрать функцию для вызова вместе с аргументами. Один из реализованных методов - pingback.ping, ожидающий аргументы pagelinkedfrom и pagelinkedto: первый - это адрес статьи, ссылающейся на вторую pagelinkedto должен указывать на существующую статью локального экземпляра, здесь http://blog.tld/?p=1, а pagelinkedfrom - на внешний URL, который должен содержать ссылку на pagelinkedto.
Ниже показано, как будет выглядеть запрос к этой конечной точке:
POST /xmlrpc.php HTTP/1.1 Host: blog.tld [...] <methodCall> <methodName>pingback.ping</methodName> <params> <param> <value><string>http://evil.tld</string></value> </param> <param> <value><string>http://blog.tld/?p=1</string></value> </param> </params> </methodCall>
Реализация проверки URL
Метод WordPress Core wp_http_validate_url() выполняет несколько проверок URL-адресов, предоставляемых пользователями, чтобы снизить риск злоупотреблений. Например:
- Место назначения не может содержать имя пользователя и пароль;
- Имя хоста не должно содержать следующих символов: #:?[]
- Доменное имя не должно указывать на локальный или частный IP-адрес, например 127.0.0.1, 192.168.* и т.д.
- Порт назначения URL должен быть либо 80, либо 443, либо 8080.
Третий шаг может включать разрешение доменных имен, если они присутствуют в URL (например, http://foo.bar.tld). В этом случае IP-адрес удаленного сервера получается путем разбора URL [1] и последующего его разрешения [2] перед проверкой для исключения непубличных диапазонов IP-адресов:
$parsed_url = parse_url( $url ); // [1] // [...] $ip = gethostbyname( $host ); // [2] if ( $ip === $host ) { // Error condition for gethostbyname(). return false; } // IP validation happens here }
Код проверки выглядит правильно реализованным, и URL теперь считается доверенным. Что происходит дальше?
Реализация HTTP-клиента(ов)
Два HTTP-клиента могут обрабатывать запросы pingback после проверки URL, основываясь на доступных функциях PHP: Requests_Transport_cURL и Requests_Transport_fsockopen. Они оба являются частями библиотеки Requests, разработанной независимо под зонтиком WordPress.
Давайте посмотрим на реализацию последней. Из названия мы знаем, что она использует API потоков PHP. Он работает на транспортном уровне, и клиенту приходится формировать HTTP-запрос вручную. URL снова разбирается с помощью parse_url(), а затем его хост-часть используется для создания назначения, совместимого с PHP streams API (например, tcp://host:port):
public function request($url, $headers = array(), $data = array(), $options = array()) { // [...] $url_parts = parse_url($url); // [...] $host = $url_parts['host']; else { $remote_socket = 'tcp://' . $host; } // [...] $remote_socket .= ':' . $url_parts['port'];
Далее это место назначения используется для создания нового потока с помощью stream_socket_client(), а HTTP-запрос формируется и записывается в него:
$socket = stream_socket_client($remote_socket, $errno, $errstr, ceil($options['connect_timeout']), STREAM_CLIENT_CONNECT, $context); // [...] $out = sprintf("%s %s HTTP/%.1F\r\n", $options['type'], $path, $options['protocol_version']); // [...] if (!isset($case_insensitive_headers['Host'])) { $out .= sprintf('Host: %s', $url_parts['host']); // [...] } // [...] fwrite($socket, $out);
Как мы видим, этот процесс подразумевает еще одно разрешение DNS, поэтому stream_socket_client() может определить IP хоста для отправки пакетов.
Поведение другого HTTP-клиента, cURL, очень похоже и здесь рассматриваться не будет.
Уязвимость
У этой конструкции есть проблема: HTTP-клиенту приходится заново анализировать URL и заново определять имя хоста, чтобы отправить запрос. Между тем, злоумышленник мог изменить домен так, чтобы он указывал на другой адрес, а не на тот, который был подтвержден ранее! Этот класс ошибок также называется Time-of-Check-Time-of-Use: ресурс подтвержден, но может быть изменен позже, до его эффективного использования. Такие уязвимости часто встречаются в средствах защиты от подделки запросов на стороне сервера (SSRF).
Мы обобщили эти последовательные шаги на приведенной ниже схеме:
Мы провели аудит кода в надежде найти ошибки дифференциации парсера, которые позволили бы обращаться к нежелательным портам или безуспешно выполнять POST-запросы: начальные шаги проверки URL достаточно ограничительны, чтобы предотвратить их эксплуатацию. Как упоминалось ранее, злоумышленникам придется связать это поведение с другой уязвимостью, чтобы существенно повлиять на безопасность целевой организации.
На момент написания данной публикации нам не известно о наличии какого-либо публичного патча; приведенные выше сведения основаны на промежуточном патче, предоставленном нам в процессе раскрытия информации.
Устранение подобных уязвимостей требует сохранения проверенных данных до тех пор, пока они не будут использованы для выполнения HTTP-запроса. Они не должны отбрасываться или преобразовываться после этапа проверки.
Разработчики WordPress пошли по этому пути, введя второй, необязательный аргумент в функцию wp_http_validate_url(). Этот параметр передается по ссылке и содержит IP-адреса, на которых WordPress выполнил проверку. Окончательный код немного более многословен, чтобы приспособиться к более старым версиям PHP, но основная идея здесь.
В качестве временного обходного пути мы рекомендуем системным администраторам удалить обработчик pingback.ping конечной точки XMLRPC. Один из способов сделать это - обновить functions.php используемой темы, чтобы ввести следующий вызов:
add_filter('xmlrpc_methods', function($methods) { unset($methods['pingback.ping']); return $methods; });
Также можно заблокировать доступ к xmlrpc.php на уровне веб-сервера.
РезюмеВ этой статье мы описали слепую SSRF-уязвимость, затрагивающую WordPress Core. Хотя в данном случае воздействие считается незначительным, это широко распространенный уязвимый шаблон кода, который мы продолжаем встречать даже в крупных проектах. Мы призываем разработчиков проверить свои собственные базы кода на наличие уязвимостей такого типа, которые, как мы показали, могут скрываться даже в очень популярном и хорошо отрецензированном коде.
Наш канал : https://t.me/webhack_kakao